From 20f881fc1453dc71b85396b9a3659bbaf93f2176 Mon Sep 17 00:00:00 2001 From: ryanbajollari <54822716+rbajollari@users.noreply.github.com> Date: Mon, 27 Mar 2023 14:04:00 -0400 Subject: [PATCH] feat: Remove price-feeder and use Ojo's umee price-feeder for e2e tests (#1939) * Remove price-feeder dir and use umee price-feeder docker file * e2e working * Add price-feeder README * Use latest pf docker image * Remove price-feeder workflow * Remove unneccesary pf references * Fix Makefile conflicts * Try no experimental * revert test workflow * add e2e pf err log * pf log * Remove logging code in runPriceFeeeder * Add log command at failure * Update log at failure * bad flag test * remove bad flag * build price-feeder container with amd64 * possible fix in actions * remove container: ubuntu * Fix health check * try with ojo pf * ojo network pf docker container * remove experimental flag e2e * Update readme * Move pf readme to pf dir * pr comments * release compatibility matrix * release note * change back to experimental e2e * Use umee pf docker container * Back to ojo pf container * umee docker container --- .dockerignore | 1 - .../workflows/release-price-feeder-docker.yml | 55 - .github/workflows/release-price-feeder.yml | 52 - .github/workflows/tests.yml | 22 - .mergify.yml | 9 - Makefile | 2 +- README.md | 4 +- contrib/images/price-feeder.dockerfile | 29 - contrib/images/umee.e2e.dockerfile | 8 +- price-feeder/.goreleaser.yml | 46 - price-feeder/CHANGELOG.md | 271 -- price-feeder/LICENSE | 201 -- price-feeder/Makefile | 62 - price-feeder/README.md | 153 +- price-feeder/cmd/price-feeder.go | 298 --- price-feeder/cmd/version.go | 70 - price-feeder/config/config.go | 303 --- price-feeder/config/config_test.go | 761 ------ .../config/currency_provider_tracker.go | 228 -- price-feeder/go.mod | 322 --- price-feeder/go.sum | 2207 ----------------- price-feeder/main.go | 9 - price-feeder/oracle/client/chain_height.go | 112 - price-feeder/oracle/client/client.go | 282 --- price-feeder/oracle/client/tx.go | 78 - price-feeder/oracle/convert.go | 214 -- price-feeder/oracle/convert_test.go | 269 -- price-feeder/oracle/filter.go | 151 -- price-feeder/oracle/filter_test.go | 128 - price-feeder/oracle/grpc.go | 34 - price-feeder/oracle/oracle.go | 658 ----- price-feeder/oracle/oracle_test.go | 812 ------ price-feeder/oracle/param.go | 45 - price-feeder/oracle/param_test.go | 65 - price-feeder/oracle/prices.go | 47 - price-feeder/oracle/provider/binance.go | 387 --- price-feeder/oracle/provider/binance_test.go | 103 - price-feeder/oracle/provider/bitget.go | 457 ---- price-feeder/oracle/provider/bitget_test.go | 182 -- price-feeder/oracle/provider/coinbase.go | 447 ---- price-feeder/oracle/provider/coinbase_test.go | 101 - price-feeder/oracle/provider/crypto.go | 449 ---- price-feeder/oracle/provider/crypto_test.go | 122 - price-feeder/oracle/provider/gate.go | 513 ---- price-feeder/oracle/provider/gate_test.go | 101 - price-feeder/oracle/provider/huobi.go | 452 ---- price-feeder/oracle/provider/huobi_test.go | 107 - price-feeder/oracle/provider/kraken.go | 575 ----- price-feeder/oracle/provider/kraken_test.go | 112 - price-feeder/oracle/provider/mexc.go | 388 --- price-feeder/oracle/provider/mexc_test.go | 99 - price-feeder/oracle/provider/mock.go | 150 -- price-feeder/oracle/provider/mock_test.go | 77 - price-feeder/oracle/provider/okx.go | 418 ---- price-feeder/oracle/provider/okx_test.go | 107 - price-feeder/oracle/provider/osmosis.go | 222 -- price-feeder/oracle/provider/osmosis_test.go | 193 -- price-feeder/oracle/provider/osmosisv2.go | 376 --- .../oracle/provider/osmosisv2_test.go | 119 - price-feeder/oracle/provider/provider.go | 114 - price-feeder/oracle/provider/telemetry.go | 99 - .../oracle/provider/websocket_controller.go | 311 --- .../provider/websocket_controller_test.go | 62 - price-feeder/oracle/types/candle_price.go | 29 - .../oracle/types/candle_price_test.go | 37 - price-feeder/oracle/types/currency.go | 28 - price-feeder/oracle/types/errors.go | 22 - price-feeder/oracle/types/ticker_price.go | 28 - .../oracle/types/ticker_price_test.go | 34 - price-feeder/oracle/util.go | 222 -- price-feeder/oracle/util_test.go | 369 --- price-feeder/pkg/httputil/http.go | 32 - price-feeder/pkg/sync/sync.go | 33 - price-feeder/price-feeder.example.toml | 308 --- price-feeder/router/middleware/middleware.go | 72 - price-feeder/router/v1/metrics.go | 7 - price-feeder/router/v1/oracle.go | 16 - price-feeder/router/v1/response.go | 58 - price-feeder/router/v1/router.go | 151 -- price-feeder/router/v1/router_test.go | 153 -- .../tests/integration/provider_test.go | 125 - price-feeder/tools/tools.go | 13 - tests/e2e/e2e_setup_test.go | 124 +- tests/e2e/scripts/price_feeder_bootstrap.sh | 159 -- 84 files changed, 75 insertions(+), 16796 deletions(-) delete mode 100644 .github/workflows/release-price-feeder-docker.yml delete mode 100644 .github/workflows/release-price-feeder.yml delete mode 100644 contrib/images/price-feeder.dockerfile delete mode 100644 price-feeder/.goreleaser.yml delete mode 100644 price-feeder/CHANGELOG.md delete mode 100644 price-feeder/LICENSE delete mode 100644 price-feeder/Makefile delete mode 100644 price-feeder/cmd/price-feeder.go delete mode 100644 price-feeder/cmd/version.go delete mode 100644 price-feeder/config/config.go delete mode 100644 price-feeder/config/config_test.go delete mode 100644 price-feeder/config/currency_provider_tracker.go delete mode 100644 price-feeder/go.mod delete mode 100644 price-feeder/go.sum delete mode 100644 price-feeder/main.go delete mode 100644 price-feeder/oracle/client/chain_height.go delete mode 100644 price-feeder/oracle/client/client.go delete mode 100644 price-feeder/oracle/client/tx.go delete mode 100644 price-feeder/oracle/convert.go delete mode 100644 price-feeder/oracle/convert_test.go delete mode 100644 price-feeder/oracle/filter.go delete mode 100644 price-feeder/oracle/filter_test.go delete mode 100644 price-feeder/oracle/grpc.go delete mode 100644 price-feeder/oracle/oracle.go delete mode 100644 price-feeder/oracle/oracle_test.go delete mode 100644 price-feeder/oracle/param.go delete mode 100644 price-feeder/oracle/param_test.go delete mode 100644 price-feeder/oracle/prices.go delete mode 100644 price-feeder/oracle/provider/binance.go delete mode 100644 price-feeder/oracle/provider/binance_test.go delete mode 100644 price-feeder/oracle/provider/bitget.go delete mode 100644 price-feeder/oracle/provider/bitget_test.go delete mode 100644 price-feeder/oracle/provider/coinbase.go delete mode 100644 price-feeder/oracle/provider/coinbase_test.go delete mode 100644 price-feeder/oracle/provider/crypto.go delete mode 100644 price-feeder/oracle/provider/crypto_test.go delete mode 100644 price-feeder/oracle/provider/gate.go delete mode 100644 price-feeder/oracle/provider/gate_test.go delete mode 100644 price-feeder/oracle/provider/huobi.go delete mode 100644 price-feeder/oracle/provider/huobi_test.go delete mode 100644 price-feeder/oracle/provider/kraken.go delete mode 100644 price-feeder/oracle/provider/kraken_test.go delete mode 100644 price-feeder/oracle/provider/mexc.go delete mode 100644 price-feeder/oracle/provider/mexc_test.go delete mode 100644 price-feeder/oracle/provider/mock.go delete mode 100644 price-feeder/oracle/provider/mock_test.go delete mode 100644 price-feeder/oracle/provider/okx.go delete mode 100644 price-feeder/oracle/provider/okx_test.go delete mode 100644 price-feeder/oracle/provider/osmosis.go delete mode 100644 price-feeder/oracle/provider/osmosis_test.go delete mode 100644 price-feeder/oracle/provider/osmosisv2.go delete mode 100644 price-feeder/oracle/provider/osmosisv2_test.go delete mode 100644 price-feeder/oracle/provider/provider.go delete mode 100644 price-feeder/oracle/provider/telemetry.go delete mode 100644 price-feeder/oracle/provider/websocket_controller.go delete mode 100644 price-feeder/oracle/provider/websocket_controller_test.go delete mode 100644 price-feeder/oracle/types/candle_price.go delete mode 100644 price-feeder/oracle/types/candle_price_test.go delete mode 100644 price-feeder/oracle/types/currency.go delete mode 100644 price-feeder/oracle/types/errors.go delete mode 100644 price-feeder/oracle/types/ticker_price.go delete mode 100644 price-feeder/oracle/types/ticker_price_test.go delete mode 100644 price-feeder/oracle/util.go delete mode 100644 price-feeder/oracle/util_test.go delete mode 100644 price-feeder/pkg/httputil/http.go delete mode 100644 price-feeder/pkg/sync/sync.go delete mode 100644 price-feeder/price-feeder.example.toml delete mode 100644 price-feeder/router/middleware/middleware.go delete mode 100644 price-feeder/router/v1/metrics.go delete mode 100644 price-feeder/router/v1/oracle.go delete mode 100644 price-feeder/router/v1/response.go delete mode 100644 price-feeder/router/v1/router.go delete mode 100644 price-feeder/router/v1/router_test.go delete mode 100644 price-feeder/tests/integration/provider_test.go delete mode 100644 price-feeder/tools/tools.go delete mode 100755 tests/e2e/scripts/price_feeder_bootstrap.sh diff --git a/.dockerignore b/.dockerignore index a485146420..0d03fb464f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,3 @@ build/* -price-feeder/build/* networks networks/* diff --git a/.github/workflows/release-price-feeder-docker.yml b/.github/workflows/release-price-feeder-docker.yml deleted file mode 100644 index fba39413ef..0000000000 --- a/.github/workflows/release-price-feeder-docker.yml +++ /dev/null @@ -1,55 +0,0 @@ -# This workflow helps with creating docker images. -# This job will only be triggered when a tag (vX.X.x) is pushed -name: Price Feeder Docker Release -on: - push: - tags: - - "price-feeder/v*.*.*" - -concurrency: - group: ci-${{ github.ref }}-${{ github.workflow }} - cancel-in-progress: true - -jobs: - price-feeder-docker: - name: price-feeder Docker - runs-on: ubuntu-latest - steps: - - name: checkout - uses: actions/checkout@v3 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - - name: Docker meta - id: meta - uses: docker/metadata-action@v4 - with: - images: ghcr.io/${{ github.repository_owner }}/price-feeder - flavor: | - latest=false - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Print tags and labels - run: | - echo image: ${{ env.REGISTRY }}/${{ github.repository }} - echo tags "${{ steps.meta.outputs.tags }}" - echo labels "${{ steps.meta.outputs.labels }}" - - - name: Login to GHCR - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push - uses: docker/build-push-action@v4 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - file: ./contrib/images/price-feeder.dockerfile diff --git a/.github/workflows/release-price-feeder.yml b/.github/workflows/release-price-feeder.yml deleted file mode 100644 index 13721b4cce..0000000000 --- a/.github/workflows/release-price-feeder.yml +++ /dev/null @@ -1,52 +0,0 @@ -# This workflow helps with creating docker images. -# This job will only be triggered when a tag [price-feeder/(vX.X.x) is pushed -name: Release price-feeder - -on: - push: - tags: - - "price-feeder/v*.*.*" - -concurrency: - group: ci-${{ github.ref }}-${{ github.workflow }} - cancel-in-progress: true - -jobs: - goreleaser: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: actions/setup-go@v4 - with: - go-version: 1.19 - cache: true - cache-dependency-path: price-feeder/go.sum - - # Parse 'v*.*.*' semantic version from 'price-feeder/v*.*.*' and save to - # the $GITHUB_ENV environment variable. - - name: Set env - run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/price-feeder/}" >> $GITHUB_ENV - # Remove the possible pre-existing same tag for umee related tags instead - # of price-feeder tags, since goreleaser enforces semantic versioning and - # will error on non compliant tags. - # - # Ref: https://goreleaser.com/limitations/semver - - name: Tag without prefix locally to avoid error in goreleaser - run: |- - git tag -d ${{ env.RELEASE_VERSION }} || echo "No such a tag exists before" - git tag ${{ env.RELEASE_VERSION }} HEAD - - name: Release - uses: goreleaser/goreleaser-action@v4 - with: - # Note, we have to pin to v0.179.0 due to newer releases enforcing - # correct semantic versioning even when '--skip-validate' is provided. - # - # Ref: https://github.com/goreleaser/goreleaser/pull/2503 - version: v0.179.0 - args: release --rm-dist --skip-validate - workdir: price-feeder - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GORELEASER_CURRENT_TAG: price-feeder/${{ env.RELEASE_VERSION }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bcf505158c..f24dda6327 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -124,28 +124,6 @@ jobs: run: | EXPERIMENTAL=true make test-e2e - price-feeder: - runs-on: ubuntu-latest - needs: install-tparse - steps: - - uses: actions/checkout@v3 - - uses: technote-space/get-diff-action@v6.1.2 - with: - PATTERNS: | - **/**.go - price-feeder/go.mod - price-feeder/go.sum - - uses: actions/setup-go@v4 - if: env.GIT_DIFF - with: - go-version: 1.19 - cache: true - cache-dependency-path: price-feeder/go.sum - - name: Test price-feeder - if: env.GIT_DIFF - run: | - cd price-feeder && make test-unit - liveness-test: needs: install-tparse # needs: build-umeed diff --git a/.mergify.yml b/.mergify.yml index 811b0f6db2..72060675e5 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -18,15 +18,6 @@ pull_request_rules: {{ title }} (#{{ number }}) {{ body }} - - name: Backport patches to the release/price-feeder/v2.x.x branch - conditions: - - base=main - - label=S:backport/price-feeder/v2.x.x - actions: - backport: - branches: - - release/price-feeder/v2.x.x - - name: Backport patches to the release/v4.0.x branch conditions: - base=main diff --git a/Makefile b/Makefile index 0dc22c2049..f553395e55 100644 --- a/Makefile +++ b/Makefile @@ -191,7 +191,7 @@ TEST_PACKAGES=./... TEST_TARGETS := test-unit test-unit-cover test-race TEST_COVERAGE_PROFILE=coverage.txt -UNIT_TEST_TAGS = norace +UNIT_TEST_TAGS = norace TEST_RACE_TAGS = "" TEST_E2E_TAGS = "e2e" TEST_E2E_DEPS = docker-build diff --git a/README.md b/README.md index 35747309c3..9d0fa8d662 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ See [Release procedure](CONTRIBUTING.md#release-procedure) for more information Since `umeed v3.2` there is a new runtime dependency: `libwasmvm.x86_64.so v1.1.1` is required. Building from source will automatically link the `libwasmvm.x86_64.so` created as a part of the build process (you must build on same host as you run the binary, or copy the `libwasmvm.x86_64.so` your lib directory). +After `Price Feeder v2.1.0` the recommended oracle price feeder has been moved to this [repository](https://github.com/ojo-network/price-feeder/tree/umee) with the version prefix `umee/v.x`. + ### Release Compatibility Matrix | Umee Version | Mainnet | Experimental | Cosmos SDK | IBC | Peggo | Price Feeder | Gravity Bridge | @@ -51,7 +53,7 @@ Building from source will automatically link the `libwasmvm.x86_64.so` created a | v3.3.x | ✓ | ✗ | v0.46.6+ | v5.1.x | v1.3.x+ | v2.0.2 | umee/v3 v1.5.3-umee-3 | | v4.0.x | ✓ | ✗ | v0.46.6+ | v5.1.x | v1.3.x+ | v2.0.3 | umee/v4 v1.5.3-umee-4 | | v4.1.x | ✓ | ✗ | v0.46.7+ | v5.2.x | v1.3.x+ | v2.1.0 | umee/v4 v1.5.3-umee-4 | -| v4.2.x | ✓ | ✗ | v0.46.10+ | v5.2.x | v1.3.x+ | v2.1.0 | umee/v4 v1.5.3-umee-4 | +| v4.2.x | ✓ | ✗ | v0.46.10+ | v5.2.x | v1.3.x+ | umee/v2.1.1 | umee/v4 v1.5.3-umee-4 | ### Active Networks diff --git a/contrib/images/price-feeder.dockerfile b/contrib/images/price-feeder.dockerfile deleted file mode 100644 index 172683a1e0..0000000000 --- a/contrib/images/price-feeder.dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -# Fetch base packages -FROM golang:1.19-alpine AS builder -RUN apk add --no-cache make git libc-dev gcc linux-headers build-base - -WORKDIR /src/ -# optimization: if go.sum didn't change, docker will use cached image -COPY go.mod go.sum ./ -RUN go mod download - -COPY . . - -# Cosmwasm - Download correct libwasmvm version -RUN WASMVM_VERSION=$(go list -m github.com/CosmWasm/wasmvm | cut -d ' ' -f 2) && \ - wget https://github.com/CosmWasm/wasmvm/releases/download/$WASMVM_VERSION/libwasmvm_muslc.$(uname -m).a \ - -O /lib/libwasmvm_muslc.a && \ - # verify checksum - wget https://github.com/CosmWasm/wasmvm/releases/download/$WASMVM_VERSION/checksums.txt -O /tmp/checksums.txt && \ - sha256sum /lib/libwasmvm_muslc.a | grep $(cat /tmp/checksums.txt | grep $(uname -m) | cut -d ' ' -f 1) -# Build the binary -RUN cd price-feeder && LEDGER_ENABLED=false BUILD_TAGS=muslc LINK_STATICALLY=true make install - -## Prepare the final clear binary -FROM alpine:3.17 -EXPOSE 7171 -STOPSIGNAL SIGTERM -CMD ["price-feeder"] - -RUN apk add ca-certificates -COPY --from=builder /go/bin/price-feeder /usr/local/bin/ diff --git a/contrib/images/umee.e2e.dockerfile b/contrib/images/umee.e2e.dockerfile index 0d2ba007ee..d16625ad80 100644 --- a/contrib/images/umee.e2e.dockerfile +++ b/contrib/images/umee.e2e.dockerfile @@ -12,19 +12,13 @@ WORKDIR /src/umee COPY go.mod go.sum ./ RUN go mod download -## Download go module dependnecies for price-feeder -WORKDIR /src/umee/price-feeder -COPY price-feeder/go.mod price-feeder/go.sum ./ -RUN go mod download - -## Build umeed and price-feeder +## Build umeed ## optimization: we move setting experimental flag here. ENV EXPERIMENTAL $EXPERIMENTAL WORKDIR /src/umee COPY . . RUN if [ "$EXPERIMENTAL" = "true" ] ; then echo "Installing experimental build";else echo "Installing stable build";fi RUN BUILD_TAGS=badgerdb make install -RUN cd price-feeder && make install ## Prepare the final clear binary FROM ubuntu:rolling diff --git a/price-feeder/.goreleaser.yml b/price-feeder/.goreleaser.yml deleted file mode 100644 index 95aeb8f4e0..0000000000 --- a/price-feeder/.goreleaser.yml +++ /dev/null @@ -1,46 +0,0 @@ -project_name: price-feeder - -env: - - CGO_ENABLED=1 - -before: - hooks: - - go mod download - -builds: - - main: ./ - id: "price-feeder" - binary: price-feeder - mod_timestamp: "{{ .CommitTimestamp }}" - flags: - - -trimpath - ldflags: - - -s -w -X main.commit={{.Commit}} -X main.date={{ .CommitDate }} -X github.com/umee-network/umee/price-feeder/cmd.Version={{ replace .Version "price-feeder/" "price-feeder-" }} -X github.com/umee-network/umee/price-feeder/cmd.Commit={{ .Commit }} - goos: - - linux - goarch: - - amd64 - -archives: - - format: tar.gz - wrap_in_directory: true - format_overrides: - - goos: windows - format: zip - name_template: '{{ replace .Version "price-feeder/" "price-feeder-" }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' - files: - - README.md - - LICENSE - -release: - disable: false - -snapshot: - name_template: SNAPSHOT-{{ .Commit }} - -checksum: - name_template: 'SHA256SUMS-{{ replace .Version "price-feeder/" "price-feeder-" }}.txt' - algorithm: sha256 - -changelog: - skip: false diff --git a/price-feeder/CHANGELOG.md b/price-feeder/CHANGELOG.md deleted file mode 100644 index 8e8f83d4a4..0000000000 --- a/price-feeder/CHANGELOG.md +++ /dev/null @@ -1,271 +0,0 @@ - - - - - -# Changelog - -## Unreleased - -### Improvements -- [1769](https://github.com/umee-network/umee/pull/1769) Small config refactor to make integration test cleaner. -- [1773](https://github.com/umee-network/umee/pull/1773) Websocket controller refactor to create a seperate webscoket connection per pair subrsibed to. -- [1781](https://github.com/umee-network/umee/pull/1781) BTC was removed from example config since WBTC is used by the `Oracle` module instead. -- [1792](https://github.com/umee-network/umee/pull/1792) Replace deprecated sdkerrors with new errors package. - -### Bugs -- [1767](https://github.com/umee-network/umee/pull/1767) Fix getter methods in providers for tickers and candles. -- [1781](https://github.com/umee-network/umee/pull/1781) Currency provider tracker fix to prevent CoinGecko API call failure from stopping the price feeder. -- [1791](https://github.com/umee-network/umee/pull/1791) Fix Gate provider not receiving candles. - -## [v2.0.4](https://github.com/umee-network/umee/releases/tag/price-feeder/v2.0.4) 2023-01-31 - -### Improvements -- [1768](https://github.com/umee-network/umee/pull/1768) New example config with new oracle assets in it, and enable "OSMO" as a quotable asset. - -## [v2.0.3](https://github.com/umee-network/umee/releases/tag/price-feeder/v2.0.3) 2023-01-05 - -### Bugs - -- [1672](https://github.com/umee-network/umee/pull/1672) Fix docker release and proto CI. - -## [v2.0.2](https://github.com/umee-network/umee/releases/tag/price-feeder/v2.0.2) 2022-12-14 - -### Bugs - -- [1660](https://github.com/umee-network/umee/pull/1660) Fix module version - -## [v2.0.1](https://github.com/umee-network/umee/releases/tag/price-feeder/v2.0.1) 2022-12-01 - -### Bugs - -- [1615](https://github.com/umee-network/umee/pull/1615) Parse multiple candles from OsmosisV2 response -- [1635](https://github.com/umee-network/umee/pull/1635) Vote on exchange rates even if one is missing. -- [1634](https://github.com/umee-network/umee/pull/1634) Add minimum candle volume for low-trading assets. - -### Improvements - -- [1602](https://github.com/umee-network/umee/pull/1602) Remove FTX provider. - -## [v2.0.0](https://github.com/umee-network/umee/releases/tag/price-feeder/v2.0.0) 2022-11-15 - -v2.0.0 of the price feeder contains numerous fixes for low-market-cap assets and API changes. It's highly recommended to switch to v2.0.0, especially as it removes the need to use the `ftx` provider for certain assets. - -This was released as a part of [Umee Prop 27.](https://www.mintscan.io/umee/proposals/27) - -### Bugs - -- [1428](https://github.com/umee-network/umee/pull/1428) Update umeed version to an actual tag. - -### Features - -- [1448](https://github.com/umee-network/umee/pull/1448) Add crypto.com provider. -- [1496](https://github.com/umee-network/umee/pull/1496) Dynamic provider minimum enforcement with CoinGecko API. -- [1510](https://github.com/umee-network/umee/pull/1510) Integrate osmosis-api provider into price-feeder. -- [1534](https://github.com/umee-network/umee/pull/1534) Query osmosis-api REST server for available asset pairs supported by it. -- [1554](https://github.com/umee-network/umee/pull/1554) Convert remaining providers to the Websocket Controller. -- [1589](https://github.com/umee-network/umee/pull/1589) Add Binance US provider. - -### Improvements - -- [1484](https://github.com/umee-network/umee/pull/1484) Standardize websocket connection error for providers. -- [1509](https://github.com/umee-network/umee/pull/1509) Update price feeder example config. -- [1527](https://github.com/umee-network/umee/pull/1527) Update convertTickersToUSD and convertCandlesToUSD to public. - -## [v1.0.0](https://github.com/umee-network/umee/releases/tag/price-feeder%2Fv1.0.0) - 2022-09-19 - -### Features - -- [1328](https://github.com/umee-network/umee/pull/1328) Add bitget provider. -- [1339](https://github.com/umee-network/umee/pull/1339) Add mexc provider. -- [1445](https://github.com/umee-network/umee/pull/1445) Add computed prices api endpoints for debugging. - -### Bugs - -- [1338](https://github.com/umee-network/umee/pull/1338) Fix websocket reconnections on remote closures. - -## [v0.3.0](https://github.com/umee-network/umee/releases/tag/price-feeder%2Fv0.3.0) - 2022-08-31 - -### Bugs - -- [1084](https://github.com/umee-network/umee/pull/1084) Initializes block height before subscription to fix an error message that appeared on the first few ticks. -- [1244](https://github.com/umee-network/umee/pull/1244) Add verification for quote in conversion rate. -- [1264](https://github.com/umee-network/umee/pull/1264) Convert osmosis candle timestamp from seconds to milliseconds. -- [1262](https://github.com/umee-network/umee/pull/1262) Add verification for quote in tvwap map. -- [1268](https://github.com/umee-network/umee/pull/1268) Don't panic when a provider has only out-of-date candles. -- [1291](https://github.com/umee-network/umee/pull/1291) Set sdk version during build time. - -### Improvements - -- [#1121](https://github.com/umee-network/umee/pull/1121) Use the cosmos-sdk telemetry package instead of our own. -- [#1032](https://github.com/umee-network/umee/pull/1032) Update the accepted tvwap period from 3 minutes to 5 minutes. -- [#978](https://github.com/umee-network/umee/pull/978) Cleanup the oracle package by moving deviation & conversion logic. -- [#1175](https://github.com/umee-network/umee/pull/1175) Add type ProviderName. -- [#1255](https://github.com/umee-network/umee/pull/1255) Move TickerPrice and CandlePrice to types package -- [#1374](https://github.com/umee-network/umee/pull/1374) Add standard for telemetry metrics. -- [#1431](https://github.com/umee-network/umee/pull/1431) Convert floats to sdk decimal using helper functions in all providers. -- [#1442](https://github.com/umee-network/umee/pull/1442) Remove unnecessary method in reconnection logic. - -### Features - -- [#1038](https://github.com/umee-network/umee/pull/1038) Adds the option for validators to override API endpoints in our config. -- [#1002](https://github.com/umee-network/umee/pull/1002) Add linting to the price feeder CI. -- [#1170](https://github.com/umee-network/umee/pull/1170) Restrict price feeder quotes to USD, USDT, USDC, ETH, DAI, and BTC. -- [#1175](https://github.com/umee-network/umee/pull/1175) Add ProviderName type to facilitate the reading of maps. -- [#1215](https://github.com/umee-network/umee/pull/1215) Moved ProviderName to Name in provider package. -- [#1274](https://github.com/umee-network/umee/pull/1274) Add option to set config by env variables. -- [#1299](https://github.com/umee-network/umee/pull/1299) Add FTX as a provider. - -## [v0.2.5](https://github.com/umee-network/umee/releases/tag/price-feeder%2Fv0.2.5) - 2022-07-28 - -### Bugs - -- [1177](https://github.com/umee-network/umee/pull/1177) Update a deprecated osmosis api endpoint. - -### Improvements - -- [#1179](https://github.com/umee-network/umee/pull/1179) Improve logs when unable to find prices. - -## [v0.2.4](https://github.com/umee-network/umee/releases/tag/price-feeder%2Fv0.2.4) - 2022-07-14 - -### Features - -- [1110](https://github.com/umee-network/umee/pull/1110) Add the ability to detect deviations with multi-quoted prices, ex. using BTC/USD and BTC/ETH at the same time. -- [#998](https://github.com/umee-network/umee/pull/998) Make deviation thresholds configurable for stablecoin support. - -## [v0.2.3](https://github.com/umee-network/umee/releases/tag/price-feeder%2Fv0.2.3) - 2022-06-30 - -### Improvements - -- [#1069](https://github.com/umee-network/umee/pull/1069) Subscribe to node event EventNewBlockHeader to have the current chain height. - -## [v0.2.2](https://github.com/umee-network/umee/releases/tag/price-feeder%2Fv0.2.2) - 2022-06-27 - -### Improvements - -- [#1050](https://github.com/umee-network/umee/pull/1050) Cache x/oracle params to decrease the number of queries to nodes. - -### Features - -- [#925](https://github.com/umee-network/umee/pull/925) Require stablecoins to be converted to USD to protect against depegging. - -## [v0.2.1](https://github.com/umee-network/umee/releases/tag/price-feeder%2Fv0.2.1) - 2022-04-06 - -### Improvements - -- [#766](https://github.com/umee-network/umee/pull/766) Update deps to use umee v2.0.0. - -## [v0.2.0](https://github.com/umee-network/umee/releases/tag/price-feeder%2Fv0.2.0) - 2022-04-04 - -### Features - -- [#730](https://github.com/umee-network/umee/pull/730) Update the mock provider to use a new spreadsheet which uses randomness. - -### Improvements - -- [#684](https://github.com/umee-network/umee/pull/684) Log errors when providers are unable to unmarshal candles and tickers, instead of either one. -- [#732](https://github.com/umee-network/umee/pull/732) Set oracle functions to public to facilitate usage in other repositories. - -### Bugs - -- [#732](https://github.com/umee-network/umee/pull/732) Fixes an issue where filtering out erroneous providers' candles wasn't working. - -## [v0.1.4](https://github.com/umee-network/umee/releases/tag/price-feeder%2Fv0.1.4) - 2022-03-24 - -### Features - -- [#648](https://github.com/umee-network/umee/pull/648) Add Coinbase as a provider. -- [#679](https://github.com/umee-network/umee/pull/679) Add a configurable provider timeout, which defaults to 100ms. - -### Bug Fixes - -- [#675](https://github.com/umee-network/umee/pull/675) Add necessary input validation to SubscribePairs in the price feeder. - -## [v0.1.3](https://github.com/umee-network/umee/releases/tag/price-feeder%2Fv0.1.3) - 2022-03-21 - -### Features - -- [#649](https://github.com/umee-network/umee/pull/649) Add "GetAvailablePairs" function to providers. - -## [v0.1.2](https://github.com/umee-network/umee/releases/tag/price-feeder%2Fv0.1.2) - 2022-03-08 - -### Features - -- [#592](https://github.com/umee-network/umee/pull/592) Add subscribe ticker function to the following providers: Binance, Huobi, Kraken, and Okx. -- [#601](https://github.com/umee-network/umee/pull/601) Use TVWAP formula for determining prices when available. -- [#609](https://github.com/umee-network/umee/pull/609) TVWAP faulty provider detection. - -### Bug Fixes - -- [#607](https://github.com/umee-network/umee/pull/607) Fix kraken provider timestamp unit. - -### Refactor - -- [#610](https://github.com/umee-network/umee/pull/610) Split subscription of ticker and candle channels. - -## [v0.1.1](https://github.com/umee-network/umee/releases/tag/price-feeder%2Fv0.1.1) - 2022-03-01 - -### Features - -- [#502](https://github.com/umee-network/umee/pull/502) Faulty provider detection: discard prices that are not within 2𝜎 of others. -- [#536](https://github.com/umee-network/umee/pull/536) Force a minimum of three providers per asset. -- [#522](https://github.com/umee-network/umee/pull/522) Add Okx as a provider. -- [#551](https://github.com/umee-network/umee/pull/551) Update Binance provider to use WebSocket. -- [#569](https://github.com/umee-network/umee/pull/569) Update Huobi provider to use WebSocket. -- [#540](https://github.com/umee-network/umee/pull/536) Use environment vars / standard input for the keyring password instead of the config file. -- [#580](https://github.com/umee-network/umee/pull/580) Update Kraken provider to use WebSocket. - -### Bug Fixes - -- [#552](https://github.com/umee-network/umee/pull/552) Stop requiring telemetry during config validation. -- [#573](https://github.com/umee-network/umee/pull/573) Strengthen CORS settings. -- [#574](https://github.com/umee-network/umee/pull/574) Stop registering metrics endpoint if telemetry is disabled. - -### Refactor - -- [#587](https://github.com/umee-network/umee/pull/587) Clean up logs from price feeder providers. - -## [v0.1.0](https://github.com/umee-network/umee/releases/tag/price-feeder%2Fv0.1.0) - 2022-02-07 - -### Features - -- Initial release!!! diff --git a/price-feeder/LICENSE b/price-feeder/LICENSE deleted file mode 100644 index 692d4330f7..0000000000 --- a/price-feeder/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright Umee Technologies LLC - - 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 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/price-feeder/Makefile b/price-feeder/Makefile deleted file mode 100644 index 941ee20d07..0000000000 --- a/price-feeder/Makefile +++ /dev/null @@ -1,62 +0,0 @@ -BRANCH := $(shell git rev-parse --abbrev-ref HEAD) -BUILD_DIR ?= $(CURDIR)/build -COMMIT := $(shell git log -1 --format='%H') -SDK_VERSION := $(shell go list -m github.com/cosmos/cosmos-sdk | sed 's:.* ::') - -all: test-unit install - -.PHONY: all - -############################################################################### -## Version ## -############################################################################### - -ifeq (,$(VERSION)) - VERSION := $(shell git describe --exact-match 2>/dev/null) - # if VERSION is empty, then populate it with branch's name and raw commit hash - ifeq (,$(VERSION)) - VERSION := $(BRANCH)-$(COMMIT) - endif -endif - -############################################################################### -## Build / Install ## -############################################################################### - -ldflags = -X github.com/umee-network/umee/price-feeder/cmd.Version=$(VERSION) \ - -X github.com/umee-network/umee/price-feeder/cmd.Commit=$(COMMIT) \ - -X github.com/umee-network/umee/price-feeder/cmd.SDKVersion=$(SDK_VERSION) - -ifeq ($(LINK_STATICALLY),true) - ldflags += -linkmode=external -extldflags "-Wl,-z,muldefs -static" -endif - -build_tags += $(BUILD_TAGS) - -BUILD_FLAGS := -tags "$(build_tags)" -ldflags '$(ldflags)' - -build: go.sum - @echo "--> Building..." - go build -mod=readonly -o $(BUILD_DIR)/ $(BUILD_FLAGS) ./... - -install: go.sum - @echo "--> Installing..." - go install -mod=readonly $(BUILD_FLAGS) ./... - -.PHONY: build install - -############################################################################### -## Tests & Linting ## -############################################################################### - -test-unit: - @echo "--> Running tests" - @go test -short -mod=readonly -race ./... -v - -.PHONY: test-unit - -lint: - @echo "--> Running linter" - @go run github.com/golangci/golangci-lint/cmd/golangci-lint run --fix --timeout=8m - -.PHONY: lint diff --git a/price-feeder/README.md b/price-feeder/README.md index 22f3575d5b..7ccfb4d599 100644 --- a/price-feeder/README.md +++ b/price-feeder/README.md @@ -1,154 +1,3 @@ # Oracle Price Feeder -The `price-feeder` tool is an extension of Umee's `x/oracle` module, both of -which are based on Terra's [x/oracle](https://github.com/terra-money/classic-core/tree/main/x/oracle) -module and [oracle-feeder](https://github.com/terra-money/oracle-feeder). The -core differences are as follows: - -- All exchange rates must be quoted in USD or USD stablecoins. -- No need or use of reference exchange rates (e.g. Luna). -- No need or use of Tobin tax. -- The `price-feeder` combines both `feeder` and `price-server` into a single - Golang-based application for better UX, testability, and integration. - -## Background - -The `price-feeder` tool is responsible for performing the following: - -1. Fetching and aggregating exchange rate price data from various providers, e.g. - Binance and Osmosis, based on operator configuration. These exchange rates - are exposed via an API and are used to feed into the main oracle process. -2. Taking aggregated exchange rate price data and submitting those exchange rates - on-chain to Umee's `x/oracle` module following Umee's [Oracle](https://github.com/umee-network/umee/tree/main/x/oracle#readme) - specification. - - - -## Providers - -The list of current supported providers: - -- [Binance](https://www.binance.com/en) -- [Bitget](https://www.bitget.com/) -- [Coinbase](https://www.coinbase.com/) -- [Crypto](https://crypto.com/) -- [Gate](https://www.gate.io/) -- [Huobi](https://www.huobi.com/en-us/) -- [Kraken](https://www.kraken.com/en-us/) -- [Mexc](https://www.mexc.com/) -- [Okx](https://www.okx.com/) -- [Osmosis](https://app.osmosis.zone/) -- [OsmosisV2](https://github.com/umee-network/osmosis-api) - - -## Usage - -The `price-feeder` tool runs off of a single configuration file. This configuration -file defines what exchange rates to fetch and what providers to get them from. -In addition, it defines the oracle's keyring and feeder account information. -The keyring's password is defined via environment variables or user input. -More information on the keyring can be found [here](#keyring) -Please see the [example configuration](price-feeder.example.toml) for more details. - -```shell -$ price-feeder /path/to/price_feeder_config.toml -``` - -Umee rules for checking the free oracle transactions are: - -- must be only prevote or vote -- gas is limited to [`MaxMsgGasUsage`](https://github.com/umee-network/umee/blob/main/ante/fee.go#L15) constant. - -So, if you don't want to pay for gas, TX must be below `MaxMsgGasUsage`. If you set too much gas (which is what is happening when when you set `gas_adjustment` to 2), then the tx will allocate 2x gas, and hence will go above the free quota, so you would need to attach fee to pay for that gas. -The easiest is to just set constant gas. We recommend 10k below the `MaxMsgGasUsage`. - -## Configuration - -### `telemetry` - -A set of options for the application's telemetry, which is disabled by default. An in-memory sink is the default, but Prometheus is also supported. We use the [cosmos sdk telemetry package](https://github.com/cosmos/cosmos-sdk/blob/3689d6f41ad8afa6e0f9b4ecb03b4d7f2d3a9e94/docs/docs/core/09-telemetry.md). - -### `deviation` - -Deviation allows validators to set a custom amount of standard deviations around the median which is helpful if any providers become faulty. It should be noted that the default for this option is 1 standard deviation. - -### `provider_endpoints` - -The provider_endpoints option enables validators to setup their own API endpoints for a given provider. - -### `server` - -The `server` section contains configuration pertaining to the API served by the -`price-feeder` process such the listening address and various HTTP timeouts. - -### `currency_pairs` - -The `currency_pairs` sections contains one or more exchange rates along with the -providers from which to get market data from. It is important to note that the -providers supplied in each `currency_pairs` must support the given exchange rate. - -For example, to get multiple price points on ATOM, you could define `currency_pairs` -as follows: - -```toml -[[currency_pairs]] -base = "ATOM" -providers = [ - "binance", -] -quote = "USDT" - -[[currency_pairs]] -base = "ATOM" -providers = [ - "kraken", - "osmosis", -] -quote = "USD" -``` - -Providing multiple providers is beneficial in case any provider fails to return -market data. Prices per exchange rate are submitted on-chain via pre-vote and -vote messages using a time-weighted average price (TVWAP). - -### `provider_min_override` - -At startup the amount of possible providers for a currency is checked by querying the -CoinGecko API to enforce an acceptable minimum providers for a given currency pair. If -this request fails and `provider_min_override` is set to true, the minimum is not enforced -and the `price-feeder` is allowed to run irrespective of how many providers are provided -for a given currency pair. `provider_min_override` will not take effect if CoinGecko -requests are successful. - -### `account` - -The `account` section contains the oracle's feeder and validator account information. -These are used to sign and populate data in pre-vote and vote oracle messages. - -### `keyring` - -The `keyring` section contains Keyring related material used to fetch the key pair -associated with the oracle account that signs pre-vote and vote oracle messages. - -### `rpc` - -The `rpc` section contains the Tendermint and Cosmos application gRPC endpoints. -These endpoints are used to query for on-chain data that pertain to oracle -functionality and for broadcasting signed pre-vote and vote oracle messages. - -## Keyring - -Our keyring must be set up to sign transactions before running the price feeder. -Additional info on the different keyring modes is available [here](https://docs.cosmos.network/v0.46/run-node/keyring.html). -**Please note that the `test` and `memory` modes are only for testing purposes.** -**Do not use these modes for running the price feeder against mainnet.** - -### Setup - -The keyring `dir` and `backend` are defined in the config file. -You may use the `PRICE_FEEDER_PASS` environment variable to set up the keyring password. - -Ex : -`export PRICE_FEEDER_PASS=keyringPassword` - -If this environment variable is not set, the price feeder will prompt the user for input. +After Umee Version v4.2.x, the Umee `price-feeder` built in the Umee project is now deprecated. The recommended `price-feeder` to run along the Umee node should be the [umee branch](https://github.com/ojo-network/price-feeder/tree/umee) of the Ojo Network's `price-feeder`. diff --git a/price-feeder/cmd/price-feeder.go b/price-feeder/cmd/price-feeder.go deleted file mode 100644 index af060ecdd3..0000000000 --- a/price-feeder/cmd/price-feeder.go +++ /dev/null @@ -1,298 +0,0 @@ -package cmd - -import ( - "context" - "fmt" - "io" - "net/http" - "os" - "os/signal" - "strings" - "syscall" - "time" - - "github.com/mitchellh/mapstructure" - - "github.com/gorilla/mux" - "github.com/rs/zerolog" - "github.com/spf13/cobra" - "golang.org/x/sync/errgroup" - "golang.org/x/term" - - "github.com/cosmos/cosmos-sdk/telemetry" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/umee-network/umee/price-feeder/v2/config" - "github.com/umee-network/umee/price-feeder/v2/oracle" - "github.com/umee-network/umee/price-feeder/v2/oracle/client" - v1 "github.com/umee-network/umee/price-feeder/v2/router/v1" -) - -const ( - logLevelJSON = "json" - logLevelText = "text" - - flagLogLevel = "log-level" - flagLogFormat = "log-format" - - envVariablePass = "PRICE_FEEDER_PASS" -) - -var rootCmd = &cobra.Command{ - Use: "price-feeder [config-file]", - Args: cobra.ExactArgs(1), - Short: "price-feeder is a side-car process for providing Umee's on-chain oracle with price data", - Long: `A side-car process that Umee validators must run in order to provide -Umee's on-chain price oracle with price information. The price-feeder performs -two primary functions. First, it is responsible for obtaining price information -from various reliable data sources, e.g. exchanges, and exposing this data via -an API. Secondly, the price-feeder consumes this data and periodically submits -vote and prevote messages following the oracle voting procedure.`, - RunE: priceFeederCmdHandler, -} - -func init() { - rootCmd.PersistentFlags().String(flagLogLevel, zerolog.InfoLevel.String(), "logging level") - rootCmd.PersistentFlags().String(flagLogFormat, logLevelText, "logging format; must be either json or text") - - rootCmd.AddCommand(getVersionCmd()) -} - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - if err := rootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } -} - -func priceFeederCmdHandler(cmd *cobra.Command, args []string) error { - logLvlStr, err := cmd.Flags().GetString(flagLogLevel) - if err != nil { - return err - } - - logLvl, err := zerolog.ParseLevel(logLvlStr) - if err != nil { - return err - } - - logFormatStr, err := cmd.Flags().GetString(flagLogFormat) - if err != nil { - return err - } - - var logWriter io.Writer - switch strings.ToLower(logFormatStr) { - case logLevelJSON: - logWriter = os.Stderr - - case logLevelText: - logWriter = zerolog.ConsoleWriter{Out: os.Stderr} - - default: - return fmt.Errorf("invalid logging format: %s", logFormatStr) - } - - logger := zerolog.New(logWriter).Level(logLvl).With().Timestamp().Logger() - - cfg, err := config.ParseConfig(args[0]) - if err != nil { - return err - } - - err = config.CheckProviderMins(cmd.Context(), logger, cfg) - if err != nil { - return err - } - - ctx, cancel := context.WithCancel(cmd.Context()) - g, ctx := errgroup.WithContext(ctx) - - // listen for and trap any OS signal to gracefully shutdown and exit - trapSignal(cancel, logger) - - rpcTimeout, err := time.ParseDuration(cfg.RPC.RPCTimeout) - if err != nil { - return fmt.Errorf("failed to parse RPC timeout: %w", err) - } - - // Gather pass via env variable || std input - keyringPass, err := getKeyringPassword(cfg.Keyring.Backend) - if err != nil { - return err - } - - oracleClient, err := client.NewOracleClient( - ctx, - logger, - cfg.Account.ChainID, - cfg.Keyring.Backend, - cfg.Keyring.Dir, - keyringPass, - cfg.RPC.TMRPCEndpoint, - rpcTimeout, - cfg.Account.Address, - cfg.Account.Validator, - cfg.RPC.GRPCEndpoint, - cfg.GasAdjustment, - ) - if err != nil { - return err - } - - providerTimeout, err := time.ParseDuration(cfg.ProviderTimeout) - if err != nil { - return fmt.Errorf("failed to parse provider timeout: %w", err) - } - - deviations := make(map[string]sdk.Dec, len(cfg.Deviations)) - for _, deviation := range cfg.Deviations { - threshold, err := sdk.NewDecFromStr(deviation.Threshold) - if err != nil { - return err - } - deviations[deviation.Base] = threshold - } - - oracle := oracle.New( - logger, - oracleClient, - cfg.ProviderPairs(), - providerTimeout, - deviations, - cfg.ProviderEndpointsMap(), - ) - - telemetryCfg := telemetry.Config{} - err = mapstructure.Decode(cfg.Telemetry, &telemetryCfg) - if err != nil { - return err - } - metrics, err := telemetry.New(telemetryCfg) - if err != nil { - return err - } - - g.Go(func() error { - // start the process that observes and publishes exchange prices - return startPriceFeeder(ctx, logger, cfg, oracle, metrics) - }) - g.Go(func() error { - // start the process that calculates oracle prices and votes - return startPriceOracle(ctx, logger, oracle) - }) - - // Block main process until all spawned goroutines have gracefully exited and - // signal has been captured in the main process or if an error occurs. - return g.Wait() -} - -func getKeyringPassword(keyringBackend string) (string, error) { - if keyringBackend == "test" { - return "", nil - } - - pass := os.Getenv(envVariablePass) - if pass == "" { - fmt.Print("Enter keyring password: ") - bytePassword, err := term.ReadPassword(syscall.Stdin) - if err != nil { - return "", err - } - pass = string(bytePassword) - } - return pass, nil -} - -// trapSignal will listen for any OS signal and invoke Done on the main -// WaitGroup allowing the main process to gracefully exit. -func trapSignal(cancel context.CancelFunc, logger zerolog.Logger) { - sigCh := make(chan os.Signal, 1) - - signal.Notify(sigCh, syscall.SIGTERM) - signal.Notify(sigCh, syscall.SIGINT) - - go func() { - sig := <-sigCh - logger.Info().Str("signal", sig.String()).Msg("caught signal; shutting down...") - cancel() - }() -} - -func startPriceFeeder( - ctx context.Context, - logger zerolog.Logger, - cfg config.Config, - oracle *oracle.Oracle, - metrics *telemetry.Metrics, -) error { - rtr := mux.NewRouter() - v1Router := v1.New(logger, cfg, oracle, metrics) - v1Router.RegisterRoutes(rtr, v1.APIPathPrefix) - - writeTimeout, err := time.ParseDuration(cfg.Server.WriteTimeout) - if err != nil { - return err - } - readTimeout, err := time.ParseDuration(cfg.Server.ReadTimeout) - if err != nil { - return err - } - - srvErrCh := make(chan error, 1) - srv := &http.Server{ - Handler: rtr, - Addr: cfg.Server.ListenAddr, - WriteTimeout: writeTimeout, - ReadTimeout: readTimeout, - ReadHeaderTimeout: readTimeout, - } - - go func() { - logger.Info().Str("listen_addr", cfg.Server.ListenAddr).Msg("starting price-feeder server...") - srvErrCh <- srv.ListenAndServe() - }() - - for { - select { - case <-ctx.Done(): - shutdownCtx, cancel := context.WithTimeout(ctx, 15*time.Second) - defer cancel() - - logger.Info().Str("listen_addr", cfg.Server.ListenAddr).Msg("shutting down price-feeder server...") - if err := srv.Shutdown(shutdownCtx); err != nil { - logger.Error().Err(err).Msg("failed to gracefully shutdown price-feeder server") - return err - } - - return nil - - case err := <-srvErrCh: - logger.Error().Err(err).Msg("failed to start price-feeder server") - return err - } - } -} - -func startPriceOracle(ctx context.Context, logger zerolog.Logger, oracle *oracle.Oracle) error { - srvErrCh := make(chan error, 1) - - go func() { - logger.Info().Msg("starting price-feeder oracle...") - srvErrCh <- oracle.Start(ctx) - }() - - for { - select { - case <-ctx.Done(): - logger.Info().Msg("shutting down price-feeder oracle...") - return nil - - case err := <-srvErrCh: - logger.Err(err).Msg("error starting the price-feeder oracle") - oracle.Stop() - return err - } - } -} diff --git a/price-feeder/cmd/version.go b/price-feeder/cmd/version.go deleted file mode 100644 index debe4fef77..0000000000 --- a/price-feeder/cmd/version.go +++ /dev/null @@ -1,70 +0,0 @@ -package cmd - -import ( - "encoding/json" - "fmt" - "runtime" - - "github.com/spf13/cobra" - "gopkg.in/yaml.v3" -) - -const ( - flagFormat = "format" -) - -var ( - // Version defines the application version (defined at compile time) - Version = "" - - // Commit defines the application commit hash (defined at compile time) - Commit = "" - - // SDKVersion defines the sdk version (defined at compile time) - SDKVersion = "" - - versionFormat string -) - -type versionInfo struct { - Version string `json:"version" yaml:"version"` - Commit string `json:"commit" yaml:"commit"` - SDK string `json:"sdk" yaml:"sdk"` - Go string `json:"go" yaml:"go"` -} - -func getVersionCmd() *cobra.Command { - versionCmd := &cobra.Command{ - Use: "version", - Short: "Print binary version information", - RunE: func(cmd *cobra.Command, args []string) error { - verInfo := versionInfo{ - Version: Version, - Commit: Commit, - SDK: SDKVersion, - Go: fmt.Sprintf("%s %s/%s", runtime.Version(), runtime.GOOS, runtime.GOARCH), - } - - var bz []byte - - var err error - switch versionFormat { - case "json": - bz, err = json.Marshal(verInfo) - - default: - bz, err = yaml.Marshal(&verInfo) - } - if err != nil { - return err - } - - _, err = fmt.Println(string(bz)) - return err - }, - } - - versionCmd.Flags().StringVar(&versionFormat, flagFormat, "text", "Print the version in the given format (text|json)") - - return versionCmd -} diff --git a/price-feeder/config/config.go b/price-feeder/config/config.go deleted file mode 100644 index 6b07d21820..0000000000 --- a/price-feeder/config/config.go +++ /dev/null @@ -1,303 +0,0 @@ -package config - -import ( - "context" - "errors" - "fmt" - "strings" - "time" - - "github.com/cosmos/cosmos-sdk/telemetry" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/go-playground/validator/v10" - "github.com/rs/zerolog" - "github.com/spf13/viper" - "github.com/umee-network/umee/price-feeder/v2/oracle/provider" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -const ( - DenomUSD = "USD" - - defaultListenAddr = "0.0.0.0:7171" - defaultSrvWriteTimeout = 15 * time.Second - defaultSrvReadTimeout = 15 * time.Second - defaultProviderTimeout = 100 * time.Millisecond -) - -var ( - validate = validator.New() - - // ErrEmptyConfigPath defines a sentinel error for an empty config path. - ErrEmptyConfigPath = errors.New("empty configuration file path") - - // SupportedProviders defines a lookup table of all the supported currency API - // providers. - SupportedProviders = map[provider.Name]struct{}{ - provider.ProviderKraken: {}, - provider.ProviderBinance: {}, - provider.ProviderBinanceUS: {}, - provider.ProviderOsmosis: {}, - provider.ProviderOsmosisV2: {}, - provider.ProviderOkx: {}, - provider.ProviderHuobi: {}, - provider.ProviderGate: {}, - provider.ProviderCoinbase: {}, - provider.ProviderBitget: {}, - provider.ProviderMexc: {}, - provider.ProviderCrypto: {}, - provider.ProviderMock: {}, - } - - // maxDeviationThreshold is the maximum allowed amount of standard - // deviations which validators are able to set for a given asset. - maxDeviationThreshold = sdk.MustNewDecFromStr("3.0") - - // SupportedQuotes defines a lookup table for which assets we support - // using as quotes. - SupportedQuotes = map[string]struct{}{ - DenomUSD: {}, - "USDC": {}, - "USDT": {}, - "DAI": {}, - "BTC": {}, - "ETH": {}, - "ATOM": {}, - "OSMO": {}, - } -) - -type ( - // Config defines all necessary price-feeder configuration parameters. - Config struct { - Server Server `mapstructure:"server"` - CurrencyPairs []CurrencyPair `mapstructure:"currency_pairs" validate:"required,gt=0,dive,required"` - Deviations []Deviation `mapstructure:"deviation_thresholds"` - Account Account `mapstructure:"account" validate:"required,gt=0,dive,required"` - Keyring Keyring `mapstructure:"keyring" validate:"required,gt=0,dive,required"` - RPC RPC `mapstructure:"rpc" validate:"required,gt=0,dive,required"` - Telemetry telemetry.Config `mapstructure:"telemetry"` - GasAdjustment float64 `mapstructure:"gas_adjustment" validate:"required"` - ProviderTimeout string `mapstructure:"provider_timeout"` - ProviderMinOverride bool `mapstructure:"provider_min_override"` - ProviderEndpoints []provider.Endpoint `mapstructure:"provider_endpoints" validate:"dive"` - } - - // Server defines the API server configuration. - Server struct { - ListenAddr string `mapstructure:"listen_addr"` - WriteTimeout string `mapstructure:"write_timeout"` - ReadTimeout string `mapstructure:"read_timeout"` - VerboseCORS bool `mapstructure:"verbose_cors"` - AllowedOrigins []string `mapstructure:"allowed_origins"` - } - - // CurrencyPair defines a price quote of the exchange rate for two different - // currencies and the supported providers for getting the exchange rate. - CurrencyPair struct { - Base string `mapstructure:"base" validate:"required"` - Quote string `mapstructure:"quote" validate:"required"` - Providers []provider.Name `mapstructure:"providers" validate:"required,gt=0,dive,required"` - } - - // Deviation defines a maximum amount of standard deviations that a given asset can - // be from the median without being filtered out before voting. - Deviation struct { - Base string `mapstructure:"base" validate:"required"` - Threshold string `mapstructure:"threshold" validate:"required"` - } - - // Account defines account related configuration that is related to the Umee - // network and transaction signing functionality. - Account struct { - ChainID string `mapstructure:"chain_id" validate:"required"` - Address string `mapstructure:"address" validate:"required"` - Validator string `mapstructure:"validator" validate:"required"` - } - - // Keyring defines the required Umee keyring configuration. - Keyring struct { - Backend string `mapstructure:"backend" validate:"required"` - Dir string `mapstructure:"dir" validate:"required"` - } - - // RPC defines RPC configuration of both the Umee gRPC and Tendermint nodes. - RPC struct { - TMRPCEndpoint string `mapstructure:"tmrpc_endpoint" validate:"required"` - GRPCEndpoint string `mapstructure:"grpc_endpoint" validate:"required"` - RPCTimeout string `mapstructure:"rpc_timeout" validate:"required"` - } -) - -// telemetryValidation is custom validation for the Telemetry struct. -func telemetryValidation(sl validator.StructLevel) { - tel := sl.Current().Interface().(telemetry.Config) - - if tel.Enabled && (len(tel.GlobalLabels) == 0 || len(tel.ServiceName) == 0) { - sl.ReportError(tel.Enabled, "enabled", "Enabled", "enabledNoOptions", "") - } -} - -// endpointValidation is custom validation for the ProviderEndpoint struct. -func endpointValidation(sl validator.StructLevel) { - endpoint := sl.Current().Interface().(provider.Endpoint) - - if len(endpoint.Name) < 1 || len(endpoint.Rest) < 1 || len(endpoint.Websocket) < 1 { - sl.ReportError(endpoint, "endpoint", "Endpoint", "unsupportedEndpointType", "") - } - if _, ok := SupportedProviders[endpoint.Name]; !ok { - sl.ReportError(endpoint.Name, "name", "Name", "unsupportedEndpointProvider", "") - } -} - -// Validate returns an error if the Config object is invalid. -func (c Config) Validate() error { - validate.RegisterStructValidation(telemetryValidation, telemetry.Config{}) - validate.RegisterStructValidation(endpointValidation, provider.Endpoint{}) - return validate.Struct(c) -} - -func (c Config) ProviderPairs() map[provider.Name][]types.CurrencyPair { - providerPairs := make(map[provider.Name][]types.CurrencyPair) - - for _, pair := range c.CurrencyPairs { - for _, provider := range pair.Providers { - providerPairs[provider] = append(providerPairs[provider], types.CurrencyPair{ - Base: pair.Base, - Quote: pair.Quote, - }) - } - } - return providerPairs -} - -// ProviderEndpointsMap converts the provider_endpoints from the config -// file into a map of provider.Endpoint where the key is the provider name -func (c Config) ProviderEndpointsMap() map[provider.Name]provider.Endpoint { - endpoints := make(map[provider.Name]provider.Endpoint, len(c.ProviderEndpoints)) - for _, endpoint := range c.ProviderEndpoints { - endpoints[endpoint.Name] = endpoint - } - return endpoints -} - -// ParseConfig attempts to read and parse configuration from the given file path. -// An error is returned if reading or parsing the config fails. -func ParseConfig(configPath string) (Config, error) { - var cfg Config - - if configPath == "" { - return cfg, ErrEmptyConfigPath - } - - viper.AutomaticEnv() - viper.SetConfigFile(configPath) - - if err := viper.ReadInConfig(); err != nil { - return cfg, fmt.Errorf("failed to read config: %w", err) - } - - if err := viper.Unmarshal(&cfg); err != nil { - return cfg, fmt.Errorf("failed to decode config: %w", err) - } - - if cfg.Server.ListenAddr == "" { - cfg.Server.ListenAddr = defaultListenAddr - } - if len(cfg.Server.WriteTimeout) == 0 { - cfg.Server.WriteTimeout = defaultSrvWriteTimeout.String() - } - if len(cfg.Server.ReadTimeout) == 0 { - cfg.Server.ReadTimeout = defaultSrvReadTimeout.String() - } - if len(cfg.ProviderTimeout) == 0 { - cfg.ProviderTimeout = defaultProviderTimeout.String() - } - - pairs := make(map[string]map[provider.Name]struct{}) - coinQuotes := make(map[string]struct{}) - for _, cp := range cfg.CurrencyPairs { - if _, ok := pairs[cp.Base]; !ok { - pairs[cp.Base] = make(map[provider.Name]struct{}) - } - if strings.ToUpper(cp.Quote) != DenomUSD { - coinQuotes[cp.Quote] = struct{}{} - } - if _, ok := SupportedQuotes[strings.ToUpper(cp.Quote)]; !ok { - return cfg, fmt.Errorf("unsupported quote: %s", cp.Quote) - } - - for _, provider := range cp.Providers { - if _, ok := SupportedProviders[provider]; !ok { - return cfg, fmt.Errorf("unsupported provider: %s", provider) - } - pairs[cp.Base][provider] = struct{}{} - } - } - - // Use coinQuotes to ensure that any quotes can be converted to USD. - for quote := range coinQuotes { - for index, pair := range cfg.CurrencyPairs { - if pair.Base == quote && pair.Quote == DenomUSD { - break - } - if index == len(cfg.CurrencyPairs)-1 { - return cfg, fmt.Errorf("all non-usd quotes require a conversion rate feed") - } - } - } - - for _, deviation := range cfg.Deviations { - threshold, err := sdk.NewDecFromStr(deviation.Threshold) - if err != nil { - return cfg, fmt.Errorf("deviation thresholds must be numeric: %w", err) - } - - if threshold.GT(maxDeviationThreshold) { - return cfg, fmt.Errorf("deviation thresholds must not exceed 3.0") - } - } - - return cfg, cfg.Validate() -} - -// CheckProviderMins starts the currency provider tracker to check the amount of -// providers available for a currency by querying CoinGecko's API. It will enforce -// a provider minimum for a given currency based on its available providers. -func CheckProviderMins(ctx context.Context, logger zerolog.Logger, cfg Config) error { - currencyProviderTracker, err := NewCurrencyProviderTracker(ctx, logger, cfg.CurrencyPairs...) - if err != nil { - logger.Error().Err(err).Msg("failed to start currency provider tracker") - // If currency tracker errors out and override flag is set, the price-feeder - // will run without enforcing provider minimums. - if cfg.ProviderMinOverride { - return nil - } - } - - pairs := make(map[string]map[provider.Name]struct{}) - for _, cp := range cfg.CurrencyPairs { - if _, ok := pairs[cp.Base]; !ok { - pairs[cp.Base] = make(map[provider.Name]struct{}) - } - for _, provider := range cp.Providers { - pairs[cp.Base][provider] = struct{}{} - } - } - - for base, providers := range pairs { - // If currency provider tracker errored, default to three providers as - // the minimum. - var minProviders int - if currencyProviderTracker != nil { - minProviders = currencyProviderTracker.CurrencyProviderMin[base] - } else { - minProviders = 3 - } - if _, ok := pairs[base][provider.ProviderMock]; !ok && len(providers) < minProviders { - return fmt.Errorf("must have at least %d providers for %s", minProviders, base) - } - } - - return nil -} diff --git a/price-feeder/config/config_test.go b/price-feeder/config/config_test.go deleted file mode 100644 index 776193cb54..0000000000 --- a/price-feeder/config/config_test.go +++ /dev/null @@ -1,761 +0,0 @@ -package config_test - -import ( - "context" - "io/ioutil" - "os" - "testing" - - "github.com/cosmos/cosmos-sdk/telemetry" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" - "github.com/umee-network/umee/price-feeder/v2/config" - "github.com/umee-network/umee/price-feeder/v2/oracle/provider" - "gotest.tools/v3/assert" -) - -func TestValidate(t *testing.T) { - validConfig := func() config.Config { - return config.Config{ - Server: config.Server{ - ListenAddr: "0.0.0.0:7171", - VerboseCORS: false, - AllowedOrigins: []string{}, - }, - CurrencyPairs: []config.CurrencyPair{ - {Base: "ATOM", Quote: "USDT", Providers: []provider.Name{provider.ProviderKraken}}, - }, - Account: config.Account{ - Address: "fromaddr", - Validator: "valaddr", - ChainID: "chain-id", - }, - Keyring: config.Keyring{ - Backend: "test", - Dir: "/Users/username/.umee", - }, - RPC: config.RPC{ - TMRPCEndpoint: "http://localhost:26657", - GRPCEndpoint: "localhost:9090", - RPCTimeout: "100ms", - }, - Telemetry: telemetry.Config{ - ServiceName: "price-feeder", - Enabled: true, - EnableHostname: true, - EnableHostnameLabel: true, - EnableServiceLabel: true, - GlobalLabels: make([][]string, 1), - PrometheusRetentionTime: 120, - }, - GasAdjustment: 1.5, - } - } - emptyPairs := validConfig() - emptyPairs.CurrencyPairs = []config.CurrencyPair{} - - invalidBase := validConfig() - invalidBase.CurrencyPairs = []config.CurrencyPair{ - {Base: "", Quote: "USDT", Providers: []provider.Name{provider.ProviderKraken}}, - } - - invalidQuote := validConfig() - invalidQuote.CurrencyPairs = []config.CurrencyPair{ - {Base: "ATOM", Quote: "", Providers: []provider.Name{provider.ProviderKraken}}, - } - - emptyProviders := validConfig() - emptyProviders.CurrencyPairs = []config.CurrencyPair{ - {Base: "ATOM", Quote: "USDT", Providers: []provider.Name{}}, - } - - invalidEndpoints := validConfig() - invalidEndpoints.ProviderEndpoints = []provider.Endpoint{ - { - Name: provider.ProviderBinance, - }, - } - - invalidEndpointsProvider := validConfig() - invalidEndpointsProvider.ProviderEndpoints = []provider.Endpoint{ - { - Name: "foo", - Rest: "bar", - Websocket: "baz", - }, - } - - testCases := []struct { - name string - cfg config.Config - expectErr bool - }{ - { - "valid config", - validConfig(), - false, - }, - { - "empty pairs", - emptyPairs, - true, - }, - { - "invalid base", - invalidBase, - true, - }, - { - "invalid quote", - invalidQuote, - true, - }, - { - "empty providers", - emptyProviders, - true, - }, - { - "invalid endpoints", - invalidEndpoints, - true, - }, - { - "invalid endpoint provider", - invalidEndpointsProvider, - true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.cfg.Validate() != nil, tc.expectErr) - }) - } -} - -func TestParseConfig_Valid(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "price-feeder*.toml") - require.NoError(t, err) - defer os.Remove(tmpFile.Name()) - - content := []byte(` -gas_adjustment = 1.5 - -[server] -listen_addr = "0.0.0.0:99999" -read_timeout = "20s" -verbose_cors = true -write_timeout = "20s" - -[[currency_pairs]] -base = "ATOM" -quote = "USDT" -providers = [ - "kraken", - "binance", - "huobi" -] - -[[currency_pairs]] -base = "UMEE" -quote = "USDT" -providers = [ - "kraken", - "binance", - "huobi" -] - -[[currency_pairs]] -base = "USDT" -quote = "USD" -providers = [ - "kraken", - "binance", - "huobi" -] - -[account] -address = "umee15nejfgcaanqpw25ru4arvfd0fwy6j8clccvwx4" -validator = "umeevalcons14rjlkfzp56733j5l5nfk6fphjxymgf8mj04d5p" -chain_id = "umee-local-testnet" - -[keyring] -backend = "test" -dir = "/Users/username/.umee" -pass = "keyringPassword" - -[rpc] -tmrpc_endpoint = "http://localhost:26657" -grpc_endpoint = "localhost:9090" -rpc_timeout = "100ms" - -[telemetry] -service-name = "price-feeder" -enabled = true -enable-hostname = true -enable-hostname-label = true -enable-service-label = true -prometheus-retention = 120 -global-labels = [["chain-id", "umee-local-testnet"]] -`) - _, err = tmpFile.Write(content) - require.NoError(t, err) - - cfg, err := config.ParseConfig(tmpFile.Name()) - require.NoError(t, err) - - require.Equal(t, "0.0.0.0:99999", cfg.Server.ListenAddr) - require.Equal(t, "20s", cfg.Server.WriteTimeout) - require.Equal(t, "20s", cfg.Server.ReadTimeout) - require.True(t, cfg.Server.VerboseCORS) - require.Len(t, cfg.CurrencyPairs, 3) - require.Equal(t, "ATOM", cfg.CurrencyPairs[0].Base) - require.Equal(t, "USDT", cfg.CurrencyPairs[0].Quote) - require.Len(t, cfg.CurrencyPairs[0].Providers, 3) - require.Equal(t, provider.ProviderKraken, cfg.CurrencyPairs[0].Providers[0]) - require.Equal(t, provider.ProviderBinance, cfg.CurrencyPairs[0].Providers[1]) -} - -func TestParseConfig_Valid_NoTelemetry(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "price-feeder*.toml") - require.NoError(t, err) - defer os.Remove(tmpFile.Name()) - - content := []byte(` -gas_adjustment = 1.5 - -[server] -listen_addr = "0.0.0.0:99999" -read_timeout = "20s" -verbose_cors = true -write_timeout = "20s" - -[[currency_pairs]] -base = "ATOM" -quote = "USDT" -providers = [ - "kraken", - "binance", - "huobi" -] - -[[currency_pairs]] -base = "UMEE" -quote = "USDT" -providers = [ - "kraken", - "binance", - "huobi" -] - -[[currency_pairs]] -base = "USDT" -quote = "USD" -providers = [ - "kraken", - "binance", - "huobi" -] - -[account] -address = "umee15nejfgcaanqpw25ru4arvfd0fwy6j8clccvwx4" -validator = "umeevalcons14rjlkfzp56733j5l5nfk6fphjxymgf8mj04d5p" -chain_id = "umee-local-testnet" - -[keyring] -backend = "test" -dir = "/Users/username/.umee" -pass = "keyringPassword" - -[rpc] -tmrpc_endpoint = "http://localhost:26657" -grpc_endpoint = "localhost:9090" -rpc_timeout = "100ms" - -[telemetry] -enabled = false -`) - _, err = tmpFile.Write(content) - require.NoError(t, err) - - cfg, err := config.ParseConfig(tmpFile.Name()) - require.NoError(t, err) - - require.Equal(t, "0.0.0.0:99999", cfg.Server.ListenAddr) - require.Equal(t, "20s", cfg.Server.WriteTimeout) - require.Equal(t, "20s", cfg.Server.ReadTimeout) - require.True(t, cfg.Server.VerboseCORS) - require.Len(t, cfg.CurrencyPairs, 3) - require.Equal(t, "ATOM", cfg.CurrencyPairs[0].Base) - require.Equal(t, "USDT", cfg.CurrencyPairs[0].Quote) - require.Len(t, cfg.CurrencyPairs[0].Providers, 3) - require.Equal(t, provider.ProviderKraken, cfg.CurrencyPairs[0].Providers[0]) - require.Equal(t, provider.ProviderBinance, cfg.CurrencyPairs[0].Providers[1]) - require.Equal(t, cfg.Telemetry.Enabled, false) -} - -func TestParseConfig_InvalidProvider(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "price-feeder*.toml") - require.NoError(t, err) - defer os.Remove(tmpFile.Name()) - - content := []byte(` -listen_addr = "" - -[[currency_pairs]] -base = "ATOM" -quote = "USD" -providers = [ - "kraken", - "binance" -] - -[[currency_pairs]] -base = "UMEE" -quote = "USD" -providers = [ - "kraken", - "foobar" -] -`) - _, err = tmpFile.Write(content) - require.NoError(t, err) - - _, err = config.ParseConfig(tmpFile.Name()) - require.Error(t, err) -} - -func TestParseConfig_NonUSDQuote(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "price-feeder*.toml") - require.NoError(t, err) - defer os.Remove(tmpFile.Name()) - - content := []byte(` -listen_addr = "" - -[[currency_pairs]] -base = "ATOM" -quote = "USDT" -providers = [ - "kraken", - "binance" -] - -[[currency_pairs]] -base = "UMEE" -quote = "USDT" -providers = [ - "kraken", - "binance" -] -`) - _, err = tmpFile.Write(content) - require.NoError(t, err) - - _, err = config.ParseConfig(tmpFile.Name()) - require.Error(t, err) -} - -func TestParseConfig_Valid_Deviations(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "price-feeder*.toml") - require.NoError(t, err) - defer os.Remove(tmpFile.Name()) - - content := []byte(` -gas_adjustment = 1.5 - -[server] -listen_addr = "0.0.0.0:99999" -read_timeout = "20s" -verbose_cors = true -write_timeout = "20s" - -[[deviation_thresholds]] -base = "USDT" -threshold = "2" - -[[deviation_thresholds]] -base = "ATOM" -threshold = "1.5" - -[[currency_pairs]] -base = "ATOM" -quote = "USDT" -providers = [ - "kraken", - "binance", - "huobi" -] - -[[currency_pairs]] -base = "UMEE" -quote = "USDT" -providers = [ - "kraken", - "binance", - "huobi" -] - -[[currency_pairs]] -base = "USDT" -quote = "USD" -providers = [ - "kraken", - "binance", - "huobi" -] - -[account] -address = "umee15nejfgcaanqpw25ru4arvfd0fwy6j8clccvwx4" -validator = "umeevalcons14rjlkfzp56733j5l5nfk6fphjxymgf8mj04d5p" -chain_id = "umee-local-testnet" - -[keyring] -backend = "test" -dir = "/Users/username/.umee" -pass = "keyringPassword" - -[rpc] -tmrpc_endpoint = "http://localhost:26657" -grpc_endpoint = "localhost:9090" -rpc_timeout = "100ms" - -[telemetry] -service-name = "price-feeder" -enabled = true -enable-hostname = true -enable-hostname-label = true -enable-service-label = true -prometheus-retention = 120 -global-labels = [["chain-id", "umee-local-testnet"]] -`) - _, err = tmpFile.Write(content) - require.NoError(t, err) - - cfg, err := config.ParseConfig(tmpFile.Name()) - require.NoError(t, err) - - require.Equal(t, "0.0.0.0:99999", cfg.Server.ListenAddr) - require.Equal(t, "20s", cfg.Server.WriteTimeout) - require.Equal(t, "20s", cfg.Server.ReadTimeout) - require.True(t, cfg.Server.VerboseCORS) - require.Len(t, cfg.CurrencyPairs, 3) - require.Equal(t, "ATOM", cfg.CurrencyPairs[0].Base) - require.Equal(t, "USDT", cfg.CurrencyPairs[0].Quote) - require.Len(t, cfg.CurrencyPairs[0].Providers, 3) - require.Equal(t, provider.ProviderKraken, cfg.CurrencyPairs[0].Providers[0]) - require.Equal(t, provider.ProviderBinance, cfg.CurrencyPairs[0].Providers[1]) - require.Equal(t, "2", cfg.Deviations[0].Threshold) - require.Equal(t, "USDT", cfg.Deviations[0].Base) - require.Equal(t, "1.5", cfg.Deviations[1].Threshold) - require.Equal(t, "ATOM", cfg.Deviations[1].Base) -} - -func TestParseConfig_Invalid_Deviations(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "price-feeder*.toml") - require.NoError(t, err) - defer os.Remove(tmpFile.Name()) - - content := []byte(` -gas_adjustment = 1.5 - -[server] -listen_addr = "0.0.0.0:99999" -read_timeout = "20s" -verbose_cors = true -write_timeout = "20s" - -[[deviation_thresholds]] -base = "USDT" -threshold = "4.0" - -[[deviation_thresholds]] -base = "ATOM" -threshold = "1.5" - -[[currency_pairs]] -base = "ATOM" -quote = "USDT" -providers = [ - "kraken", - "binance", - "huobi" -] - -[[currency_pairs]] -base = "UMEE" -quote = "USDT" -providers = [ - "kraken", - "binance", - "huobi" -] - -[[currency_pairs]] -base = "USDT" -quote = "USD" -providers = [ - "kraken", - "binance", - "huobi" -] - -[account] -address = "umee15nejfgcaanqpw25ru4arvfd0fwy6j8clccvwx4" -validator = "umeevalcons14rjlkfzp56733j5l5nfk6fphjxymgf8mj04d5p" -chain_id = "umee-local-testnet" - -[keyring] -backend = "test" -dir = "/Users/username/.umee" -pass = "keyringPassword" - -[rpc] -tmrpc_endpoint = "http://localhost:26657" -grpc_endpoint = "localhost:9090" -rpc_timeout = "100ms" - -[telemetry] -service-name = "price-feeder" -enabled = true -enable-hostname = true -enable-hostname_label = true -enable-service_label = true -prometheus-retention = 120 -global-labels = [["chain-id", "umee-local-testnet"]] -`) - _, err = tmpFile.Write(content) - require.NoError(t, err) - - _, err = config.ParseConfig(tmpFile.Name()) - require.Error(t, err) -} - -func TestParseConfig_Env_Vars(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "price-feeder*.toml") - require.NoError(t, err) - defer os.Remove(tmpFile.Name()) - - content := []byte(` -gas_adjustment = 1.5 - -[server] -listen_addr = "0.0.0.0:99999" -read_timeout = "20s" -verbose_cors = true -write_timeout = "20s" - -[[currency_pairs]] -base = "ATOM" -quote = "USDT" -providers = [ - "kraken", - "binance", - "huobi" -] - -[[currency_pairs]] -base = "UMEE" -quote = "USDT" -providers = [ - "kraken", - "binance", - "huobi" -] - -[[currency_pairs]] -base = "USDT" -quote = "USD" -providers = [ - "kraken", - "binance", - "huobi" -] - -[account] -address = "umee15nejfgcaanqpw25ru4arvfd0fwy6j8clccvwx4" -validator = "umeevalcons14rjlkfzp56733j5l5nfk6fphjxymgf8mj04d5p" -chain_id = "umee-local-testnet" - -[keyring] -backend = "test" -dir = "/Users/username/.umee" -pass = "keyringPassword" - -[rpc] -tmrpc_endpoint = "http://localhost:26657" -grpc_endpoint = "localhost:9090" -rpc_timeout = "100ms" - -[telemetry] -service-name = "price-feeder" -enabled = true -enable-hostname = true -enable-hostname_label = true -enable-service_label = true -prometheus-retention = 120 -global-labels = [["chain-id", "umee-local-testnet"]] -`) - _, err = tmpFile.Write(content) - require.NoError(t, err) - - // Set env variables to overwrite config files - os.Setenv("SERVER.LISTEN_ADDR", "0.0.0.0:888888") - os.Setenv("SERVER.WRITE_TIMEOUT", "10s") - os.Setenv("SERVER.READ_TIMEOUT", "10s") - os.Setenv("SERVER.VERBOSE_CORS", "false") - - cfg, err := config.ParseConfig(tmpFile.Name()) - require.NoError(t, err) - - require.Equal(t, "0.0.0.0:888888", cfg.Server.ListenAddr) - require.Equal(t, "10s", cfg.Server.WriteTimeout) - require.Equal(t, "10s", cfg.Server.ReadTimeout) - require.False(t, cfg.Server.VerboseCORS) - require.Len(t, cfg.CurrencyPairs, 3) - require.Equal(t, "ATOM", cfg.CurrencyPairs[0].Base) - require.Equal(t, "USDT", cfg.CurrencyPairs[0].Quote) - require.Len(t, cfg.CurrencyPairs[0].Providers, 3) - require.Equal(t, provider.ProviderKraken, cfg.CurrencyPairs[0].Providers[0]) - require.Equal(t, provider.ProviderBinance, cfg.CurrencyPairs[0].Providers[1]) -} - -func TestCheckProviderMins_Valid(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "price-feeder*.toml") - require.NoError(t, err) - defer os.Remove(tmpFile.Name()) - - content := []byte(` -gas_adjustment = 1.5 - -[server] -listen_addr = "0.0.0.0:99999" -read_timeout = "20s" -verbose_cors = true -write_timeout = "20s" - -[[currency_pairs]] -base = "ATOM" -quote = "USDT" -providers = [ - "kraken", - "binance", - "huobi" -] - -[[currency_pairs]] -base = "UMEE" -quote = "USDT" -providers = [ - "kraken", - "binance", - "huobi" -] - -[[currency_pairs]] -base = "USDT" -quote = "USD" -providers = [ - "kraken", - "binance", - "huobi" -] - -[account] -address = "umee15nejfgcaanqpw25ru4arvfd0fwy6j8clccvwx4" -validator = "umeevalcons14rjlkfzp56733j5l5nfk6fphjxymgf8mj04d5p" -chain_id = "umee-local-testnet" - -[keyring] -backend = "test" -dir = "/Users/username/.umee" -pass = "keyringPassword" - -[rpc] -tmrpc_endpoint = "http://localhost:26657" -grpc_endpoint = "localhost:9090" -rpc_timeout = "100ms" - -[telemetry] -enabled = false -`) - _, err = tmpFile.Write(content) - require.NoError(t, err) - - cfg, err := config.ParseConfig(tmpFile.Name()) - require.NoError(t, err) - - logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).Level(zerolog.InfoLevel).With().Timestamp().Logger() - err = config.CheckProviderMins(context.TODO(), logger, cfg) - require.NoError(t, err) -} - -func TestCheckProviderMins_Invalid(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "price-feeder*.toml") - require.NoError(t, err) - defer os.Remove(tmpFile.Name()) - - content := []byte(` -gas_adjustment = 1.5 - -[server] -listen_addr = "0.0.0.0:99999" -read_timeout = "20s" -verbose_cors = true -write_timeout = "20s" - -[[currency_pairs]] -base = "ATOM" -quote = "USDT" -providers = [ - "kraken", - "binance", -] - -[[currency_pairs]] -base = "UMEE" -quote = "USDT" -providers = [ - "kraken", - "binance", - "huobi" -] - -[[currency_pairs]] -base = "USDT" -quote = "USD" -providers = [ - "kraken", - "binance", - "huobi" -] - -[account] -address = "umee15nejfgcaanqpw25ru4arvfd0fwy6j8clccvwx4" -validator = "umeevalcons14rjlkfzp56733j5l5nfk6fphjxymgf8mj04d5p" -chain_id = "umee-local-testnet" - -[keyring] -backend = "test" -dir = "/Users/username/.umee" -pass = "keyringPassword" - -[rpc] -tmrpc_endpoint = "http://localhost:26657" -grpc_endpoint = "localhost:9090" -rpc_timeout = "100ms" - -[telemetry] -enabled = false -`) - _, err = tmpFile.Write(content) - require.NoError(t, err) - - cfg, err := config.ParseConfig(tmpFile.Name()) - require.NoError(t, err) - - logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).Level(zerolog.InfoLevel).With().Timestamp().Logger() - err = config.CheckProviderMins(context.TODO(), logger, cfg) - require.EqualError(t, err, "must have at least 3 providers for ATOM") -} diff --git a/price-feeder/config/currency_provider_tracker.go b/price-feeder/config/currency_provider_tracker.go deleted file mode 100644 index 6a8696a551..0000000000 --- a/price-feeder/config/currency_provider_tracker.go +++ /dev/null @@ -1,228 +0,0 @@ -package config - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "strings" - "time" - - "github.com/rs/zerolog" -) - -const ( - coinGeckoRestURL = "https://api.coingecko.com/api/v3/coins" - coinGeckoListEndpoint = "list" - coinGeckoTickersEndpoint = "tickers" - osmosisV2RestURL = "https://api.osmo-api.network.umee.cc" - osmosisV2AssetPairsEndpoint = "assetpairs" - requestTimeout = time.Second * 2 - trackingPeriod = time.Hour * 24 -) - -type ( - // CurrencyProviderTracker queries the CoinGecko API and UMEE's osmosis-api for all - // the exchanges that support the currency pairs set in the price feeder config. It - // will poll the APIs every 24 hours to log any new exchanges that were added for a - // given currency. - // - // REF: https://www.coingecko.com/en/api/documentation - // REF: https://github.com/umee-network/osmosis-api - CurrencyProviderTracker struct { - logger zerolog.Logger - pairs []CurrencyPair - coinIDSymbolMap map[string]string // ex: map["ATOM"] = "cosmos" - CurrencyProviders map[string][]string // map of price feeder currencies and what exchanges support them - CurrencyProviderMin map[string]int // map of price feeder currencies and min required providers for them - } - - // List of assets on CoinGecko and their corresponding id and symbol. - coinList struct { - ID string `json:"id"` // ex: "cosmos" - Symbol string `json:"symbol"` // ex: "ATOM" - } - - // CoinGecko ticker shows market data for a given currency pair including what - // exchanges they're on. - coinTickerResponse struct { - Tickers []coinTicker `json:"tickers"` - } - coinTicker struct { - Base string `json:"base"` // CurrencyPair.Base - Target string `json:"target"` // CurrencyPair.Quote - Market coinMarket `json:"market"` - } - coinMarket struct { - Name string `json:"name"` // ex: Binance - } - - // Response from the osmosis-api REST server. - assetPair struct { - Base string `json:"base"` - Quote string `json:"quote"` - } -) - -func NewCurrencyProviderTracker( - ctx context.Context, - logger zerolog.Logger, - pairs ...CurrencyPair, -) (*CurrencyProviderTracker, error) { - currencyProviderTracker := &CurrencyProviderTracker{ - logger: logger, - pairs: pairs, - coinIDSymbolMap: map[string]string{}, - CurrencyProviders: map[string][]string{}, - CurrencyProviderMin: map[string]int{}, - } - - if err := currencyProviderTracker.setCoinIDSymbolMap(); err != nil { - return nil, err - } - - osmosisAPIPairs, err := currencyProviderTracker.getOsmosisAPIPairs() - if err != nil { - return nil, err - } - - if err := currencyProviderTracker.setCurrencyProviders(osmosisAPIPairs); err != nil { - return nil, err - } - - currencyProviderTracker.setCurrencyProviderMin() - - go currencyProviderTracker.trackCurrencyProviders(ctx) - - return currencyProviderTracker, nil -} - -func (t *CurrencyProviderTracker) logCurrencyProviders() { - for currency, providers := range t.CurrencyProviders { - t.logger.Info().Msg(fmt.Sprintf("providers supporting %s: %v", currency, providers)) - } -} - -// setCoinIDSymbolMap gets list of assets on CoinGecko to cross reference coin symbol to id. -func (t *CurrencyProviderTracker) setCoinIDSymbolMap() error { - resp, err := http.Get(fmt.Sprintf("%s/%s", coinGeckoRestURL, coinGeckoListEndpoint)) - if err != nil { - return err - } - defer resp.Body.Close() - - var listResponse []coinList - if err := json.NewDecoder(resp.Body).Decode(&listResponse); err != nil { - return err - } - - for _, coin := range listResponse { - if _, ok := t.coinIDSymbolMap[coin.Symbol]; !ok { - t.coinIDSymbolMap[coin.Symbol] = coin.ID - } - } - - return nil -} - -// getOsmosisAPIPairs queries the osmosis-api assetpairs endpoint to get the asset pairs -// supported by it. -func (t *CurrencyProviderTracker) getOsmosisAPIPairs() (map[string]string, error) { - client := &http.Client{ - Timeout: requestTimeout, - } - osmosisAPIPairs := make(map[string]string) - - osmosisResp, err := client.Get(fmt.Sprintf("%s/%s", osmosisV2RestURL, osmosisV2AssetPairsEndpoint)) - if err != nil { - return nil, err - } - defer osmosisResp.Body.Close() - var assetPairsResponse []assetPair - if err = json.NewDecoder(osmosisResp.Body).Decode(&assetPairsResponse); err != nil { - return nil, err - } - - for _, assetPair := range assetPairsResponse { - osmosisAPIPairs[assetPair.Base] = assetPair.Quote - } - - return osmosisAPIPairs, nil -} - -// setCurrencyProviders queries CoinGecko's tickers endpoint to get all the exchanges -// that support each price feeder currency pair and store it in the CurrencyProviders map. -func (t *CurrencyProviderTracker) setCurrencyProviders(osmosisAPIPairs map[string]string) error { - client := &http.Client{ - Timeout: requestTimeout, - } - for _, pair := range t.pairs { - // check if its a pair supported by the osmosis api - if osmosisAPIPairs[strings.ToUpper(pair.Base)] == strings.ToUpper(pair.Quote) { - t.CurrencyProviders[pair.Base] = append(t.CurrencyProviders[pair.Base], "osmosisv2") - } - - // check if CoinGecko API supports pair - pairBaseID := t.coinIDSymbolMap[strings.ToLower(pair.Base)] - coinGeckoResp, err := client.Get(fmt.Sprintf("%s/%s/%s", coinGeckoRestURL, pairBaseID, coinGeckoTickersEndpoint)) - if err != nil { - t.logger.Error().Err(err).Msg(fmt.Sprintf("Failed to query coin gecko api tickers endpoint for %s", pair.Base)) - } else { - defer coinGeckoResp.Body.Close() - var tickerResponse coinTickerResponse - if err = json.NewDecoder(coinGeckoResp.Body).Decode(&tickerResponse); err != nil { - return err - } - - for _, ticker := range tickerResponse.Tickers { - if ticker.Target == pair.Quote { - t.CurrencyProviders[pair.Base] = append(t.CurrencyProviders[pair.Base], ticker.Market.Name) - } - } - - if len(t.CurrencyProviders[pair.Base]) == 0 { - t.logger.Warn().Msg(fmt.Sprintf("Coin gecko found 0 providers for %s", pair.Base)) - } - } - } - - return nil -} - -// setCurrencyProviderMin will set the minimum amount of providers for each currency -// to the amount of exchanges that support them if it's less than 3. Otherwise it is -// set to 3 providers. -func (t *CurrencyProviderTracker) setCurrencyProviderMin() { - for base, exchanges := range t.CurrencyProviders { - // If CoinGecko returns 0 or 3 and above providers for a given pair, the - // minimum providers enforced will default to 3. - if len(exchanges) < 3 && len(exchanges) > 0 { - t.CurrencyProviderMin[base] = len(exchanges) - } else { - t.CurrencyProviderMin[base] = 3 - } - } -} - -// trackCurrencyProviders resets CurrencyProviders map and logs out supported -// exchanges for each currency every 24 hours. -func (t *CurrencyProviderTracker) trackCurrencyProviders(ctx context.Context) { - t.logCurrencyProviders() - - for { - select { - case <-ctx.Done(): - return - case <-time.After(trackingPeriod): - osmosisAPIPairs, err := t.getOsmosisAPIPairs() - if err != nil { - t.logger.Error().Err(err).Msg("failed to query osmosis-api for available asset pairs") - } - if err := t.setCurrencyProviders(osmosisAPIPairs); err != nil { - t.logger.Error().Err(err).Msg("failed to set available providers for currencies") - } - - t.logCurrencyProviders() - } - } -} diff --git a/price-feeder/go.mod b/price-feeder/go.mod deleted file mode 100644 index 7e50ab5bde..0000000000 --- a/price-feeder/go.mod +++ /dev/null @@ -1,322 +0,0 @@ -module github.com/umee-network/umee/price-feeder/v2 - -go 1.19 - -require ( - cosmossdk.io/errors v1.0.0-beta.7 - github.com/armon/go-metrics v0.4.1 - github.com/cosmos/cosmos-sdk v0.46.11 - github.com/go-playground/validator/v10 v10.12.0 - github.com/golangci/golangci-lint v1.52.2 - github.com/gorilla/mux v1.8.0 - github.com/gorilla/websocket v1.5.0 - github.com/justinas/alice v1.2.0 - github.com/mgechev/revive v1.3.1 - github.com/mitchellh/mapstructure v1.5.0 - github.com/rs/cors v1.8.3 - github.com/rs/zerolog v1.29.0 - github.com/spf13/cobra v1.6.1 - github.com/spf13/viper v1.15.0 - github.com/stretchr/testify v1.8.2 - github.com/tendermint/tendermint v0.34.27 - github.com/umee-network/umee/v4 v4.0.0 - golang.org/x/sync v0.1.0 - golang.org/x/term v0.6.0 - google.golang.org/grpc v1.54.0 - gopkg.in/yaml.v3 v3.0.1 - gotest.tools/v3 v3.4.0 -) - -require ( - 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect - 4d63.com/gochecknoglobals v0.2.1 // indirect - cloud.google.com/go v0.107.0 // indirect - cloud.google.com/go/compute v1.15.1 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v0.8.0 // indirect - cloud.google.com/go/storage v1.27.0 // indirect - cosmossdk.io/math v1.0.0 // indirect - filippo.io/edwards25519 v1.0.0-rc.1 // indirect - github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect - github.com/99designs/keyring v1.2.1 // indirect - github.com/Abirdcfly/dupword v0.0.11 // indirect - github.com/Antonboom/errname v0.1.9 // indirect - github.com/Antonboom/nilnil v0.1.3 // indirect - github.com/BurntSushi/toml v1.2.1 // indirect - github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect - github.com/CosmWasm/wasmd v0.29.0 // indirect - github.com/CosmWasm/wasmvm v1.1.1 // indirect - github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect - github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 // indirect - github.com/Gravity-Bridge/Gravity-Bridge/module v1.5.3 // indirect - github.com/Masterminds/semver v1.5.0 // indirect - github.com/OpenPeeDeeP/depguard v1.1.1 // indirect - github.com/Workiva/go-datastructures v1.0.53 // indirect - github.com/alexkohler/prealloc v1.0.0 // indirect - github.com/alingse/asasalint v0.0.11 // indirect - github.com/ashanbrown/forbidigo v1.5.1 // indirect - github.com/ashanbrown/makezero v1.1.1 // indirect - github.com/aws/aws-sdk-go v1.44.122 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect - github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect - github.com/bkielbasa/cyclop v1.2.0 // indirect - github.com/blizzy78/varnamelen v0.8.0 // indirect - github.com/bombsimon/wsl/v3 v3.4.0 // indirect - github.com/breml/bidichk v0.2.4 // indirect - github.com/breml/errchkjson v0.3.1 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect - github.com/butuzov/ireturn v0.1.1 // indirect - github.com/cenkalti/backoff/v4 v4.1.3 // indirect - github.com/cespare/xxhash v1.1.0 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/charithe/durationcheck v0.0.10 // indirect - github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 // indirect - github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect - github.com/cockroachdb/apd/v2 v2.0.2 // indirect - github.com/coinbase/rosetta-sdk-go v0.7.9 // indirect - github.com/cometbft/cometbft-db v0.7.0 // indirect - github.com/confio/ics23/go v0.9.0 // indirect - github.com/cosmos/btcutil v1.0.5 // indirect - github.com/cosmos/cosmos-proto v1.0.0-beta.3 // indirect - github.com/cosmos/go-bip39 v1.0.0 // indirect - github.com/cosmos/gogoproto v1.4.3 // indirect - github.com/cosmos/gorocksdb v1.2.0 // indirect - github.com/cosmos/iavl v0.19.5 // indirect - github.com/cosmos/ibc-go/v5 v5.2.0 // indirect - github.com/cosmos/ledger-cosmos-go v0.12.2 // indirect - github.com/creachadair/taskgroup v0.3.2 // indirect - github.com/curioswitch/go-reassign v0.2.0 // indirect - github.com/daixiang0/gci v0.10.1 // indirect - github.com/danieljoos/wincred v1.1.2 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect - github.com/denis-tingaikin/go-header v0.4.3 // indirect - github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect - github.com/dgraph-io/badger/v2 v2.2007.4 // indirect - github.com/dgraph-io/ristretto v0.1.0 // indirect - github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect - github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac // indirect - github.com/dvsekhvalnov/jose2go v1.5.0 // indirect - github.com/esimonov/ifshort v1.0.4 // indirect - github.com/ethereum/go-ethereum v1.11.5 // indirect - github.com/ettle/strcase v0.1.1 // indirect - github.com/fatih/color v1.15.0 // indirect - github.com/fatih/structtag v1.2.0 // indirect - github.com/felixge/httpsnoop v1.0.1 // indirect - github.com/firefart/nonamedreturns v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/go-critic/go-critic v0.7.0 // indirect - github.com/go-kit/kit v0.12.0 // indirect - github.com/go-kit/log v0.2.1 // indirect - github.com/go-logfmt/logfmt v0.5.1 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-toolsmith/astcast v1.1.0 // indirect - github.com/go-toolsmith/astcopy v1.1.0 // indirect - github.com/go-toolsmith/astequal v1.1.0 // indirect - github.com/go-toolsmith/astfmt v1.1.0 // indirect - github.com/go-toolsmith/astp v1.1.0 // indirect - github.com/go-toolsmith/strparse v1.1.0 // indirect - github.com/go-toolsmith/typep v1.1.0 // indirect - github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect - github.com/gobwas/glob v0.2.3 // indirect - github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect - github.com/gofrs/flock v0.8.1 // indirect - github.com/gogo/gateway v1.1.0 // indirect - github.com/gogo/protobuf v1.3.3 // indirect - github.com/golang/glog v1.0.0 // 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.4 // indirect - github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 // indirect - github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect - github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe // indirect - github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2 // indirect - github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 // indirect - github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca // indirect - github.com/golangci/misspell v0.4.0 // indirect - github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 // indirect - github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect - github.com/google/btree v1.1.2 // indirect - github.com/google/go-cmp v0.5.9 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/google/orderedcode v0.0.1 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect - github.com/googleapis/gax-go/v2 v2.7.0 // indirect - github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 // indirect - github.com/gorilla/handlers v1.5.1 // indirect - github.com/gostaticanalysis/analysisutil v0.7.1 // indirect - github.com/gostaticanalysis/comment v1.4.2 // indirect - github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect - github.com/gostaticanalysis/nilerr v0.1.1 // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect - github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect - github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect - github.com/gtank/merlin v0.1.1 // indirect - github.com/gtank/ristretto255 v0.1.2 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-getter v1.7.0 // indirect - github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-safetemp v1.0.0 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect - github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect - github.com/hexops/gotextdiff v1.0.3 // indirect - github.com/holiman/uint256 v1.2.0 // indirect - github.com/ignite/cli v0.26.1 // indirect - github.com/improbable-eng/grpc-web v0.15.0 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect - github.com/jgautheron/goconst v1.5.1 // indirect - github.com/jingyugao/rowserrcheck v1.1.1 // indirect - github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/jmhodges/levigo v1.0.0 // indirect - github.com/julz/importas v0.1.0 // indirect - github.com/junk1tm/musttag v0.5.0 // indirect - github.com/kisielk/errcheck v1.6.3 // indirect - github.com/kisielk/gotool v1.0.0 // indirect - github.com/kkHAIKE/contextcheck v1.1.4 // indirect - github.com/klauspost/compress v1.15.15 // indirect - github.com/kulti/thelper v0.6.3 // indirect - github.com/kunwardeep/paralleltest v1.0.6 // indirect - github.com/kyoh86/exportloopref v0.1.11 // indirect - github.com/ldez/gomoddirectives v0.2.3 // indirect - github.com/ldez/tagliatelle v0.4.0 // indirect - github.com/leodido/go-urn v1.2.2 // indirect - github.com/leonklingele/grouper v1.1.1 // indirect - github.com/lib/pq v1.10.7 // indirect - github.com/libp2p/go-buffer-pool v0.1.0 // indirect - github.com/lufeee/execinquery v1.2.1 // indirect - github.com/magiconair/properties v1.8.7 // indirect - github.com/manifoldco/promptui v0.9.0 // indirect - github.com/maratori/testableexamples v1.0.0 // indirect - github.com/maratori/testpackage v1.1.1 // indirect - github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect - github.com/mattn/go-runewidth v0.0.14 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/mbilski/exhaustivestruct v1.2.0 // indirect - github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect - github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect - github.com/minio/highwayhash v1.0.2 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/go-testing-interface v1.14.1 // indirect - github.com/moricho/tparallel v0.3.1 // indirect - github.com/mtibben/percent v0.2.1 // indirect - github.com/nakabonne/nestif v0.3.1 // indirect - github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect - github.com/nishanths/exhaustive v0.9.5 // indirect - github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.9.0 // indirect - github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/osmosis-labs/bech32-ibc v0.3.1 // indirect - github.com/pelletier/go-toml/v2 v2.0.6 // indirect - github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/polyfloyd/go-errorlint v1.4.0 // indirect - github.com/prometheus/client_golang v1.14.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.39.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect - github.com/quasilyte/go-ruleguard v0.3.19 // indirect - github.com/quasilyte/gogrep v0.5.0 // indirect - github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect - github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect - github.com/rakyll/statik v0.1.7 // indirect - github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect - github.com/regen-network/cosmos-proto v0.3.1 // indirect - github.com/rivo/uniseg v0.4.2 // indirect - github.com/rs/xid v1.4.0 // indirect - github.com/ryancurrah/gomodguard v1.3.0 // indirect - github.com/ryanrolds/sqlclosecheck v0.4.0 // indirect - github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect - github.com/sasha-s/go-deadlock v0.3.1 // indirect - github.com/sashamelentyev/interfacebloat v1.1.0 // indirect - github.com/sashamelentyev/usestdlibvars v1.23.0 // indirect - github.com/securego/gosec/v2 v2.15.0 // indirect - github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect - github.com/sirupsen/logrus v1.9.0 // indirect - github.com/sivchari/containedctx v1.0.2 // indirect - github.com/sivchari/nosnakecase v1.7.0 // indirect - github.com/sivchari/tenv v1.7.1 // indirect - github.com/sonatard/noctx v0.0.2 // indirect - github.com/sourcegraph/go-diff v0.7.0 // indirect - github.com/spf13/afero v1.9.3 // indirect - github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect - github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect - github.com/stretchr/objx v0.5.0 // indirect - github.com/subosito/gotenv v1.4.2 // indirect - github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect - github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect - github.com/tdakkota/asciicheck v0.2.0 // indirect - github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect - github.com/tendermint/go-amino v0.16.0 // indirect - github.com/tendermint/tm-db v0.6.7 // indirect - github.com/tetafro/godot v1.4.11 // indirect - github.com/tidwall/btree v1.5.0 // indirect - github.com/timakin/bodyclose v0.0.0-20221125081123-e39cf3fc478e // indirect - github.com/timonwong/loggercheck v0.9.4 // indirect - github.com/tomarrell/wrapcheck/v2 v2.8.1 // indirect - github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect - github.com/ugorji/go/codec v1.2.7 // indirect - github.com/ulikunitz/xz v0.5.10 // indirect - github.com/ultraware/funlen v0.0.3 // indirect - github.com/ultraware/whitespace v0.0.5 // indirect - github.com/uudashr/gocognit v1.0.6 // indirect - github.com/yagipy/maintidx v1.0.0 // indirect - github.com/yeya24/promlinter v0.2.0 // indirect - github.com/zondax/hid v0.9.1 // indirect - github.com/zondax/ledger-go v0.14.1 // indirect - gitlab.com/bosi/decorder v0.2.3 // indirect - go.etcd.io/bbolt v1.3.6 // indirect - go.opencensus.io v0.24.0 // indirect - go.uber.org/atomic v1.10.0 // indirect - go.uber.org/multierr v1.8.0 // indirect - go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.7.0 // indirect - golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect - golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2 // indirect - golang.org/x/mod v0.9.0 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/oauth2 v0.4.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect - golang.org/x/tools v0.7.0 // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.107.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa // indirect - google.golang.org/protobuf v1.30.0 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - honnef.co/go/tools v0.4.3 // indirect - mvdan.cc/gofumpt v0.4.0 // indirect - mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect - mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect - mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d // indirect - nhooyr.io/websocket v1.8.6 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect -) - -replace ( - github.com/CosmWasm/wasmd => github.com/notional-labs/wasmd v0.29.0-sdk46 - github.com/Gravity-Bridge/Gravity-Bridge/module => github.com/umee-network/Gravity-Bridge/module v1.5.3-umee-4 - github.com/cosmos/cosmos-sdk => github.com/umee-network/cosmos-sdk v0.46.11-umee - // dgrijalva/jwt-go is deprecated and doesn't receive security updates. - github.com/dgrijalva/jwt-go => github.com/golang-jwt/jwt/v4 v4.4.2 - github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 - github.com/osmosis-labs/bech32-ibc => github.com/umee-network/bech32-ibc v0.3.2 - github.com/tendermint/tendermint => github.com/cometbft/cometbft v0.34.27 - github.com/umee-network/umee/v4 => ../ -) diff --git a/price-feeder/go.sum b/price-feeder/go.sum deleted file mode 100644 index aa03c271e3..0000000000 --- a/price-feeder/go.sum +++ /dev/null @@ -1,2207 +0,0 @@ -4d63.com/gocheckcompilerdirectives v1.2.1 h1:AHcMYuw56NPjq/2y615IGg2kYkBdTvOaojYCBcRE7MA= -4d63.com/gocheckcompilerdirectives v1.2.1/go.mod h1:yjDJSxmDTtIHHCqX0ufRYZDL6vQtMG7tJdKVeWwsqvs= -4d63.com/gochecknoglobals v0.2.1 h1:1eiorGsgHOFOuoOiJDy2psSrQbRdIHrlge0IJIkUgDc= -4d63.com/gochecknoglobals v0.2.1/go.mod h1:KRE8wtJB3CXCsb1xy421JfTHIIbmT3U5ruxw2Qu8fSU= -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.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.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.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.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.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.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww= -cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= -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/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/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/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/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/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/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/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/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= -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/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/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/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.15.1 h1:7UGq3QknM33pw5xATlpzeoomNxsacIVvTqTTvbfajmE= -cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= -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/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/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/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/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/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/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= -cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= -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/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/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/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/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/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/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/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/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/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/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= -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.8.0 h1:E2osAkZzxI/+8pZcxVLcDtAQx/u+hZXVryUaYQ5O0Kk= -cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= -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/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/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= -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/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= -cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= -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/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/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/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/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/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/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/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/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/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/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/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/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/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/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= -cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= -cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= -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/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= -cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= -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/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/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 h1:YOO045NZI9RKfCj1c5A/ZtuuENUc8OAW+gHdGnDgyMQ= -cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= -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/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/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/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/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= -cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= -collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= -cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w= -cosmossdk.io/errors v1.0.0-beta.7/go.mod h1:mz6FQMJRku4bY7aqS/Gwfcmr/ue91roMEKAmDUDpBfE= -cosmossdk.io/math v1.0.0 h1:ro9w7eKx23om2tZz/VM2Pf+z2WAbGX1yDQQOJ6iGeJw= -cosmossdk.io/math v1.0.0/go.mod h1:Ygz4wBHrgc7g0N+8+MrnTfS9LLn9aaTGa9hKopuym5k= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= -filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= -git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3/go.mod h1:wMEGFFFNuPos7vHmWXfszqImLppbc0wEhh6JBfJIUgw= -git.sr.ht/~sircmpwn/go-bare v0.0.0-20210406120253-ab86bc2846d9/go.mod h1:BVJwbDfVjCjoFiKrhkei6NdGcZYpkDkdyCdg1ukytRA= -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/Abirdcfly/dupword v0.0.11 h1:z6v8rMETchZXUIuHxYNmlUAuKuB21PeaSymTed16wgU= -github.com/Abirdcfly/dupword v0.0.11/go.mod h1:wH8mVGuf3CP5fsBTkfWwwwKTjDnVVCxtU8d8rgeVYXA= -github.com/Antonboom/errname v0.1.9 h1:BZDX4r3l4TBZxZ2o2LNrlGxSHran4d1u4veZdoORTT4= -github.com/Antonboom/errname v0.1.9/go.mod h1:nLTcJzevREuAsgTbG85UsuiWpMpAqbKD1HNZ29OzE58= -github.com/Antonboom/nilnil v0.1.3 h1:6RTbx3d2mcEu3Zwq9TowQpQMVpP75zugwOtqY1RTtcE= -github.com/Antonboom/nilnil v0.1.3/go.mod h1:iOov/7gRcXkeEU+EMGpBu2ORih3iyVEiWjeste1SJm8= -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= -github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -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= -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/CosmWasm/wasmvm v1.1.1 h1:0xtdrmmsP9fibe+x42WcMkp5aQ738BICgcH3FNVLzm4= -github.com/CosmWasm/wasmvm v1.1.1/go.mod h1:ei0xpvomwSdONsxDuONzV7bL1jSET1M8brEx0FCXc+A= -github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/DataDog/zstd v1.5.0/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= -github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= -github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= -github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 h1:+r1rSv4gvYn0wmRjC8X7IAzX8QezqtFV9m0MUHFJgts= -github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0/go.mod h1:b3g59n2Y+T5xmcxJL+UEG2f8cQploZm1mR/v6BW0mU0= -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= -github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/OpenPeeDeeP/depguard v1.1.1 h1:TSUznLjvp/4IUP+OQ0t/4jF4QUyxIcVX8YnghZdunyA= -github.com/OpenPeeDeeP/depguard v1.1.1/go.mod h1:JtAMzWkmFEzDPyAd+W0NHl1lvpQKTvT9jnRVsohBKpc= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= -github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig= -github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= -github.com/Zilliqa/gozilliqa-sdk v1.2.1-0.20201201074141-dd0ecada1be6/go.mod h1:eSYp2T6f0apnuW8TzhV3f6Aff2SE8Dwio++U4ha4yEM= -github.com/adlio/schema v1.3.3 h1:oBJn8I02PyTB466pZO1UZEn1TV5XLlifBSyMrmHl/1I= -github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= -github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= -github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= -github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= -github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.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/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= -github.com/ashanbrown/forbidigo v1.5.1 h1:WXhzLjOlnuDYPYQo/eFlcFMi8X/kLfvWLYu6CSoebis= -github.com/ashanbrown/forbidigo v1.5.1/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= -github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= -github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= -github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= -github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.44.122 h1:p6mw01WBaNpbdP2xrisz5tIkcNwzj/HysobNoaAHjgo= -github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= -github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= -github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8= -github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4= -github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= -github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= -github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= -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/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= -github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 h1:41iFGWnSlI2gVpmOtVTJZNodLdLQLn/KsJqFvXwnd/s= -github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bkielbasa/cyclop v1.2.0 h1:7Jmnh0yL2DjKfw28p86YTd/B4lRGcNuu12sKE35sM7A= -github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI= -github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= -github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= -github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= -github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= -github.com/bombsimon/wsl/v3 v3.4.0 h1:RkSxjT3tmlptwfgEgTgU+KYKLI35p/tviNXNXiL2aNU= -github.com/bombsimon/wsl/v3 v3.4.0/go.mod h1:KkIB+TXkqy6MvK9BDZVbZxKNYsE1/oLRJbIFtf14qqo= -github.com/breml/bidichk v0.2.4 h1:i3yedFWWQ7YzjdZJHnPo9d/xURinSq3OM+gyM43K4/8= -github.com/breml/bidichk v0.2.4/go.mod h1:7Zk0kRFt1LIZxtQdl9W9JwGAcLTTkOs+tN7wuEYGJ3s= -github.com/breml/errchkjson v0.3.1 h1:hlIeXuspTyt8Y/UmP5qy1JocGNR00KQHgfaNtRAjoxQ= -github.com/breml/errchkjson v0.3.1/go.mod h1:XroxrzKjdiutFyW3nWhw34VGg7kiMsDQox73yWCGI2U= -github.com/btcsuite/btcd v0.0.0-20190315201642-aa6e0f35703c/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= -github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.21.0-beta.0.20201114000516-e9c7a5ac6401/go.mod h1:Sv4JPQ3/M+teHz9Bo5jBpkNcP0x6r7rdihlNL/7tTAs= -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.1.2/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= -github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= -github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= -github.com/btcsuite/btcd/btcutil v1.1.2 h1:XLMbX8JQEiwMcYft2EGi8zPUkoa0abKIU6/BJSRsjzQ= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= -github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= -github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= -github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= -github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= -github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= -github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/butuzov/ireturn v0.1.1 h1:QvrO2QF2+/Cx1WA/vETCIYBKtRjc30vesdoPUNo1EbY= -github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= -github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= -github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= -github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -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/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= -github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= -github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 h1:W9o46d2kbNL06lq7UNDPV0zYLzkrde/bjIqO02eoll0= -github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8/go.mod h1:gakxgyXaaPkxvLw1XQxNGK4I37ys9iBRzNUx/B7pUCo= -github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= -github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= -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/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= -github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/coinbase/kryptology v1.8.0/go.mod h1:RYXOAPdzOGUe3qlSFkMGn58i3xUA8hmxYHksuq+8ciI= -github.com/coinbase/rosetta-sdk-go v0.7.9 h1:lqllBjMnazTjIqYrOGv8h8jxjg9+hJazIGZr9ZvoCcA= -github.com/coinbase/rosetta-sdk-go v0.7.9/go.mod h1:0/knutI7XGVqXmmH4OQD8OckFrbQ8yMsUZTG7FXCR2M= -github.com/cometbft/cometbft v0.34.27 h1:ri6BvmwjWR0gurYjywcBqRe4bbwc3QVs9KRcCzgh/J0= -github.com/cometbft/cometbft v0.34.27/go.mod h1:BcCbhKv7ieM0KEddnYXvQZR+pZykTKReJJYf7YC7qhw= -github.com/cometbft/cometbft-db v0.7.0 h1:uBjbrBx4QzU0zOEnU8KxoDl18dMNgDh+zZRUE0ucsbo= -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/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= -github.com/consensys/bavard v0.1.8-0.20210915155054-088da2f7f54a/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= -github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= -github.com/consensys/gnark-crypto v0.5.3/go.mod h1:hOdPlWQV1gDLp7faZVeg8Y0iEPFaOUnCc4XeCCk96p0= -github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk= -github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis= -github.com/cosmos/cosmos-proto v1.0.0-beta.3 h1:VitvZ1lPORTVxkmF2fAp3IiA61xVwArQYKXTdEcpW6o= -github.com/cosmos/cosmos-proto v1.0.0-beta.3/go.mod h1:t8IASdLaAq+bbHbjq4p960BvcTqtwuAxid3b/2rOD6I= -github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= -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/gogoproto v1.4.3 h1:RP3yyVREh9snv/lsOvmsAPQt8f44LgL281X0IOIhhcI= -github.com/cosmos/gogoproto v1.4.3/go.mod h1:0hLIG5TR7IvV1fme1HCFKjfzW9X2x0Mo+RooWXCnOWU= -github.com/cosmos/gorocksdb v1.2.0 h1:d0l3jJG8M4hBouIZq0mDUHZ+zjOx044J3nGRskwTb4Y= -github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw= -github.com/cosmos/iavl v0.19.5 h1:rGA3hOrgNxgRM5wYcSCxgQBap7fW82WZgY78V9po/iY= -github.com/cosmos/iavl v0.19.5/go.mod h1:X9PKD3J0iFxdmgNLa7b2LYWdsGd90ToV5cAONApkEPw= -github.com/cosmos/ibc-go/v5 v5.2.0 h1:LxwttRQqdUJpQ3/Gc3XPg5lkRo3pcbzx65dxFIY6ONE= -github.com/cosmos/ibc-go/v5 v5.2.0/go.mod h1:MhDUMDVSboK5JW2pEWHNcw0wJHaHqKV/vwwP7awGhzI= -github.com/cosmos/interchain-accounts v0.3.2 h1:7S5rSndahpjkzUvG00kKZrTZsEuVHuVC9a7p0qDVcF8= -github.com/cosmos/ledger-cosmos-go v0.12.2 h1:/XYaBlE2BJxtvpkHiBm97gFGSGmYGKunKyF3nNqAXZA= -github.com/cosmos/ledger-cosmos-go v0.12.2/go.mod h1:ZcqYgnfNJ6lAXe4HPtWgarNEY+B74i+2/8MhZw4ziiI= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creachadair/taskgroup v0.3.2 h1:zlfutDS+5XG40AOxcHDSThxKzns8Tnr9jnr6VqkYlkM= -github.com/creachadair/taskgroup v0.3.2/go.mod h1:wieWwecHVzsidg2CsUnFinW1faVN4+kq+TDlRJQ0Wbk= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= -github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= -github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= -github.com/daixiang0/gci v0.10.1 h1:eheNA3ljF6SxnPD/vE4lCBusVHmV3Rs3dkKvFrJ7MR0= -github.com/daixiang0/gci v0.10.1/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI= -github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= -github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= -github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= -github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= -github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= -github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= -github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= -github.com/denis-tingaikin/go-header v0.4.3 h1:tEaZKAlqql6SKCY++utLmkPLd6K8IBM20Ha7UVm+mtU= -github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c= -github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= -github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= -github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= -github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= -github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= -github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= -github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= -github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= -github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -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/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= -github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac h1:opbrjaN/L8gg6Xh5D04Tem+8xVcz6ajZlGCs49mQgyg= -github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -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/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/esimonov/ifshort v1.0.4 h1:6SID4yGWfRae/M7hkVDVVyppy8q/v9OuxNdmjLQStBA= -github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= -github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= -github.com/ethereum/go-ethereum v1.11.5 h1:3M1uan+LAUvdn+7wCEFrcMM4LJTeuxDrPTg/f31a5QQ= -github.com/ethereum/go-ethereum v1.11.5/go.mod h1:it7x0DWnTDMfVFdXcU6Ti4KEFQynLHVRarcSlPr0HBo= -github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= -github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= -github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= -github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= -github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -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/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= -github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= -github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= -github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= -github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= -github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= -github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= -github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= -github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= -github.com/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/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= -github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= -github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= -github.com/go-critic/go-critic v0.7.0 h1:tqbKzB8pqi0NsRZ+1pyU4aweAF7A7QN0Pi4Q02+rYnQ= -github.com/go-critic/go-critic v0.7.0/go.mod h1:moYzd7GdVXE2C2hYTwd7h0CPcqlUeclsyBRwMa38v64= -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/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= -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.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-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.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -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/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI= -github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA= -github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= -github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= -github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= -github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= -github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= -github.com/go-toolsmith/astequal v1.1.0 h1:kHKm1AWqClYn15R0K1KKE4RG614D46n+nqUQ06E1dTw= -github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= -github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco= -github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= -github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= -github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= -github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk= -github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= -github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= -github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= -github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= -github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= -github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U= -github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee 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/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= -github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -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 v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/gateway v1.1.0 h1:u0SuhL9+Il+UbjM9VIE3ntfRujKbvVpFvNB4HbjeVQ0= -github.com/gogo/gateway v1.1.0/go.mod h1:S7rR8FRQyG3QFESeSv4l2WnsyzlCLG0CzBbUUo/mbic= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/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 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= -github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/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.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0= -github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= -github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= -github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= -github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe h1:6RGUuS7EGotKx6J5HIP8ZtyMdiDscjMLfRBSPuzVVeo= -github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe/go.mod h1:gjqyPShc/m8pEMpk0a3SeagVb0kaqvhscv+i9jI5ZhQ= -github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2 h1:amWTbTGqOZ71ruzrdA+Nx5WA3tV1N0goTspwmKCQvBY= -github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2/go.mod h1:9wOXstvyDRshQ9LggQuzBCGysxs3b6Uo/1MvYCR2NMs= -github.com/golangci/golangci-lint v1.52.2 h1:FrPElUUI5rrHXg1mQ7KxI1MXPAw5lBVskiz7U7a8a1A= -github.com/golangci/golangci-lint v1.52.2/go.mod h1:S5fhC5sHM5kE22/HcATKd1XLWQxX+y7mHj8B5H91Q/0= -github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= -github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= -github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= -github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= -github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= -github.com/golangci/misspell v0.4.0 h1:KtVB/hTK4bbL/S6bs64rYyk8adjmh1BygbBiaAiX+a0= -github.com/golangci/misspell v0.4.0/go.mod h1:W6O/bwV6lGDxUCChm2ykw9NQdd5bYd1Xkjo88UcWyJc= -github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 h1:DIPQnGy2Gv2FSA4B/hh8Q7xx3B7AIDk3DAMeHclH1vQ= -github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6/go.mod h1:0AKcRCkMoKvUvlf89F6O7H2LYdhr1zBh736mBItOdRs= -github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys= -github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= -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 v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/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/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.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= -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 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= -github.com/google/martian/v3 v3.2.1/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= -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.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/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 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg= -github.com/googleapis/enterprise-certificate-proxy v0.2.1/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 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= -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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 h1:9alfqbrhuD+9fLZ4iaAVwhlp5PEhmnBt7yvK2Oy5C1U= -github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= -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.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -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/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= -github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= -github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= -github.com/gostaticanalysis/comment v1.4.2 h1:hlnx5+S2fY9Zo9ePo4AhgYsYHbM2+eAv8m/s1JiCd6Q= -github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= -github.com/gostaticanalysis/forcetypeassert v0.1.0 h1:6eUflI3DiGusXGK6X7cCcIgVCpZ2CiZ1Q7jl6ZxNV70= -github.com/gostaticanalysis/forcetypeassert v0.1.0/go.mod h1:qZEedyP/sY1lTGV1uJ3VhWZ2mqag3IkWsDHVbplHXak= -github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= -github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= -github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= -github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY= -github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= -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-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= -github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= -github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= -github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is= -github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= -github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= -github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= -github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= -github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -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-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-getter v1.7.0 h1:bzrYP+qu/gMrL1au7/aDvkoOVGUJpeKBgbqRHACAFDY= -github.com/hashicorp/go-getter v1.7.0/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= -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-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -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-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= -github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= -github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 h1:aSVUgRRRtOrZOC1fYmY9gV0e9z/Iu+xNVSASWjsuyGU= -github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3/go.mod h1:5PC6ZNPde8bBqU/ewGZig35+UIZtw9Ytxez8/q5ZyFE= -github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= -github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= -github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= -github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= -github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= -github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= -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/ignite/cli v0.26.1 h1:T4qMjM9H38JOBsgCruilGcsfrlDGHO2K1V88gIe0ubs= -github.com/ignite/cli v0.26.1/go.mod h1:0BQcJCseK0O5RG8HYP/lvVTFbZQjkw+AY8B+wDklj38= -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= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= -github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= -github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= -github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= -github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= -github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= -github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= -github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= -github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= -github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= -github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= -github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= -github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jgautheron/goconst v1.5.1 h1:HxVbL1MhydKs8R8n/HE5NPvzfaYmQJA3o879lE4+WcM= -github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= -github.com/jhump/protoreflect v1.12.1-0.20220721211354-060cc04fc18b h1:izTof8BKh/nE1wrKOrloNA5q4odOarjf+Xpe+4qow98= -github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= -github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= -github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48= -github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= -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/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= -github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= -github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/junk1tm/musttag v0.5.0 h1:bV1DTdi38Hi4pG4OVWa7Kap0hi0o7EczuK6wQt9zPOM= -github.com/junk1tm/musttag v0.5.0/go.mod h1:PcR7BA+oREQYvHwgjIDmw3exJeds5JzRcvEJTfjrA0M= -github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo= -github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA= -github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= -github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/errcheck v1.6.3 h1:dEKh+GLHcWm2oN34nMvDzn1sqI0i0WxPvrgiJA5JuM8= -github.com/kisielk/errcheck v1.6.3/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw= -github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kkHAIKE/contextcheck v1.1.4 h1:B6zAaLhOEEcjvUgIYEqystmnFk1Oemn8bvJhbt0GMb8= -github.com/kkHAIKE/contextcheck v1.1.4/go.mod h1:1+i/gWqokIa+dm31mqGLZhZJ7Uh44DJGZVmr6QRBNJg= -github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= -github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= -github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= -github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/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.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.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -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/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= -github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= -github.com/kunwardeep/paralleltest v1.0.6 h1:FCKYMF1OF2+RveWlABsdnmsvJrei5aoyZoaGS+Ugg8g= -github.com/kunwardeep/paralleltest v1.0.6/go.mod h1:Y0Y0XISdZM5IKm3TREQMZ6iteqn1YuwCsJO/0kL9Zes= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ= -github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA= -github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= -github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= -github.com/ldez/gomoddirectives v0.2.3 h1:y7MBaisZVDYmKvt9/l1mjNCiSA1BVn34U0ObUcJwlhA= -github.com/ldez/gomoddirectives v0.2.3/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0= -github.com/ldez/tagliatelle v0.4.0 h1:sylp7d9kh6AdXN2DpVGHBRb5guTVAgOxqNGhbqc4b1c= -github.com/ldez/tagliatelle v0.4.0/go.mod h1:mNtTfrHy2haaBAw+VT7IBV6VXBThS7TCreYWbBcJ87I= -github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4= -github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ= -github.com/leonklingele/grouper v1.1.1 h1:suWXRU57D4/Enn6pXR0QVqqWWrnJ9Osrz+5rjt8ivzU= -github.com/leonklingele/grouper v1.1.1/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= -github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= -github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= -github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= -github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/lucasjones/reggen v0.0.0-20180717132126-cdb49ff09d77/go.mod h1:5ELEyG+X8f+meRWHuqUOewBOhvHkl7M76pdGEansxW4= -github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM= -github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= -github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -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/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= -github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= -github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= -github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= -github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= -github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= -github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 h1:gWg6ZQ4JhDfJPqlo2srm/LN17lpybq15AryXIRcWYLE= -github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= -github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= -github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12/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.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -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-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.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.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwgOdMUQePUo= -github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= -github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 h1:zpIH83+oKzcpryru8ceC6BxnoG8TBrhgAvRg8obzup0= -github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= -github.com/mgechev/revive v1.3.1 h1:OlQkcH40IB2cGuprTPcjB0iIUddgVZgGmDX3IAMR8D4= -github.com/mgechev/revive v1.3.1/go.mod h1:YlD6TTWl2B8A103R9KWJSPVI9DrEf+oqr15q21Ld+5I= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -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/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= -github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= -github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.3/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/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= -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/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= -github.com/moricho/tparallel v0.3.1 h1:fQKD4U1wRMAYNngDonW5XupoB/ZGJHdpzrWqgyg9krA= -github.com/moricho/tparallel v0.3.1/go.mod h1:leENX2cUv7Sv2qDgdi0D0fCftN8fRC67Bcn8pqzeYNI= -github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= -github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= -github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76/go.mod h1:x5OoJHDHqxHS801UIuhqGl6QdSAEJvtausosHSdazIo= -github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= -github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= -github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= -github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= -github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA= -github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= -github.com/neilotoole/errgroup v0.1.6/go.mod h1:Q2nLGf+594h0CLBs/Mbg6qOr7GtqDK7C2S41udRnToE= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nishanths/exhaustive v0.9.5 h1:TzssWan6orBiLYVqewCG8faud9qlFntJE30ACpzmGME= -github.com/nishanths/exhaustive v0.9.5/go.mod h1:IbwrGdVMizvDcIxPYGVdQn5BqWJaOwpCvg4RGb8r/TA= -github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= -github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/notional-labs/wasmd v0.29.0-sdk46 h1:KaGpIdDOdDbWoHp0ZUzIdSGgUqe03Mach4zpEAznXls= -github.com/notional-labs/wasmd v0.29.0-sdk46/go.mod h1:NbER19ksI5bZOJUta8lN4wrkVYBKD9VzOFjoCgL4P3o= -github.com/nunnatsa/ginkgolinter v0.9.0 h1:Sm0zX5QfjJzkeCjEp+t6d3Ha0jwvoDjleP9XCsrEzOA= -github.com/nunnatsa/ginkgolinter v0.9.0/go.mod h1:FHaMLURXP7qImeH6bvxWJUpyH+2tuqe5j4rW1gxJRmI= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= -github.com/onsi/ginkgo/v2 v2.8.0 h1:pAM+oBNPrpXRs+E/8spkeGx9QgekbRVyr74EUvRVOUI= -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.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= -github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= -github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= -github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= -github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= -github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= -github.com/otiai10/copy v1.9.0 h1:7KFNiCgZ91Ru4qW4CWPf/7jqtxLagGRmIxWldPP9VY4= -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= -github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= -github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= -github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= -github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= -github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= -github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= -github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= -github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= -github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v1.4.0 h1:b+sQ5HibPIAjEZwtuwU8Wz/u0dMZ7YL+bk+9yWyHVJk= -github.com/polyfloyd/go-errorlint v1.4.0/go.mod h1:qJCkPeBn+0EXkdKTrUCcuFStM2xrDKfxI3MGLXPexUs= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= -github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.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.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/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.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= -github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= -github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.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.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -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/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/quasilyte/go-ruleguard v0.3.19 h1:tfMnabXle/HzOb5Xe9CUZYWXKfkS1KwRmZyPmD9nVcc= -github.com/quasilyte/go-ruleguard v0.3.19/go.mod h1:lHSn69Scl48I7Gt9cX3VrbsZYvYiBYszZOZW4A+oTEw= -github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= -github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= -github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= -github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= -github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= -github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= -github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= -github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/regen-network/cosmos-proto v0.3.1 h1:rV7iM4SSFAagvy8RiyhiACbWEGotmqzywPxOvwMdxcg= -github.com/regen-network/cosmos-proto v0.3.1/go.mod h1:jO0sVX6a1B36nmE8C9xBFXpNwWejXC7QqCOnH3O0+YM= -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/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= -github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -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/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= -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/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ= -github.com/ryancurrah/gomodguard v1.3.0 h1:q15RT/pd6UggBXVBuLps8BXRvl5GPBcwVA7BJHMLuTw= -github.com/ryancurrah/gomodguard v1.3.0/go.mod h1:ggBxb3luypPEzqVtq33ee7YSN35V28XeGnid8dnni50= -github.com/ryanrolds/sqlclosecheck v0.4.0 h1:i8SX60Rppc1wRuyQjMciLqIzV3xnoHB7/tXbr6RGYNI= -github.com/ryanrolds/sqlclosecheck v0.4.0/go.mod h1:TBRRjzL31JONc9i4XMinicuo+s+E8yKZ5FN8X3G6CKQ= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= -github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc= -github.com/sanposhiho/wastedassign/v2 v2.0.7/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= -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/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= -github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= -github.com/sashamelentyev/usestdlibvars v1.23.0 h1:01h+/2Kd+NblNItNeux0veSL5cBF1jbEOPrEhDzGYq0= -github.com/sashamelentyev/usestdlibvars v1.23.0/go.mod h1:YPwr/Y1LATzHI93CqoPUN/2BzGQ/6N/cl/KwgR0B/aU= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/securego/gosec/v2 v2.15.0 h1:v4Ym7FF58/jlykYmmhZ7mTm7FQvN/setNm++0fgIAtw= -github.com/securego/gosec/v2 v2.15.0/go.mod h1:VOjTrZOkUtSDt2QLSJmQBMWnvwiQPEjg0l+5juIqGk8= -github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= -github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= -github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= -github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= -github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sivchari/containedctx v1.0.2 h1:0hLQKpgC53OVF1VT7CeoFHk9YKstur1XOgfYIc1yrHI= -github.com/sivchari/containedctx v1.0.2/go.mod h1:PwZOeqm4/DLoJOqMSIJs3aKqXRX4YO+uXww087KZ7Bw= -github.com/sivchari/nosnakecase v1.7.0 h1:7QkpWIRMe8x25gckkFd2A5Pi6Ymo0qgr4JrhGt95do8= -github.com/sivchari/nosnakecase v1.7.0/go.mod h1:CwDzrzPea40/GB6uynrNLiorAlgFRvRbFSgJx2Gs+QY= -github.com/sivchari/tenv v1.7.1 h1:PSpuD4bu6fSmtWMxSGWcvqUUgIn7k3yOJhOIzVWn8Ak= -github.com/sivchari/tenv v1.7.1/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg= -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/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/sonatard/noctx v0.0.2 h1:L7Dz4De2zDQhW8S0t+KUjY0MAQJd6SgVwhzNIc4ok00= -github.com/sonatard/noctx v0.0.2/go.mod h1:kzFz+CzWSjQ2OzIm46uJZoXuBpa2+0y3T36U18dWqIo= -github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= -github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= -github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -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.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= -github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -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.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= -github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= -github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= -github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= -github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= -github.com/stbenjam/no-sprintf-host-port v0.1.1 h1:tYugd/yrm1O0dV+ThCbaKZh195Dfm07ysF0U6JQXczc= -github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8LHsN9N74I+PhRquPsxpL0I= -github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= -github.com/stretchr/objx v0.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.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.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.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.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= -github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c h1:+aPplBwWcHBo6q9xrfWdMrT9o4kltkmmvpemgIjep/8= -github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c/go.mod h1:SbErYREK7xXdsRiigaQiQkI9McGRzYMvlKYaP3Nimdk= -github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9qkHM= -github.com/tdakkota/asciicheck v0.2.0/go.mod h1:Qb7Y9EgjCLJGup51gDHFzbI08/gbGhL/UVhYIPWG2rg= -github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok= -github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= -github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= -github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= -github.com/tendermint/tm-db v0.6.7 h1:fE00Cbl0jayAoqlExN6oyQJ7fR/ZtoVOmvPJ//+shu8= -github.com/tendermint/tm-db v0.6.7/go.mod h1:byQDzFkZV1syXr/ReXS808NxA2xvyuuVgXOJ/088L6I= -github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= -github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= -github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= -github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= -github.com/tetafro/godot v1.4.11 h1:BVoBIqAf/2QdbFmSwAWnaIqDivZdOV0ZRwEm6jivLKw= -github.com/tetafro/godot v1.4.11/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8= -github.com/tidwall/btree v1.5.0 h1:iV0yVY/frd7r6qGBXfEYs7DH0gTDgrKTrDjS7xt/IyQ= -github.com/tidwall/btree v1.5.0/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYmsObdKE= -github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM= -github.com/timakin/bodyclose v0.0.0-20221125081123-e39cf3fc478e h1:MV6KaVu/hzByHP0UvJ4HcMGE/8a6A4Rggc/0wx2AvJo= -github.com/timakin/bodyclose v0.0.0-20221125081123-e39cf3fc478e/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= -github.com/timonwong/loggercheck v0.9.4 h1:HKKhqrjcVj8sxL7K77beXh0adEm6DLjV/QOGeMXEVi4= -github.com/timonwong/loggercheck v0.9.4/go.mod h1:caz4zlPcgvpEkXgVnAJGowHAMW2NwHaNlpS8xDbVhTg= -github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= -github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= -github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= -github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tomarrell/wrapcheck/v2 v2.8.1 h1:HxSqDSN0sAt0yJYsrcYVoEeyM4aI9yAm3KQpIXDJRhQ= -github.com/tomarrell/wrapcheck/v2 v2.8.1/go.mod h1:/n2Q3NZ4XFT50ho6Hbxg+RV1uyo2Uow/Vdm9NQcl5SE= -github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= -github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= -github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= -github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= -github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= -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 v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -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/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= -github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ultraware/funlen v0.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iLA= -github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= -github.com/ultraware/whitespace v0.0.5 h1:hh+/cpIcopyMYbZNVov9iSxvJU3OYQg78Sfaqzi/CzI= -github.com/ultraware/whitespace v0.0.5/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= -github.com/umee-network/Gravity-Bridge/module v1.5.3-umee-4 h1:6uF05P2w+ueKvBNqAh2GdVt8WFeCakVPzZU/LNwnEAY= -github.com/umee-network/Gravity-Bridge/module v1.5.3-umee-4/go.mod h1:qf/p/AIVfuGzBg2bYx1yg4PbHhazGfdqnm6j1BKZ62M= -github.com/umee-network/bech32-ibc v0.3.2 h1:T2QgfMVktA9iAYLYmhmcukJr5qoaEJgRLDoUtHK8CzE= -github.com/umee-network/bech32-ibc v0.3.2/go.mod h1:kbT1K+mxxzDkvmcsHtBXgM2meNv6AXroxPdSeWAcZE0= -github.com/umee-network/cosmos-sdk v0.46.11-umee h1:XzoK7WFw6FcqGJSQUrRYAyrU3lcuKry6A4W24Uv9cWc= -github.com/umee-network/cosmos-sdk v0.46.11-umee/go.mod h1:bG4AkW9bqc8ycrryyKGQEl3YV9BY2wr6HggGq8kvcgM= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/uudashr/gocognit v1.0.6 h1:2Cgi6MweCsdB6kpcVQp7EW4U23iBFQWfTXiWlyp842Y= -github.com/uudashr/gocognit v1.0.6/go.mod h1:nAIUuVBnYU7pcninia3BHOvQkpQCeO76Uscky5BOwcY= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= -github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= -github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= -github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE= -github.com/yeya24/promlinter v0.2.0 h1:xFKDQ82orCU5jQujdaD8stOHiv8UN68BSdn2a8u8Y3o= -github.com/yeya24/promlinter v0.2.0/go.mod h1:u54lkmBOZrpEbQQ6gox2zWKKLKu2SGe+2KOiextY+IA= -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/zondax/hid v0.9.1 h1:gQe66rtmyZ8VeGFcOpbuH3r7erYtNEAezCAYu8LdkJo= -github.com/zondax/hid v0.9.1/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= -github.com/zondax/ledger-go v0.14.1 h1:Pip65OOl4iJ84WTpA4BKChvOufMhhbxED3BaihoZN4c= -github.com/zondax/ledger-go v0.14.1/go.mod h1:fZ3Dqg6qcdXWSOJFKMG8GCTnD7slO/RL2feOQv8K320= -gitlab.com/bosi/decorder v0.2.3 h1:gX4/RgK16ijY8V+BRQHAySfQAb354T7/xQpDB2n10P0= -gitlab.com/bosi/decorder v0.2.3/go.mod h1:9K1RB5+VPNQYtXtTDAzd2OEftsZb1oV0IrJrzChSdGE= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= -go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.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/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -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.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.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= -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.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -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= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -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.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -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-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-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= -golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= -golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2 h1:J74nGeMgeFnYQJN59eFwh06jX/V8g0lB7LWpjSLxtgU= -golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-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.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= -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-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-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-20190813141303-74dc4d7220e7/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-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-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-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/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-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.3.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.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -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-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= -golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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-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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190130150945-aca44879d564/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-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-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-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-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-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-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-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-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-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-20210809222454-d867a43fc93e/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-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/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-20220702020025-31831981b65f/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-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 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-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 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -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.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 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-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-20190321232350-e250d351ecad/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-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-20190910044552-dd2b5c81c578/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-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-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-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-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-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -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-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -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-20200820010801-b793a1359eac/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-20201001104356-43ebab892c4c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= -golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/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-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= -golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= -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.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= -golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= -golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= -golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= -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 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -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.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= -gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.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.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.107.0 h1:I2SlFjD8ZWabaIFOfeEDg3pf0BHJDh6iYQ1ic3Yu/UU= -google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.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-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-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-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= -google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-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-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-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-20210126160654-44e461bb6506/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-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-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-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa h1:qQPhfbPO23fwm/9lQr91L1u62Zo6cm+zI+slZT+uf+o= -google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.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.32.0/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.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.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= -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 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.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-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= -gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= -gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -honnef.co/go/tools v0.4.3 h1:o/n5/K5gXqk8Gozvs2cnL0F2S1/g1vcGCAx2vETjITw= -honnef.co/go/tools v0.4.3/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA= -mvdan.cc/gofumpt v0.4.0 h1:JVf4NN1mIpHogBj7ABpgOyZc65/UUOkKQFkoURsz4MM= -mvdan.cc/gofumpt v0.4.0/go.mod h1:PljLOHDeZqgS8opHRKLzp2It2VBuSdteAgqUfzMTxlQ= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= -mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d h1:3rvTIIM22r9pvXk+q3swxUQAQOxksVMGK7sml4nG57w= -mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d/go.mod h1:IeHQjmn6TOD+e4Z3RFiZMMsLVL+A96Nvptar8Fj71is= -nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k= -nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= -pgregory.net/rapid v0.5.5 h1:jkgx1TjbQPD/feRoK+S/mXw9e1uj6WilpHrXJowi6oA= -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= -rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= -sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/price-feeder/main.go b/price-feeder/main.go deleted file mode 100644 index ab84796cc4..0000000000 --- a/price-feeder/main.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -import ( - "github.com/umee-network/umee/price-feeder/v2/cmd" -) - -func main() { - cmd.Execute() -} diff --git a/price-feeder/oracle/client/chain_height.go b/price-feeder/oracle/client/chain_height.go deleted file mode 100644 index 0560f59589..0000000000 --- a/price-feeder/oracle/client/chain_height.go +++ /dev/null @@ -1,112 +0,0 @@ -package client - -import ( - "context" - "errors" - "fmt" - "sync" - - "github.com/rs/zerolog" - tmrpcclient "github.com/tendermint/tendermint/rpc/client" - tmctypes "github.com/tendermint/tendermint/rpc/core/types" - tmtypes "github.com/tendermint/tendermint/types" -) - -var ( - errParseEventDataNewBlockHeader = errors.New("error parsing EventDataNewBlockHeader") - queryEventNewBlockHeader = tmtypes.QueryForEvent(tmtypes.EventNewBlockHeader) -) - -// ChainHeight is used to cache the chain height of the -// current node which is being updated each time the -// node sends an event of EventNewBlockHeader. -// It starts a goroutine to subscribe to blockchain new block event and update the cached height. -type ChainHeight struct { - Logger zerolog.Logger - - mtx sync.RWMutex - errGetChainHeight error - lastChainHeight int64 -} - -// NewChainHeight returns a new ChainHeight struct that -// starts a new goroutine subscribed to EventNewBlockHeader. -func NewChainHeight( - ctx context.Context, - rpcClient tmrpcclient.Client, - logger zerolog.Logger, - initialHeight int64, -) (*ChainHeight, error) { - if initialHeight < 1 { - return nil, fmt.Errorf("expected positive initial block height") - } - - if !rpcClient.IsRunning() { - if err := rpcClient.Start(); err != nil { - return nil, err - } - } - - newBlockHeaderSubscription, err := rpcClient.Subscribe( - ctx, tmtypes.EventNewBlockHeader, queryEventNewBlockHeader.String()) - if err != nil { - return nil, err - } - - chainHeight := &ChainHeight{ - Logger: logger.With().Str("oracle_client", "chain_height").Logger(), - errGetChainHeight: nil, - lastChainHeight: initialHeight, - } - - go chainHeight.subscribe(ctx, rpcClient, newBlockHeaderSubscription) - - return chainHeight, nil -} - -// updateChainHeight receives the data to be updated thread safe. -func (chainHeight *ChainHeight) updateChainHeight(blockHeight int64, err error) { - chainHeight.mtx.Lock() - defer chainHeight.mtx.Unlock() - - chainHeight.lastChainHeight = blockHeight - chainHeight.errGetChainHeight = err -} - -// subscribe listens to new blocks being made -// and updates the chain height. -func (chainHeight *ChainHeight) subscribe( - ctx context.Context, - eventsClient tmrpcclient.EventsClient, - newBlockHeaderSubscription <-chan tmctypes.ResultEvent, -) { - for { - select { - case <-ctx.Done(): - err := eventsClient.Unsubscribe(ctx, tmtypes.EventNewBlockHeader, queryEventNewBlockHeader.String()) - if err != nil { - chainHeight.Logger.Err(err) - chainHeight.updateChainHeight(chainHeight.lastChainHeight, err) - } - chainHeight.Logger.Info().Msg("closing the ChainHeight subscription") - return - - case resultEvent := <-newBlockHeaderSubscription: - eventDataNewBlockHeader, ok := resultEvent.Data.(tmtypes.EventDataNewBlockHeader) - if !ok { - chainHeight.Logger.Err(errParseEventDataNewBlockHeader) - chainHeight.updateChainHeight(chainHeight.lastChainHeight, errParseEventDataNewBlockHeader) - continue - } - chainHeight.updateChainHeight(eventDataNewBlockHeader.Header.Height, nil) - } - } -} - -// GetChainHeight returns the last chain height available. -func (chainHeight *ChainHeight) GetChainHeight() (int64, error) { - chainHeight.mtx.RLock() - defer chainHeight.mtx.RUnlock() - - return chainHeight.lastChainHeight, chainHeight.errGetChainHeight -} diff --git a/price-feeder/oracle/client/client.go b/price-feeder/oracle/client/client.go deleted file mode 100644 index d3c0a28625..0000000000 --- a/price-feeder/oracle/client/client.go +++ /dev/null @@ -1,282 +0,0 @@ -package client - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - "os" - "time" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/client/rpc" - "github.com/cosmos/cosmos-sdk/client/tx" - "github.com/cosmos/cosmos-sdk/crypto/keyring" - "github.com/cosmos/cosmos-sdk/telemetry" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/tx/signing" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/rs/zerolog" - rpchttp "github.com/tendermint/tendermint/rpc/client/http" - tmjsonclient "github.com/tendermint/tendermint/rpc/jsonrpc/client" - umeeapp "github.com/umee-network/umee/v4/app" - umeeparams "github.com/umee-network/umee/v4/app/params" -) - -type ( - // OracleClient defines a structure that interfaces with the Umee node. - OracleClient struct { - Logger zerolog.Logger - ChainID string - KeyringBackend string - KeyringDir string - KeyringPass string - TMRPC string - RPCTimeout time.Duration - OracleAddr sdk.AccAddress - OracleAddrString string - ValidatorAddr sdk.ValAddress - ValidatorAddrString string - Encoding umeeparams.EncodingConfig - GasPrices string - GasAdjustment float64 - GRPCEndpoint string - KeyringPassphrase string - ChainHeight *ChainHeight - } - - passReader struct { - pass string - buf *bytes.Buffer - } -) - -func NewOracleClient( - ctx context.Context, - logger zerolog.Logger, - chainID string, - keyringBackend string, - keyringDir string, - keyringPass string, - tmRPC string, - rpcTimeout time.Duration, - oracleAddrString string, - validatorAddrString string, - grpcEndpoint string, - gasAdjustment float64, -) (OracleClient, error) { - oracleAddr, err := sdk.AccAddressFromBech32(oracleAddrString) - if err != nil { - return OracleClient{}, err - } - - oracleClient := OracleClient{ - Logger: logger.With().Str("module", "oracle_client").Logger(), - ChainID: chainID, - KeyringBackend: keyringBackend, - KeyringDir: keyringDir, - KeyringPass: keyringPass, - TMRPC: tmRPC, - RPCTimeout: rpcTimeout, - OracleAddr: oracleAddr, - OracleAddrString: oracleAddrString, - ValidatorAddr: sdk.ValAddress(validatorAddrString), - ValidatorAddrString: validatorAddrString, - Encoding: umeeapp.MakeEncodingConfig(), - GasAdjustment: gasAdjustment, - GRPCEndpoint: grpcEndpoint, - } - - clientCtx, err := oracleClient.CreateClientContext() - if err != nil { - return OracleClient{}, err - } - - blockHeight, err := rpc.GetChainHeight(clientCtx) - if err != nil { - return OracleClient{}, err - } - - chainHeight, err := NewChainHeight( - ctx, - clientCtx.Client, - oracleClient.Logger, - blockHeight, - ) - if err != nil { - return OracleClient{}, err - } - oracleClient.ChainHeight = chainHeight - - return oracleClient, nil -} - -func newPassReader(pass string) io.Reader { - return &passReader{ - pass: pass, - buf: new(bytes.Buffer), - } -} - -func (r *passReader) Read(p []byte) (n int, err error) { - n, err = r.buf.Read(p) - if err == io.EOF || n == 0 { - r.buf.WriteString(r.pass + "\n") - - n, err = r.buf.Read(p) - } - - return n, err -} - -// BroadcastTx attempts to broadcast a signed transaction. If it fails, a few re-attempts -// will be made until the transaction succeeds or ultimately times out or fails. -// Ref: https://github.com/terra-money/oracle-feeder/blob/baef2a4a02f57a2ffeaa207932b2e03d7fb0fb25/feeder/src/vote.ts#L230 -func (oc OracleClient) BroadcastTx(nextBlockHeight, timeoutHeight int64, msgs ...sdk.Msg) error { - maxBlockHeight := nextBlockHeight + timeoutHeight - lastCheckHeight := nextBlockHeight - 1 - - clientCtx, err := oc.CreateClientContext() - if err != nil { - return err - } - - factory, err := oc.CreateTxFactory() - if err != nil { - return err - } - - // re-try voting until timeout - for lastCheckHeight < maxBlockHeight { - latestBlockHeight, err := oc.ChainHeight.GetChainHeight() - if err != nil { - return err - } - - if latestBlockHeight <= lastCheckHeight { - continue - } - - // set last check height to latest block height - lastCheckHeight = latestBlockHeight - - resp, err := BroadcastTx(clientCtx, factory, msgs...) - if resp != nil && resp.Code != 0 { - telemetry.IncrCounter(1, "failure", "tx", "code") - err = fmt.Errorf("invalid response code from tx: %d", resp.Code) - } - if err != nil { - var ( - code uint32 - hash string - ) - if resp != nil { - code = resp.Code - hash = resp.TxHash - } - - oc.Logger.Debug(). - Err(err). - Int64("max_height", maxBlockHeight). - Int64("last_check_height", lastCheckHeight). - Str("tx_hash", hash). - Uint32("tx_code", code). - Msg("failed to broadcast tx; retrying...") - - time.Sleep(time.Second * 1) - continue - } - - oc.Logger.Info(). - Uint32("tx_code", resp.Code). - Str("tx_hash", resp.TxHash). - Int64("tx_height", resp.Height). - Msg("successfully broadcasted tx") - - return nil - } - - telemetry.IncrCounter(1, "failure", "tx", "timeout") - return errors.New("broadcasting tx timed out") -} - -// CreateClientContext creates an SDK client Context instance used for transaction -// generation, signing and broadcasting. -func (oc OracleClient) CreateClientContext() (client.Context, error) { - var keyringInput io.Reader - if len(oc.KeyringPass) > 0 { - keyringInput = newPassReader(oc.KeyringPass) - } else { - keyringInput = os.Stdin - } - - kr, err := keyring.New("oracle", oc.KeyringBackend, oc.KeyringDir, keyringInput, oc.Encoding.Codec) - if err != nil { - return client.Context{}, err - } - - httpClient, err := tmjsonclient.DefaultHTTPClient(oc.TMRPC) - if err != nil { - return client.Context{}, err - } - - httpClient.Timeout = oc.RPCTimeout - - tmRPC, err := rpchttp.NewWithClient(oc.TMRPC, "/websocket", httpClient) - if err != nil { - return client.Context{}, err - } - - keyInfo, err := kr.KeyByAddress(oc.OracleAddr) - if err != nil { - return client.Context{}, err - } - clientCtx := client.Context{ - ChainID: oc.ChainID, - InterfaceRegistry: oc.Encoding.InterfaceRegistry, - Output: os.Stderr, - BroadcastMode: flags.BroadcastSync, - TxConfig: oc.Encoding.TxConfig, - AccountRetriever: authtypes.AccountRetriever{}, - Codec: oc.Encoding.Codec, - LegacyAmino: oc.Encoding.Amino, - Input: os.Stdin, - NodeURI: oc.TMRPC, - Client: tmRPC, - Keyring: kr, - FromAddress: oc.OracleAddr, - FromName: keyInfo.Name, - From: keyInfo.Name, - OutputFormat: "json", - UseLedger: false, - Simulate: false, - GenerateOnly: false, - Offline: false, - SkipConfirm: true, - } - - return clientCtx, nil -} - -// CreateTxFactory creates an SDK Factory instance used for transaction -// generation, signing and broadcasting. -func (oc OracleClient) CreateTxFactory() (tx.Factory, error) { - clientCtx, err := oc.CreateClientContext() - if err != nil { - return tx.Factory{}, err - } - - txFactory := tx.Factory{}. - WithAccountRetriever(clientCtx.AccountRetriever). - WithChainID(oc.ChainID). - WithTxConfig(clientCtx.TxConfig). - WithGasAdjustment(oc.GasAdjustment). - WithGasPrices(oc.GasPrices). - WithKeybase(clientCtx.Keyring). - WithSignMode(signing.SignMode_SIGN_MODE_DIRECT). - WithSimulateAndExecute(true) - - return txFactory, nil -} diff --git a/price-feeder/oracle/client/tx.go b/price-feeder/oracle/client/tx.go deleted file mode 100644 index 3f49db777e..0000000000 --- a/price-feeder/oracle/client/tx.go +++ /dev/null @@ -1,78 +0,0 @@ -package client - -import ( - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/tx" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// BroadcastTx attempts to generate, sign and broadcast a transaction with the -// given set of messages. It will also simulate gas requirements if necessary. -// It will return an error upon failure. -// -// Note, BroadcastTx is copied from the SDK except it removes a few unnecessary -// things like prompting for confirmation and printing the response. Instead, -// we return the TxResponse. -func BroadcastTx(clientCtx client.Context, txf tx.Factory, msgs ...sdk.Msg) (*sdk.TxResponse, error) { - txf, err := prepareFactory(clientCtx, txf) - if err != nil { - return nil, err - } - - _, adjusted, err := tx.CalculateGas(clientCtx, txf, msgs...) - if err != nil { - return nil, err - } - - // make sure gas in enough to execute the txs - txf = txf.WithGas(adjusted * 3 / 2) - - unsignedTx, err := txf.BuildUnsignedTx(msgs...) - if err != nil { - return nil, err - } - - unsignedTx.SetFeeGranter(clientCtx.GetFeeGranterAddress()) - // unsignedTx.SetFeePayer(clientCtx.GetFeePayerAddress()) - - if err = tx.Sign(txf, clientCtx.GetFromName(), unsignedTx, true); err != nil { - return nil, err - } - - txBytes, err := clientCtx.TxConfig.TxEncoder()(unsignedTx.GetTx()) - if err != nil { - return nil, err - } - - return clientCtx.BroadcastTx(txBytes) -} - -// prepareFactory ensures the account defined by ctx.GetFromAddress() exists and -// if the account number and/or the account sequence number are zero (not set), -// they will be queried for and set on the provided Factory. A new Factory with -// the updated fields will be returned. -func prepareFactory(clientCtx client.Context, txf tx.Factory) (tx.Factory, error) { - from := clientCtx.GetFromAddress() - - if err := txf.AccountRetriever().EnsureExists(clientCtx, from); err != nil { - return txf, err - } - - initNum, initSeq := txf.AccountNumber(), txf.Sequence() - if initNum == 0 || initSeq == 0 { - num, seq, err := txf.AccountRetriever().GetAccountNumberSequence(clientCtx, from) - if err != nil { - return txf, err - } - - if initNum == 0 { - txf = txf.WithAccountNumber(num) - } - - if initSeq == 0 { - txf = txf.WithSequence(seq) - } - } - - return txf, nil -} diff --git a/price-feeder/oracle/convert.go b/price-feeder/oracle/convert.go deleted file mode 100644 index 03b142dc9d..0000000000 --- a/price-feeder/oracle/convert.go +++ /dev/null @@ -1,214 +0,0 @@ -package oracle - -import ( - "fmt" - "strings" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/rs/zerolog" - "github.com/umee-network/umee/price-feeder/v2/config" - "github.com/umee-network/umee/price-feeder/v2/oracle/provider" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -// getUSDBasedProviders retrieves which providers for an asset have a USD-based pair, -// given the asset and the map of providers to currency pairs. -func getUSDBasedProviders( - asset string, - providerPairs map[provider.Name][]types.CurrencyPair, -) (map[provider.Name]struct{}, error) { - conversionProviders := make(map[provider.Name]struct{}) - - for provider, pairs := range providerPairs { - for _, pair := range pairs { - if strings.ToUpper(pair.Quote) == config.DenomUSD && strings.ToUpper(pair.Base) == asset { - conversionProviders[provider] = struct{}{} - } - } - } - if len(conversionProviders) == 0 { - return nil, fmt.Errorf("no providers have a usd conversion for this asset") - } - - return conversionProviders, nil -} - -// ConvertCandlesToUSD converts any candles which are not quoted in USD -// to USD by other price feeds. It will also filter out any candles not -// within the deviation threshold set by the config. -// -// Ref: https://github.com/umee-network/umee/blob/4348c3e433df8c37dd98a690e96fc275de609bc1/price-feeder/oracle/filter.go#L41 -func ConvertCandlesToUSD( - logger zerolog.Logger, - candles provider.AggregatedProviderCandles, - providerPairs map[provider.Name][]types.CurrencyPair, - deviationThresholds map[string]sdk.Dec, -) (provider.AggregatedProviderCandles, error) { - if len(candles) == 0 { - return candles, nil - } - - conversionRates := make(map[string]sdk.Dec) - requiredConversions := make(map[provider.Name][]types.CurrencyPair) - - for pairProviderName, pairs := range providerPairs { - for _, pair := range pairs { - if strings.ToUpper(pair.Quote) != config.DenomUSD { - // Get valid providers and use them to generate a USD-based price for this asset. - validProviders, err := getUSDBasedProviders(pair.Quote, providerPairs) - if err != nil { - return nil, err - } - - // Find candles which we can use for conversion, and calculate the tvwap - // to find the conversion rate. - validCandleList := provider.AggregatedProviderCandles{} - for providerName, candleSet := range candles { - if _, ok := validProviders[providerName]; ok { - for base, candle := range candleSet { - if base == pair.Quote { - if _, ok := validCandleList[providerName]; !ok { - validCandleList[providerName] = make(map[string][]types.CandlePrice) - } - - validCandleList[providerName][base] = candle - } - } - } - } - - if len(validCandleList) == 0 { - return nil, fmt.Errorf("there are no valid conversion rates for %s", pair.Quote) - } - - filteredCandles, err := FilterCandleDeviations( - logger, - validCandleList, - deviationThresholds, - ) - if err != nil { - return nil, err - } - - // TODO: we should revise ComputeTVWAP to avoid return empty slices - // Ref: https://github.com/umee-network/umee/issues/1261 - tvwap, err := ComputeTVWAP(filteredCandles) - if err != nil { - return nil, err - } - - cvRate, ok := tvwap[pair.Quote] - if !ok { - return nil, fmt.Errorf("error on computing tvwap for quote: %s, base: %s", pair.Quote, pair.Base) - } - - conversionRates[pair.Quote] = cvRate - requiredConversions[pairProviderName] = append(requiredConversions[pairProviderName], pair) - } - } - } - - // Convert assets to USD. - for provider, assetMap := range candles { - for _, requiredConversion := range requiredConversions[provider] { - conversionRate, ok := conversionRates[requiredConversion.Quote] - if !ok { - continue - } - for asset, assetCandles := range assetMap { - if requiredConversion.Base == asset { - for i := range assetCandles { - assetCandles[i].Price = assetCandles[i].Price.Mul( - conversionRate, - ) - } - } - } - } - } - - return candles, nil -} - -// ConvertTickersToUSD converts any tickers which are not quoted in USD to USD, -// using the conversion rates of other tickers. It will also filter out any tickers -// not within the deviation threshold set by the config. -// -// Ref: https://github.com/umee-network/umee/blob/4348c3e433df8c37dd98a690e96fc275de609bc1/price-feeder/oracle/filter.go#L41 -func ConvertTickersToUSD( - logger zerolog.Logger, - tickers provider.AggregatedProviderPrices, - providerPairs map[provider.Name][]types.CurrencyPair, - deviationThresholds map[string]sdk.Dec, -) (provider.AggregatedProviderPrices, error) { - if len(tickers) == 0 { - return tickers, nil - } - - conversionRates := make(map[string]sdk.Dec) - requiredConversions := make(map[provider.Name]types.CurrencyPair) - - for pairProviderName, pairs := range providerPairs { - for _, pair := range pairs { - if strings.ToUpper(pair.Quote) != config.DenomUSD { - // Get valid providers and use them to generate a USD-based price for this asset. - validProviders, err := getUSDBasedProviders(pair.Quote, providerPairs) - if err != nil { - return nil, err - } - - // Find valid candles, and then let's re-compute the tvwap. - validTickerList := provider.AggregatedProviderPrices{} - for providerName, candleSet := range tickers { - // Find tickers which we can use for conversion, and calculate the vwap - // to find the conversion rate. - if _, ok := validProviders[providerName]; ok { - for base, ticker := range candleSet { - if base == pair.Quote { - if _, ok := validTickerList[providerName]; !ok { - validTickerList[providerName] = make(map[string]types.TickerPrice) - } - - validTickerList[providerName][base] = ticker - } - } - } - } - - if len(validTickerList) == 0 { - return nil, fmt.Errorf("there are no valid conversion rates for %s", pair.Quote) - } - - filteredTickers, err := FilterTickerDeviations( - logger, - validTickerList, - deviationThresholds, - ) - if err != nil { - return nil, err - } - - vwap := ComputeVWAP(filteredTickers) - - conversionRates[pair.Quote] = vwap[pair.Quote] - requiredConversions[pairProviderName] = pair - } - } - } - - // Convert assets to USD. - for providerName, assetMap := range tickers { - for asset := range assetMap { - if requiredConversions[providerName].Base == asset { - assetMap[asset] = types.TickerPrice{ - Price: assetMap[asset].Price.Mul( - conversionRates[requiredConversions[providerName].Quote], - ), - Volume: assetMap[asset].Volume, - } - } - } - } - - return tickers, nil -} diff --git a/price-feeder/oracle/convert_test.go b/price-feeder/oracle/convert_test.go deleted file mode 100644 index cd79bbc6e3..0000000000 --- a/price-feeder/oracle/convert_test.go +++ /dev/null @@ -1,269 +0,0 @@ -package oracle - -import ( - "testing" - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" - "github.com/umee-network/umee/price-feeder/v2/oracle/provider" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -var ( - atomPrice = sdk.MustNewDecFromStr("29.93") - atomVolume = sdk.MustNewDecFromStr("894123.00") - usdtPrice = sdk.MustNewDecFromStr("0.98") - usdtVolume = sdk.MustNewDecFromStr("894123.00") - - atomPair = types.CurrencyPair{ - Base: "ATOM", - Quote: "USDT", - } - usdtPair = types.CurrencyPair{ - Base: "USDT", - Quote: "USD", - } -) - -func TestGetUSDBasedProviders(t *testing.T) { - providerPairs := make(map[provider.Name][]types.CurrencyPair, 3) - providerPairs[provider.ProviderCoinbase] = []types.CurrencyPair{ - { - Base: "FOO", - Quote: "USD", - }, - } - providerPairs[provider.ProviderHuobi] = []types.CurrencyPair{ - { - Base: "FOO", - Quote: "USD", - }, - } - providerPairs[provider.ProviderKraken] = []types.CurrencyPair{ - { - Base: "FOO", - Quote: "USDT", - }, - } - providerPairs[provider.ProviderBinance] = []types.CurrencyPair{ - { - Base: "USDT", - Quote: "USD", - }, - } - - pairs, err := getUSDBasedProviders("FOO", providerPairs) - require.NoError(t, err) - expectedPairs := map[provider.Name]struct{}{ - provider.ProviderCoinbase: {}, - provider.ProviderHuobi: {}, - } - require.Equal(t, pairs, expectedPairs) - - pairs, err = getUSDBasedProviders("USDT", providerPairs) - require.NoError(t, err) - expectedPairs = map[provider.Name]struct{}{ - provider.ProviderBinance: {}, - } - require.Equal(t, pairs, expectedPairs) - - _, err = getUSDBasedProviders("BAR", providerPairs) - require.Error(t, err) -} - -func TestConvertCandlesToUSD(t *testing.T) { - providerCandles := make(provider.AggregatedProviderCandles, 2) - - binanceCandles := map[string][]types.CandlePrice{ - "ATOM": {{ - Price: atomPrice, - Volume: atomVolume, - TimeStamp: provider.PastUnixTime(1 * time.Minute), - }}, - } - providerCandles[provider.ProviderBinance] = binanceCandles - - krakenCandles := map[string][]types.CandlePrice{ - "USDT": {{ - Price: usdtPrice, - Volume: usdtVolume, - TimeStamp: provider.PastUnixTime(1 * time.Minute), - }}, - } - providerCandles[provider.ProviderKraken] = krakenCandles - - providerPairs := map[provider.Name][]types.CurrencyPair{ - provider.ProviderBinance: {atomPair}, - provider.ProviderKraken: {usdtPair}, - } - - convertedCandles, err := ConvertCandlesToUSD( - zerolog.Nop(), - providerCandles, - providerPairs, - make(map[string]sdk.Dec), - ) - require.NoError(t, err) - - require.Equal( - t, - atomPrice.Mul(usdtPrice), - convertedCandles[provider.ProviderBinance]["ATOM"][0].Price, - ) -} - -func TestConvertCandlesToUSDFiltering(t *testing.T) { - providerCandles := make(provider.AggregatedProviderCandles, 2) - - binanceCandles := map[string][]types.CandlePrice{ - "ATOM": {{ - Price: atomPrice, - Volume: atomVolume, - TimeStamp: provider.PastUnixTime(1 * time.Minute), - }}, - } - providerCandles[provider.ProviderBinance] = binanceCandles - - krakenCandles := map[string][]types.CandlePrice{ - "USDT": {{ - Price: usdtPrice, - Volume: usdtVolume, - TimeStamp: provider.PastUnixTime(1 * time.Minute), - }}, - } - providerCandles[provider.ProviderKraken] = krakenCandles - - gateCandles := map[string][]types.CandlePrice{ - "USDT": {{ - Price: usdtPrice, - Volume: usdtVolume, - TimeStamp: provider.PastUnixTime(1 * time.Minute), - }}, - } - providerCandles[provider.ProviderGate] = gateCandles - - okxCandles := map[string][]types.CandlePrice{ - "USDT": {{ - Price: sdk.MustNewDecFromStr("100.0"), - Volume: usdtVolume, - TimeStamp: provider.PastUnixTime(1 * time.Minute), - }}, - } - providerCandles[provider.ProviderOkx] = okxCandles - - providerPairs := map[provider.Name][]types.CurrencyPair{ - provider.ProviderBinance: {atomPair}, - provider.ProviderKraken: {usdtPair}, - provider.ProviderGate: {usdtPair}, - provider.ProviderOkx: {usdtPair}, - } - - convertedCandles, err := ConvertCandlesToUSD( - zerolog.Nop(), - providerCandles, - providerPairs, - make(map[string]sdk.Dec), - ) - require.NoError(t, err) - - require.Equal( - t, - atomPrice.Mul(usdtPrice), - convertedCandles[provider.ProviderBinance]["ATOM"][0].Price, - ) -} - -func TestConvertTickersToUSD(t *testing.T) { - providerPrices := make(provider.AggregatedProviderPrices, 2) - - binanceTickers := map[string]types.TickerPrice{ - "ATOM": { - Price: atomPrice, - Volume: atomVolume, - }, - } - providerPrices[provider.ProviderBinance] = binanceTickers - - krakenTicker := map[string]types.TickerPrice{ - "USDT": { - Price: usdtPrice, - Volume: usdtVolume, - }, - } - providerPrices[provider.ProviderKraken] = krakenTicker - - providerPairs := map[provider.Name][]types.CurrencyPair{ - provider.ProviderBinance: {atomPair}, - provider.ProviderKraken: {usdtPair}, - } - - convertedTickers, err := ConvertTickersToUSD( - zerolog.Nop(), - providerPrices, - providerPairs, - make(map[string]sdk.Dec), - ) - require.NoError(t, err) - - require.Equal( - t, - atomPrice.Mul(usdtPrice), - convertedTickers[provider.ProviderBinance]["ATOM"].Price, - ) -} - -func TestConvertTickersToUSDFiltering(t *testing.T) { - providerPrices := make(provider.AggregatedProviderPrices, 2) - - binanceTickers := map[string]types.TickerPrice{ - "ATOM": { - Price: atomPrice, - Volume: atomVolume, - }, - } - providerPrices[provider.ProviderBinance] = binanceTickers - - krakenTicker := map[string]types.TickerPrice{ - "USDT": { - Price: usdtPrice, - Volume: usdtVolume, - }, - } - providerPrices[provider.ProviderKraken] = krakenTicker - - gateTicker := map[string]types.TickerPrice{ - "USDT": krakenTicker["USDT"], - } - providerPrices[provider.ProviderGate] = gateTicker - - huobiTicker := map[string]types.TickerPrice{ - "USDT": { - Price: sdk.MustNewDecFromStr("10000"), - Volume: usdtVolume, - }, - } - providerPrices[provider.ProviderHuobi] = huobiTicker - - providerPairs := map[provider.Name][]types.CurrencyPair{ - provider.ProviderBinance: {atomPair}, - provider.ProviderKraken: {usdtPair}, - provider.ProviderGate: {usdtPair}, - provider.ProviderHuobi: {usdtPair}, - } - - covertedDeviation, err := ConvertTickersToUSD( - zerolog.Nop(), - providerPrices, - providerPairs, - make(map[string]sdk.Dec), - ) - require.NoError(t, err) - - require.Equal( - t, - atomPrice.Mul(usdtPrice), - covertedDeviation[provider.ProviderBinance]["ATOM"].Price, - ) -} diff --git a/price-feeder/oracle/filter.go b/price-feeder/oracle/filter.go deleted file mode 100644 index 138f5bedd5..0000000000 --- a/price-feeder/oracle/filter.go +++ /dev/null @@ -1,151 +0,0 @@ -package oracle - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/rs/zerolog" - "github.com/umee-network/umee/price-feeder/v2/oracle/provider" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -// defaultDeviationThreshold defines how many 𝜎 a provider can be away -// from the mean without being considered faulty. This can be overridden -// in the config. -var defaultDeviationThreshold = sdk.MustNewDecFromStr("1.0") - -// FilterTickerDeviations finds the standard deviations of the prices of -// all assets, and filters out any providers that are not within 2𝜎 of the mean. -func FilterTickerDeviations( - logger zerolog.Logger, - prices provider.AggregatedProviderPrices, - deviationThresholds map[string]sdk.Dec, -) (provider.AggregatedProviderPrices, error) { - var ( - filteredPrices = make(provider.AggregatedProviderPrices) - priceMap = make(map[provider.Name]map[string]sdk.Dec) - ) - - for providerName, priceTickers := range prices { - p, ok := priceMap[providerName] - if !ok { - p = map[string]sdk.Dec{} - priceMap[providerName] = p - } - for base, tp := range priceTickers { - p[base] = tp.Price - } - } - - deviations, means, err := StandardDeviation(priceMap) - if err != nil { - return nil, err - } - - // We accept any prices that are within (2 * T)𝜎, or for which we couldn't get 𝜎. - // T is defined as the deviation threshold, either set by the config - // or defaulted to 1. - for providerName, priceTickers := range prices { - for base, tp := range priceTickers { - t := defaultDeviationThreshold - if _, ok := deviationThresholds[base]; ok { - t = deviationThresholds[base] - } - - if d, ok := deviations[base]; !ok || isBetween(tp.Price, means[base], d.Mul(t)) { - p, ok := filteredPrices[providerName] - if !ok { - p = map[string]types.TickerPrice{} - filteredPrices[providerName] = p - } - p[base] = tp - } else { - provider.TelemetryFailure(providerName, provider.MessageTypeTicker) - logger.Warn(). - Str("base", base). - Str("provider", string(providerName)). - Str("price", tp.Price.String()). - Msg("provider deviating from other prices") - } - } - } - - return filteredPrices, nil -} - -// FilterCandleDeviations finds the standard deviations of the tvwaps of -// all assets, and filters out any providers that are not within 2𝜎 of the mean. -func FilterCandleDeviations( - logger zerolog.Logger, - candles provider.AggregatedProviderCandles, - deviationThresholds map[string]sdk.Dec, -) (provider.AggregatedProviderCandles, error) { - var ( - filteredCandles = make(provider.AggregatedProviderCandles) - tvwaps = make(map[provider.Name]map[string]sdk.Dec) - ) - - for providerName, priceCandles := range candles { - candlePrices := make(provider.AggregatedProviderCandles) - - for base, cp := range priceCandles { - p, ok := candlePrices[providerName] - if !ok { - p = map[string][]types.CandlePrice{} - candlePrices[providerName] = p - } - p[base] = cp - } - - tvwap, err := ComputeTVWAP(candlePrices) - if err != nil { - return nil, err - } - - for base, asset := range tvwap { - if _, ok := tvwaps[providerName]; !ok { - tvwaps[providerName] = make(map[string]sdk.Dec) - } - - tvwaps[providerName][base] = asset - } - } - - deviations, means, err := StandardDeviation(tvwaps) - if err != nil { - return nil, err - } - - // We accept any prices that are within (2 * T)𝜎, or for which we couldn't get 𝜎. - // T is defined as the deviation threshold, either set by the config - // or defaulted to 1. - for providerName, priceMap := range tvwaps { - for base, price := range priceMap { - t := defaultDeviationThreshold - if _, ok := deviationThresholds[base]; ok { - t = deviationThresholds[base] - } - - if d, ok := deviations[base]; !ok || isBetween(price, means[base], d.Mul(t)) { - p, ok := filteredCandles[providerName] - if !ok { - p = map[string][]types.CandlePrice{} - filteredCandles[providerName] = p - } - p[base] = candles[providerName][base] - } else { - provider.TelemetryFailure(providerName, provider.MessageTypeCandle) - logger.Warn(). - Str("base", base). - Str("provider", string(providerName)). - Str("price", price.String()). - Msg("provider deviating from other candles") - } - } - } - - return filteredCandles, nil -} - -func isBetween(p, mean, margin sdk.Dec) bool { - return p.GTE(mean.Sub(margin)) && - p.LTE(mean.Add(margin)) -} diff --git a/price-feeder/oracle/filter_test.go b/price-feeder/oracle/filter_test.go deleted file mode 100644 index 745411b4a3..0000000000 --- a/price-feeder/oracle/filter_test.go +++ /dev/null @@ -1,128 +0,0 @@ -package oracle - -import ( - "testing" - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" - "github.com/umee-network/umee/price-feeder/v2/oracle/provider" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -func TestSuccessFilterCandleDeviations(t *testing.T) { - providerCandles := make(provider.AggregatedProviderCandles, 4) - pair := types.CurrencyPair{ - Base: "ATOM", - Quote: "USDT", - } - - atomPrice := sdk.MustNewDecFromStr("29.93") - atomVolume := sdk.MustNewDecFromStr("1994674.34000000") - - atomCandlePrice := []types.CandlePrice{ - { - Price: atomPrice, - Volume: atomVolume, - TimeStamp: provider.PastUnixTime(1 * time.Minute), - }, - } - - providerCandles[provider.ProviderBinance] = map[string][]types.CandlePrice{ - pair.Base: atomCandlePrice, - } - providerCandles[provider.ProviderHuobi] = map[string][]types.CandlePrice{ - pair.Base: atomCandlePrice, - } - providerCandles[provider.ProviderKraken] = map[string][]types.CandlePrice{ - pair.Base: atomCandlePrice, - } - providerCandles[provider.ProviderCoinbase] = map[string][]types.CandlePrice{ - pair.Base: { - { - Price: sdk.MustNewDecFromStr("27.1"), - Volume: atomVolume, - TimeStamp: provider.PastUnixTime(1 * time.Minute), - }, - }, - } - - pricesFiltered, err := FilterCandleDeviations( - zerolog.Nop(), - providerCandles, - make(map[string]sdk.Dec), - ) - - _, ok := pricesFiltered[provider.ProviderCoinbase] - require.NoError(t, err, "It should successfully filter out the provider using candles") - require.False(t, ok, "The filtered candle deviation price at coinbase should be empty") - - customDeviations := make(map[string]sdk.Dec, 1) - customDeviations[pair.Base] = sdk.NewDec(2) - - pricesFilteredCustom, err := FilterCandleDeviations( - zerolog.Nop(), - providerCandles, - customDeviations, - ) - - _, ok = pricesFilteredCustom[provider.ProviderCoinbase] - require.NoError(t, err, "It should successfully not filter out coinbase") - require.True(t, ok, "The filtered candle deviation price of coinbase should remain") -} - -func TestSuccessFilterTickerDeviations(t *testing.T) { - providerTickers := make(provider.AggregatedProviderPrices, 4) - pair := types.CurrencyPair{ - Base: "ATOM", - Quote: "USDT", - } - - atomPrice := sdk.MustNewDecFromStr("29.93") - atomVolume := sdk.MustNewDecFromStr("1994674.34000000") - - atomTickerPrice := types.TickerPrice{ - Price: atomPrice, - Volume: atomVolume, - } - - providerTickers[provider.ProviderBinance] = map[string]types.TickerPrice{ - pair.Base: atomTickerPrice, - } - providerTickers[provider.ProviderHuobi] = map[string]types.TickerPrice{ - pair.Base: atomTickerPrice, - } - providerTickers[provider.ProviderKraken] = map[string]types.TickerPrice{ - pair.Base: atomTickerPrice, - } - providerTickers[provider.ProviderCoinbase] = map[string]types.TickerPrice{ - pair.Base: { - Price: sdk.MustNewDecFromStr("27.1"), - Volume: atomVolume, - }, - } - - pricesFiltered, err := FilterTickerDeviations( - zerolog.Nop(), - providerTickers, - make(map[string]sdk.Dec), - ) - - _, ok := pricesFiltered[provider.ProviderCoinbase] - require.NoError(t, err, "It should successfully filter out the provider using tickers") - require.False(t, ok, "The filtered ticker deviation price at coinbase should be empty") - - customDeviations := make(map[string]sdk.Dec, 1) - customDeviations[pair.Base] = sdk.NewDec(2) - - pricesFilteredCustom, err := FilterTickerDeviations( - zerolog.Nop(), - providerTickers, - customDeviations, - ) - - _, ok = pricesFilteredCustom[provider.ProviderCoinbase] - require.NoError(t, err, "It should successfully not filter out coinbase") - require.True(t, ok, "The filtered candle deviation price of coinbase should remain") -} diff --git a/price-feeder/oracle/grpc.go b/price-feeder/oracle/grpc.go deleted file mode 100644 index 1bed3da050..0000000000 --- a/price-feeder/oracle/grpc.go +++ /dev/null @@ -1,34 +0,0 @@ -package oracle - -import ( - "context" - "net" - "strings" -) - -func dialerFunc(_ context.Context, addr string) (net.Conn, error) { - return Connect(addr) -} - -// Connect dials the given address and returns a net.Conn. The protoAddr -// argument should be prefixed with the protocol, -// eg. "tcp://127.0.0.1:8080" or "unix:///tmp/test.sock". -func Connect(protoAddr string) (net.Conn, error) { - proto, address := ProtocolAndAddress(protoAddr) - conn, err := net.Dial(proto, address) - return conn, err -} - -// ProtocolAndAddress splits an address into the protocol and address components. -// For instance, "tcp://127.0.0.1:8080" will be split into "tcp" and "127.0.0.1:8080". -// If the address has no protocol prefix, the default is "tcp". -func ProtocolAndAddress(listenAddr string) (string, string) { - protocol, address := "tcp", listenAddr - - parts := strings.SplitN(address, "://", 2) - if len(parts) == 2 { - protocol, address = parts[0], parts[1] - } - - return protocol, address -} diff --git a/price-feeder/oracle/oracle.go b/price-feeder/oracle/oracle.go deleted file mode 100644 index cae5193484..0000000000 --- a/price-feeder/oracle/oracle.go +++ /dev/null @@ -1,658 +0,0 @@ -package oracle - -import ( - "context" - "crypto/rand" - "encoding/hex" - "fmt" - "math" - "sort" - "strings" - "sync" - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/rs/zerolog" - "golang.org/x/sync/errgroup" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - - "github.com/cosmos/cosmos-sdk/telemetry" - "github.com/umee-network/umee/price-feeder/v2/oracle/client" - "github.com/umee-network/umee/price-feeder/v2/oracle/provider" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" - pfsync "github.com/umee-network/umee/price-feeder/v2/pkg/sync" - oracletypes "github.com/umee-network/umee/v4/x/oracle/types" -) - -// We define tickerSleep as the minimum timeout between each oracle loop. We -// define this value empirically based on enough time to collect exchange rates, -// and broadcast pre-vote and vote transactions such that they're committed in -// at least one block during each voting period. -const ( - tickerSleep = 1000 * time.Millisecond -) - -// PreviousPrevote defines a structure for defining the previous prevote -// submitted on-chain. -type PreviousPrevote struct { - ExchangeRates string - Salt string - SubmitBlockHeight int64 -} - -func NewPreviousPrevote() *PreviousPrevote { - return &PreviousPrevote{ - Salt: "", - ExchangeRates: "", - SubmitBlockHeight: 0, - } -} - -// Oracle implements the core component responsible for fetching exchange rates -// for a given set of currency pairs and determining the correct exchange rates -// to submit to the on-chain price oracle adhering the oracle specification. -type Oracle struct { - logger zerolog.Logger - closer *pfsync.Closer - - providerTimeout time.Duration - providerPairs map[provider.Name][]types.CurrencyPair - previousPrevote *PreviousPrevote - previousVotePeriod float64 - priceProviders map[provider.Name]provider.Provider - oracleClient client.OracleClient - deviations map[string]sdk.Dec - endpoints map[provider.Name]provider.Endpoint - paramCache ParamCache - - pricesMutex sync.RWMutex - lastPriceSyncTS time.Time - prices map[string]sdk.Dec - - tvwapsByProvider PricesWithMutex - vwapsByProvider PricesWithMutex -} - -func New( - logger zerolog.Logger, - oc client.OracleClient, - providerPairs map[provider.Name][]types.CurrencyPair, - providerTimeout time.Duration, - deviations map[string]sdk.Dec, - endpoints map[provider.Name]provider.Endpoint, -) *Oracle { - return &Oracle{ - logger: logger.With().Str("module", "oracle").Logger(), - closer: pfsync.NewCloser(), - oracleClient: oc, - providerPairs: providerPairs, - priceProviders: make(map[provider.Name]provider.Provider), - previousPrevote: nil, - providerTimeout: providerTimeout, - deviations: deviations, - paramCache: ParamCache{}, - endpoints: endpoints, - } -} - -// Start starts the oracle process in a blocking fashion. -func (o *Oracle) Start(ctx context.Context) error { - for { - select { - case <-ctx.Done(): - o.closer.Close() - - default: - o.logger.Debug().Msg("starting oracle tick") - - startTime := time.Now() - - if err := o.tick(ctx); err != nil { - telemetry.IncrCounter(1, "failure", "tick") - o.logger.Err(err).Msg("oracle tick failed") - } - - o.lastPriceSyncTS = time.Now() - - telemetry.MeasureSince(startTime, "runtime", "tick") - telemetry.IncrCounter(1, "new", "tick") - - time.Sleep(tickerSleep) - } - } -} - -// Stop stops the oracle process and waits for it to gracefully exit. -func (o *Oracle) Stop() { - o.closer.Close() - <-o.closer.Done() -} - -// GetLastPriceSyncTimestamp returns the latest timestamp at which prices where -// fetched from the oracle's set of exchange rate providers. -func (o *Oracle) GetLastPriceSyncTimestamp() time.Time { - o.pricesMutex.RLock() - defer o.pricesMutex.RUnlock() - - return o.lastPriceSyncTS -} - -// GetPrices returns a copy of the current prices fetched from the oracle's -// set of exchange rate providers. -func (o *Oracle) GetPrices() map[string]sdk.Dec { - o.pricesMutex.RLock() - defer o.pricesMutex.RUnlock() - - // Creates a new array for the prices in the oracle - prices := make(map[string]sdk.Dec, len(o.prices)) - for k, v := range o.prices { - // Fills in the prices with each value in the oracle - prices[k] = v - } - - return prices -} - -// GetTvwapPrices returns a copy of the tvwapsByProvider map -func (o *Oracle) GetTvwapPrices() PricesByProvider { - return o.tvwapsByProvider.GetPricesClone() -} - -// GetVwapPrices returns the vwapsByProvider map using a read lock -func (o *Oracle) GetVwapPrices() PricesByProvider { - return o.vwapsByProvider.GetPricesClone() -} - -// SetPrices retrieves all the prices and candles from our set of providers as -// determined in the config. If candles are available, uses TVWAP in order -// to determine prices. If candles are not available, uses the most recent prices -// with VWAP. Warns the the user of any missing prices, and filters out any faulty -// providers which do not report prices or candles within 2𝜎 of the others. -func (o *Oracle) SetPrices(ctx context.Context) error { - g := new(errgroup.Group) - mtx := new(sync.Mutex) - providerPrices := make(provider.AggregatedProviderPrices) - providerCandles := make(provider.AggregatedProviderCandles) - requiredRates := make(map[string]struct{}) - - for providerName, currencyPairs := range o.providerPairs { - providerName := providerName - currencyPairs := currencyPairs - - priceProvider, err := o.getOrSetProvider(ctx, providerName) - if err != nil { - return err - } - - for _, pair := range currencyPairs { - if _, ok := requiredRates[pair.Base]; !ok { - requiredRates[pair.Base] = struct{}{} - } - } - - g.Go(func() error { - prices := make(map[string]types.TickerPrice, 0) - candles := make(map[string][]types.CandlePrice, 0) - ch := make(chan struct{}) - errCh := make(chan error, 1) - - go func() { - defer close(ch) - prices, err = priceProvider.GetTickerPrices(currencyPairs...) - if err != nil { - provider.TelemetryFailure(providerName, provider.MessageTypeTicker) - errCh <- err - } - - candles, err = priceProvider.GetCandlePrices(currencyPairs...) - if err != nil { - provider.TelemetryFailure(providerName, provider.MessageTypeCandle) - errCh <- err - } - }() - - select { - case <-ch: - break - case err := <-errCh: - return err - case <-time.After(o.providerTimeout): - telemetry.IncrCounter(1, "failure", "provider", "type", "timeout") - return fmt.Errorf("provider timed out") - } - - // flatten and collect prices based on the base currency per provider - // - // e.g.: {ProviderKraken: {"ATOM": , ...}} - mtx.Lock() - for _, pair := range currencyPairs { - success := SetProviderTickerPricesAndCandles(providerName, providerPrices, providerCandles, prices, candles, pair) - if !success { - mtx.Unlock() - return fmt.Errorf("failed to find any exchange rates in provider responses") - } - } - - mtx.Unlock() - return nil - }) - } - - if err := g.Wait(); err != nil { - o.logger.Err(err).Msg("failed to get ticker prices from provider") - } - - computedPrices, err := o.GetComputedPrices( - providerCandles, - providerPrices, - o.providerPairs, - o.deviations, - ) - if err != nil { - return err - } - - for base := range requiredRates { - if _, ok := computedPrices[base]; !ok { - o.logger.Warn().Str("asset", base).Msg("unable to report price for expected asset") - } - } - - o.pricesMutex.Lock() - o.prices = computedPrices - o.pricesMutex.Unlock() - return nil -} - -// GetComputedPrices gets the candle and ticker prices and computes it. -// It returns candles' TVWAP if possible, if not possible (not available -// or due to some staleness) it will use the most recent ticker prices -// and the VWAP formula instead. -func (o *Oracle) GetComputedPrices( - providerCandles provider.AggregatedProviderCandles, - providerPrices provider.AggregatedProviderPrices, - providerPairs map[provider.Name][]types.CurrencyPair, - deviations map[string]sdk.Dec, -) (prices map[string]sdk.Dec, err error) { - // convert any non-USD denominated candles into USD - convertedCandles, err := ConvertCandlesToUSD( - o.logger, - providerCandles, - providerPairs, - deviations, - ) - if err != nil { - return nil, err - } - - // filter out any erroneous candles - filteredCandles, err := FilterCandleDeviations( - o.logger, - convertedCandles, - deviations, - ) - if err != nil { - return nil, err - } - - computedPrices, _ := ComputeTvwapsByProvider(filteredCandles) - o.tvwapsByProvider.SetPrices(computedPrices) - - // attempt to use candles for TVWAP calculations - tvwapPrices, err := ComputeTVWAP(filteredCandles) - if err != nil { - return nil, err - } - - // If TVWAP candles are not available or were filtered out due to staleness, - // use most recent prices & VWAP instead. - if len(tvwapPrices) == 0 { - convertedTickers, err := ConvertTickersToUSD( - o.logger, - providerPrices, - providerPairs, - deviations, - ) - if err != nil { - return nil, err - } - - filteredProviderPrices, err := FilterTickerDeviations( - o.logger, - convertedTickers, - deviations, - ) - if err != nil { - return nil, err - } - - o.vwapsByProvider.SetPrices(ComputeVwapsByProvider(filteredProviderPrices)) - - vwapPrices := ComputeVWAP(filteredProviderPrices) - - return vwapPrices, nil - } - - return tvwapPrices, nil -} - -// SetProviderTickerPricesAndCandles flattens and collects prices for -// candles and tickers based on the base currency per provider. -// Returns true if at least one of price or candle exists. -func SetProviderTickerPricesAndCandles( - providerName provider.Name, - providerPrices provider.AggregatedProviderPrices, - providerCandles provider.AggregatedProviderCandles, - prices map[string]types.TickerPrice, - candles map[string][]types.CandlePrice, - pair types.CurrencyPair, -) (success bool) { - if _, ok := providerPrices[providerName]; !ok { - providerPrices[providerName] = make(map[string]types.TickerPrice) - } - if _, ok := providerCandles[providerName]; !ok { - providerCandles[providerName] = make(map[string][]types.CandlePrice) - } - - tp, pricesOk := prices[pair.String()] - cp, candlesOk := candles[pair.String()] - - if pricesOk { - providerPrices[providerName][pair.Base] = tp - } - if candlesOk { - providerCandles[providerName][pair.Base] = cp - } - - return pricesOk || candlesOk -} - -// GetParamCache returns the last updated parameters of the x/oracle module -// if the current ParamCache is outdated, we will query it again. -func (o *Oracle) GetParamCache(ctx context.Context, currentBlockHeigh int64) (oracletypes.Params, error) { - if !o.paramCache.IsOutdated(currentBlockHeigh) { - return *o.paramCache.params, nil - } - - params, err := o.GetParams(ctx) - if err != nil { - return oracletypes.Params{}, err - } - - o.checkAcceptList(params) - o.paramCache.Update(currentBlockHeigh, params) - return params, nil -} - -// GetParams returns the current on-chain parameters of the x/oracle module. -func (o *Oracle) GetParams(ctx context.Context) (oracletypes.Params, error) { - grpcConn, err := grpc.Dial( - o.oracleClient.GRPCEndpoint, - // the Cosmos SDK doesn't support any transport security mechanism - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithContextDialer(dialerFunc), - ) - if err != nil { - return oracletypes.Params{}, fmt.Errorf("failed to dial Cosmos gRPC service: %w", err) - } - - defer grpcConn.Close() - queryClient := oracletypes.NewQueryClient(grpcConn) - - ctx, cancel := context.WithTimeout(ctx, 15*time.Second) - defer cancel() - - queryResponse, err := queryClient.Params(ctx, &oracletypes.QueryParams{}) - if err != nil { - return oracletypes.Params{}, fmt.Errorf("failed to get x/oracle params: %w", err) - } - - return queryResponse.Params, nil -} - -func (o *Oracle) getOrSetProvider(ctx context.Context, providerName provider.Name) (provider.Provider, error) { - var ( - priceProvider provider.Provider - ok bool - ) - - priceProvider, ok = o.priceProviders[providerName] - if !ok { - newProvider, err := NewProvider( - ctx, - providerName, - o.logger, - o.endpoints[providerName], - o.providerPairs[providerName]..., - ) - if err != nil { - return nil, err - } - priceProvider = newProvider - - o.priceProviders[providerName] = priceProvider - } - - return priceProvider, nil -} - -func NewProvider( - ctx context.Context, - providerName provider.Name, - logger zerolog.Logger, - endpoint provider.Endpoint, - providerPairs ...types.CurrencyPair, -) (provider.Provider, error) { - switch providerName { - case provider.ProviderBinance: - return provider.NewBinanceProvider(ctx, logger, endpoint, false, providerPairs...) - - case provider.ProviderBinanceUS: - return provider.NewBinanceProvider(ctx, logger, endpoint, true, providerPairs...) - - case provider.ProviderKraken: - return provider.NewKrakenProvider(ctx, logger, endpoint, providerPairs...) - - case provider.ProviderOsmosis: - return provider.NewOsmosisProvider(endpoint), nil - - case provider.ProviderOsmosisV2: - return provider.NewOsmosisV2Provider(ctx, logger, endpoint, providerPairs...) - - case provider.ProviderHuobi: - return provider.NewHuobiProvider(ctx, logger, endpoint, providerPairs...) - - case provider.ProviderCoinbase: - return provider.NewCoinbaseProvider(ctx, logger, endpoint, providerPairs...) - - case provider.ProviderOkx: - return provider.NewOkxProvider(ctx, logger, endpoint, providerPairs...) - - case provider.ProviderGate: - return provider.NewGateProvider(ctx, logger, endpoint, providerPairs...) - - case provider.ProviderBitget: - return provider.NewBitgetProvider(ctx, logger, endpoint, providerPairs...) - - case provider.ProviderMexc: - return provider.NewMexcProvider(ctx, logger, endpoint, providerPairs...) - - case provider.ProviderCrypto: - return provider.NewCryptoProvider(ctx, logger, endpoint, providerPairs...) - - case provider.ProviderMock: - return provider.NewMockProvider(), nil - } - - return nil, fmt.Errorf("provider %s not found", providerName) -} - -func (o *Oracle) checkAcceptList(params oracletypes.Params) { - for _, denom := range params.AcceptList { - symbol := strings.ToUpper(denom.SymbolDenom) - if _, ok := o.prices[symbol]; !ok { - o.logger.Warn().Str("denom", symbol).Msg("price missing for required denom") - } - } -} - -func (o *Oracle) tick(ctx context.Context) error { - o.logger.Debug().Msg("executing oracle tick") - - blockHeight, err := o.oracleClient.ChainHeight.GetChainHeight() - if err != nil { - return err - } - if blockHeight < 1 { - return fmt.Errorf("expected positive block height") - } - - oracleParams, err := o.GetParamCache(ctx, blockHeight) - if err != nil { - return err - } - - if err := o.SetPrices(ctx); err != nil { - return err - } - - // Get oracle vote period, next block height, current vote period, and index - // in the vote period. - oracleVotePeriod := int64(oracleParams.VotePeriod) - nextBlockHeight := blockHeight + 1 - currentVotePeriod := math.Floor(float64(nextBlockHeight) / float64(oracleVotePeriod)) - indexInVotePeriod := nextBlockHeight % oracleVotePeriod - - // Skip until new voting period. Specifically, skip when: - // index [0, oracleVotePeriod - 1] > oracleVotePeriod - 2 OR index is 0 - if (o.previousVotePeriod != 0 && currentVotePeriod == o.previousVotePeriod) || - oracleVotePeriod-indexInVotePeriod < 2 { - o.logger.Info(). - Int64("vote_period", oracleVotePeriod). - Float64("previous_vote_period", o.previousVotePeriod). - Float64("current_vote_period", currentVotePeriod). - Msg("skipping until next voting period") - - return nil - } - - // If we're past the voting period we needed to hit, reset and submit another - // prevote. - if o.previousVotePeriod != 0 && currentVotePeriod-o.previousVotePeriod != 1 { - o.logger.Info(). - Int64("vote_period", oracleVotePeriod). - Float64("previous_vote_period", o.previousVotePeriod). - Float64("current_vote_period", currentVotePeriod). - Msg("missing vote during voting period") - telemetry.IncrCounter(1, "vote", "failure", "missed") - - o.previousVotePeriod = 0 - o.previousPrevote = nil - return nil - } - - salt, err := GenerateSalt(32) - if err != nil { - return err - } - - valAddr, err := sdk.ValAddressFromBech32(o.oracleClient.ValidatorAddrString) - if err != nil { - return err - } - - exchangeRatesStr := GenerateExchangeRatesString(o.prices) - hash := oracletypes.GetAggregateVoteHash(salt, exchangeRatesStr, valAddr) - preVoteMsg := &oracletypes.MsgAggregateExchangeRatePrevote{ - Hash: hash.String(), // hash of prices from the oracle - Feeder: o.oracleClient.OracleAddrString, - Validator: valAddr.String(), - } - - isPrevoteOnlyTx := o.previousPrevote == nil - if isPrevoteOnlyTx { - // This timeout could be as small as oracleVotePeriod-indexInVotePeriod, - // but we give it some extra time just in case. - // - // Ref : https://github.com/terra-money/oracle-feeder/blob/baef2a4a02f57a2ffeaa207932b2e03d7fb0fb25/feeder/src/vote.ts#L222 - o.logger.Info(). - Str("hash", hash.String()). - Str("validator", preVoteMsg.Validator). - Str("feeder", preVoteMsg.Feeder). - Msg("broadcasting pre-vote") - if err := o.oracleClient.BroadcastTx(nextBlockHeight, oracleVotePeriod*2, preVoteMsg); err != nil { - return err - } - - currentHeight, err := o.oracleClient.ChainHeight.GetChainHeight() - if err != nil { - return err - } - - o.previousVotePeriod = math.Floor(float64(currentHeight) / float64(oracleVotePeriod)) - o.previousPrevote = &PreviousPrevote{ - Salt: salt, - ExchangeRates: exchangeRatesStr, - SubmitBlockHeight: currentHeight, - } - } else { - // otherwise, we're in the next voting period and thus we vote - voteMsg := &oracletypes.MsgAggregateExchangeRateVote{ - Salt: o.previousPrevote.Salt, - ExchangeRates: o.previousPrevote.ExchangeRates, - Feeder: o.oracleClient.OracleAddrString, - Validator: valAddr.String(), - } - - o.logger.Info(). - Str("exchange_rates", voteMsg.ExchangeRates). - Str("validator", voteMsg.Validator). - Str("feeder", voteMsg.Feeder). - Msg("broadcasting vote") - if err := o.oracleClient.BroadcastTx( - nextBlockHeight, - oracleVotePeriod-indexInVotePeriod, - voteMsg, - ); err != nil { - return err - } - - o.previousPrevote = nil - o.previousVotePeriod = 0 - } - - return nil -} - -// GenerateSalt generates a random salt, size length/2, as a HEX encoded string. -func GenerateSalt(length int) (string, error) { - if length == 0 { - return "", fmt.Errorf("failed to generate salt: zero length") - } - - bytes := make([]byte, length) - - if _, err := rand.Read(bytes); err != nil { - return "", err - } - - return hex.EncodeToString(bytes), nil -} - -// GenerateExchangeRatesString generates a canonical string representation of -// the aggregated exchange rates. -func GenerateExchangeRatesString(prices map[string]sdk.Dec) string { - exchangeRates := make([]string, len(prices)) - i := 0 - - // aggregate exchange rates as ":" - for base, avgPrice := range prices { - exchangeRates[i] = fmt.Sprintf("%s:%s", base, avgPrice.String()) - i++ - } - - sort.Strings(exchangeRates) - - return strings.Join(exchangeRates, ",") -} diff --git a/price-feeder/oracle/oracle_test.go b/price-feeder/oracle/oracle_test.go deleted file mode 100644 index c757ea7519..0000000000 --- a/price-feeder/oracle/oracle_test.go +++ /dev/null @@ -1,812 +0,0 @@ -package oracle - -import ( - "context" - "fmt" - "testing" - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" - - "github.com/umee-network/umee/price-feeder/v2/oracle/client" - "github.com/umee-network/umee/price-feeder/v2/oracle/provider" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -type mockProvider struct { - prices map[string]types.TickerPrice -} - -func (m mockProvider) GetTickerPrices(_ ...types.CurrencyPair) (map[string]types.TickerPrice, error) { - return m.prices, nil -} - -func (m mockProvider) GetCandlePrices(_ ...types.CurrencyPair) (map[string][]types.CandlePrice, error) { - candles := make(map[string][]types.CandlePrice) - for pair, price := range m.prices { - candles[pair] = []types.CandlePrice{ - { - Price: price.Price, - TimeStamp: provider.PastUnixTime(1 * time.Minute), - Volume: price.Volume, - }, - } - } - return candles, nil -} - -func (m mockProvider) SubscribeCurrencyPairs(_ ...types.CurrencyPair) {} - -func (m mockProvider) GetAvailablePairs() (map[string]struct{}, error) { - return map[string]struct{}{}, nil -} - -type failingProvider struct { - prices map[string]types.TickerPrice -} - -func (m failingProvider) GetTickerPrices(_ ...types.CurrencyPair) (map[string]types.TickerPrice, error) { - return nil, fmt.Errorf("unable to get ticker prices") -} - -func (m failingProvider) GetCandlePrices(_ ...types.CurrencyPair) (map[string][]types.CandlePrice, error) { - return nil, fmt.Errorf("unable to get candle prices") -} - -func (m failingProvider) SubscribeCurrencyPairs(_ ...types.CurrencyPair) {} - -func (m failingProvider) GetAvailablePairs() (map[string]struct{}, error) { - return map[string]struct{}{}, nil -} - -type OracleTestSuite struct { - suite.Suite - - oracle *Oracle -} - -// SetupSuite executes once before the suite's tests are executed. -func (ots *OracleTestSuite) SetupSuite() { - ots.oracle = New( - zerolog.Nop(), - client.OracleClient{}, - map[provider.Name][]types.CurrencyPair{ - provider.ProviderBinance: { - { - Base: "UMEE", - Quote: "USDT", - }, - }, - provider.ProviderKraken: { - { - Base: "UMEE", - Quote: "USDC", - }, - }, - provider.ProviderOsmosis: { - { - Base: "XBT", - Quote: "USDT", - }, - }, - provider.ProviderHuobi: { - { - Base: "USDC", - Quote: "USD", - }, - }, - provider.ProviderCoinbase: { - { - Base: "USDT", - Quote: "USD", - }, - }, - }, - time.Millisecond*100, - make(map[string]sdk.Dec), - make(map[provider.Name]provider.Endpoint), - ) -} - -func TestServiceTestSuite(t *testing.T) { - suite.Run(t, new(OracleTestSuite)) -} - -func (ots *OracleTestSuite) TestStop() { - ots.Eventually( - func() bool { - ots.oracle.Stop() - return true - }, - 5*time.Second, - time.Second, - ) -} - -func (ots *OracleTestSuite) TestGetLastPriceSyncTimestamp() { - // when no tick() has been invoked, assume zero value - ots.Require().Equal(time.Time{}, ots.oracle.GetLastPriceSyncTimestamp()) -} - -func (ots *OracleTestSuite) TestPrices() { - // initial prices should be empty (not set) - ots.Require().Empty(ots.oracle.GetPrices()) - - // Use a mock provider with exchange rates that are not specified in - // configuration. - ots.oracle.priceProviders = map[provider.Name]provider.Provider{ - provider.ProviderBinance: mockProvider{ - prices: map[string]types.TickerPrice{ - "UMEEUSDX": { - Price: sdk.MustNewDecFromStr("3.72"), - Volume: sdk.MustNewDecFromStr("2396974.02000000"), - }, - }, - }, - provider.ProviderKraken: mockProvider{ - prices: map[string]types.TickerPrice{ - "UMEEUSDX": { - Price: sdk.MustNewDecFromStr("3.70"), - Volume: sdk.MustNewDecFromStr("1994674.34000000"), - }, - }, - }, - } - - ots.Require().Error(ots.oracle.SetPrices(context.TODO())) - ots.Require().Empty(ots.oracle.GetPrices()) - - // use a mock provider without a conversion rate for these stablecoins - ots.oracle.priceProviders = map[provider.Name]provider.Provider{ - provider.ProviderBinance: mockProvider{ - prices: map[string]types.TickerPrice{ - "UMEEUSDT": { - Price: sdk.MustNewDecFromStr("3.72"), - Volume: sdk.MustNewDecFromStr("2396974.02000000"), - }, - }, - }, - provider.ProviderKraken: mockProvider{ - prices: map[string]types.TickerPrice{ - "UMEEUSDC": { - Price: sdk.MustNewDecFromStr("3.70"), - Volume: sdk.MustNewDecFromStr("1994674.34000000"), - }, - }, - }, - } - - ots.Require().Error(ots.oracle.SetPrices(context.TODO())) - - prices := ots.oracle.GetPrices() - ots.Require().Len(prices, 0) - - // use a mock provider to provide prices for the configured exchange pairs - ots.oracle.priceProviders = map[provider.Name]provider.Provider{ - provider.ProviderBinance: mockProvider{ - prices: map[string]types.TickerPrice{ - "UMEEUSDT": { - Price: sdk.MustNewDecFromStr("3.72"), - Volume: sdk.MustNewDecFromStr("2396974.02000000"), - }, - }, - }, - provider.ProviderKraken: mockProvider{ - prices: map[string]types.TickerPrice{ - "UMEEUSDC": { - Price: sdk.MustNewDecFromStr("3.70"), - Volume: sdk.MustNewDecFromStr("1994674.34000000"), - }, - }, - }, - provider.ProviderHuobi: mockProvider{ - prices: map[string]types.TickerPrice{ - "USDCUSD": { - Price: sdk.MustNewDecFromStr("1"), - Volume: sdk.MustNewDecFromStr("2396974.34000000"), - }, - }, - }, - provider.ProviderCoinbase: mockProvider{ - prices: map[string]types.TickerPrice{ - "USDTUSD": { - Price: sdk.MustNewDecFromStr("1"), - Volume: sdk.MustNewDecFromStr("1994674.34000000"), - }, - }, - }, - provider.ProviderOsmosis: mockProvider{ - prices: map[string]types.TickerPrice{ - "XBTUSDT": { - Price: sdk.MustNewDecFromStr("3.717"), - Volume: sdk.MustNewDecFromStr("1994674.34000000"), - }, - }, - }, - } - - ots.Require().NoError(ots.oracle.SetPrices(context.TODO())) - - prices = ots.oracle.GetPrices() - ots.Require().Len(prices, 4) - ots.Require().Equal(sdk.MustNewDecFromStr("3.710916056220858266"), prices["UMEE"]) - ots.Require().Equal(sdk.MustNewDecFromStr("3.717"), prices["XBT"]) - ots.Require().Equal(sdk.MustNewDecFromStr("1"), prices["USDC"]) - ots.Require().Equal(sdk.MustNewDecFromStr("1"), prices["USDT"]) - - // use one working provider and one provider with an incorrect exchange rate - ots.oracle.priceProviders = map[provider.Name]provider.Provider{ - provider.ProviderBinance: mockProvider{ - prices: map[string]types.TickerPrice{ - "UMEEUSDX": { - Price: sdk.MustNewDecFromStr("3.72"), - Volume: sdk.MustNewDecFromStr("2396974.02000000"), - }, - }, - }, - provider.ProviderKraken: mockProvider{ - prices: map[string]types.TickerPrice{ - "UMEEUSDC": { - Price: sdk.MustNewDecFromStr("3.70"), - Volume: sdk.MustNewDecFromStr("1994674.34000000"), - }, - }, - }, - provider.ProviderHuobi: mockProvider{ - prices: map[string]types.TickerPrice{ - "USDCUSD": { - Price: sdk.MustNewDecFromStr("1"), - Volume: sdk.MustNewDecFromStr("2396974.34000000"), - }, - }, - }, - provider.ProviderCoinbase: mockProvider{ - prices: map[string]types.TickerPrice{ - "USDTUSD": { - Price: sdk.MustNewDecFromStr("1"), - Volume: sdk.MustNewDecFromStr("1994674.34000000"), - }, - }, - }, - provider.ProviderOsmosis: mockProvider{ - prices: map[string]types.TickerPrice{ - "XBTUSDT": { - Price: sdk.MustNewDecFromStr("3.717"), - Volume: sdk.MustNewDecFromStr("1994674.34000000"), - }, - }, - }, - } - - ots.Require().NoError(ots.oracle.SetPrices(context.TODO())) - prices = ots.oracle.GetPrices() - ots.Require().Len(prices, 4) - ots.Require().Equal(sdk.MustNewDecFromStr("3.70"), prices["UMEE"]) - ots.Require().Equal(sdk.MustNewDecFromStr("3.717"), prices["XBT"]) - ots.Require().Equal(sdk.MustNewDecFromStr("1"), prices["USDC"]) - ots.Require().Equal(sdk.MustNewDecFromStr("1"), prices["USDT"]) - - // use one working provider and one provider that fails - ots.oracle.priceProviders = map[provider.Name]provider.Provider{ - provider.ProviderBinance: failingProvider{ - prices: map[string]types.TickerPrice{ - "UMEEUSDC": { - Price: sdk.MustNewDecFromStr("3.72"), - Volume: sdk.MustNewDecFromStr("2396974.02000000"), - }, - }, - }, - provider.ProviderKraken: mockProvider{ - prices: map[string]types.TickerPrice{ - "UMEEUSDC": { - Price: sdk.MustNewDecFromStr("3.71"), - Volume: sdk.MustNewDecFromStr("1994674.34000000"), - }, - }, - }, - provider.ProviderHuobi: mockProvider{ - prices: map[string]types.TickerPrice{ - "USDCUSD": { - Price: sdk.MustNewDecFromStr("1"), - Volume: sdk.MustNewDecFromStr("2396974.34000000"), - }, - }, - }, - provider.ProviderCoinbase: mockProvider{ - prices: map[string]types.TickerPrice{ - "USDTUSD": { - Price: sdk.MustNewDecFromStr("1"), - Volume: sdk.MustNewDecFromStr("1994674.34000000"), - }, - }, - }, - provider.ProviderOsmosis: mockProvider{ - prices: map[string]types.TickerPrice{ - "XBTUSDT": { - Price: sdk.MustNewDecFromStr("3.717"), - Volume: sdk.MustNewDecFromStr("1994674.34000000"), - }, - }, - }, - } - - ots.Require().NoError(ots.oracle.SetPrices(context.TODO())) - prices = ots.oracle.GetPrices() - ots.Require().Len(prices, 4) - ots.Require().Equal(sdk.MustNewDecFromStr("3.71"), prices["UMEE"]) - ots.Require().Equal(sdk.MustNewDecFromStr("3.717"), prices["XBT"]) - ots.Require().Equal(sdk.MustNewDecFromStr("1"), prices["USDC"]) - ots.Require().Equal(sdk.MustNewDecFromStr("1"), prices["USDT"]) -} - -func TestGenerateSalt(t *testing.T) { - salt, err := GenerateSalt(0) - require.Error(t, err) - require.Empty(t, salt) - - salt, err = GenerateSalt(32) - require.NoError(t, err) - require.NotEmpty(t, salt) -} - -func TestGenerateExchangeRatesString(t *testing.T) { - testCases := map[string]struct { - input map[string]sdk.Dec - expected string - }{ - "empty input": { - input: make(map[string]sdk.Dec), - expected: "", - }, - "single denom": { - input: map[string]sdk.Dec{ - "UMEE": sdk.MustNewDecFromStr("3.72"), - }, - expected: "UMEE:3.720000000000000000", - }, - "multi denom": { - input: map[string]sdk.Dec{ - "UMEE": sdk.MustNewDecFromStr("3.72"), - "ATOM": sdk.MustNewDecFromStr("40.13"), - "OSMO": sdk.MustNewDecFromStr("8.69"), - }, - expected: "ATOM:40.130000000000000000,OSMO:8.690000000000000000,UMEE:3.720000000000000000", - }, - } - - for name, tc := range testCases { - tc := tc - - t.Run(name, func(t *testing.T) { - out := GenerateExchangeRatesString(tc.input) - require.Equal(t, tc.expected, out) - }) - } -} - -func TestSuccessSetProviderTickerPricesAndCandles(t *testing.T) { - providerPrices := make(provider.AggregatedProviderPrices, 1) - providerCandles := make(provider.AggregatedProviderCandles, 1) - pair := types.CurrencyPair{ - Base: "ATOM", - Quote: "USDT", - } - - atomPrice := sdk.MustNewDecFromStr("29.93") - atomVolume := sdk.MustNewDecFromStr("894123.00") - - prices := make(map[string]types.TickerPrice, 1) - prices[pair.String()] = types.TickerPrice{ - Price: atomPrice, - Volume: atomVolume, - } - - candles := make(map[string][]types.CandlePrice, 1) - candles[pair.String()] = []types.CandlePrice{ - { - Price: atomPrice, - Volume: atomVolume, - TimeStamp: provider.PastUnixTime(1 * time.Minute), - }, - } - - success := SetProviderTickerPricesAndCandles( - provider.ProviderGate, - providerPrices, - providerCandles, - prices, - candles, - pair, - ) - - require.True(t, success, "It should successfully set the prices") - require.Equal(t, atomPrice, providerPrices[provider.ProviderGate][pair.Base].Price) - require.Equal(t, atomPrice, providerCandles[provider.ProviderGate][pair.Base][0].Price) -} - -func TestFailedSetProviderTickerPricesAndCandles(t *testing.T) { - success := SetProviderTickerPricesAndCandles( - provider.ProviderCoinbase, - make(provider.AggregatedProviderPrices, 1), - make(provider.AggregatedProviderCandles, 1), - make(map[string]types.TickerPrice, 1), - make(map[string][]types.CandlePrice, 1), - types.CurrencyPair{ - Base: "ATOM", - Quote: "USDT", - }, - ) - - require.False(t, success, "It should failed to set the prices, prices and candle are empty") -} - -func (ots *OracleTestSuite) TestSuccessGetComputedPricesCandles() { - providerCandles := make(provider.AggregatedProviderCandles, 1) - pair := types.CurrencyPair{ - Base: "ATOM", - Quote: "USD", - } - - atomPrice := sdk.MustNewDecFromStr("29.93") - atomVolume := sdk.MustNewDecFromStr("894123.00") - - candles := make(map[string][]types.CandlePrice, 1) - candles[pair.Base] = []types.CandlePrice{ - { - Price: atomPrice, - Volume: atomVolume, - TimeStamp: provider.PastUnixTime(1 * time.Minute), - }, - } - providerCandles[provider.ProviderBinance] = candles - - providerPair := map[provider.Name][]types.CurrencyPair{ - provider.ProviderBinance: {pair}, - } - - prices, err := ots.oracle.GetComputedPrices( - providerCandles, - make(provider.AggregatedProviderPrices, 1), - providerPair, - make(map[string]sdk.Dec), - ) - - require.NoError(ots.T(), err, "It should successfully get computed candle prices") - require.Equal(ots.T(), prices[pair.Base], atomPrice) -} - -func (ots *OracleTestSuite) TestSuccessGetComputedPricesTickers() { - providerPrices := make(provider.AggregatedProviderPrices, 1) - pair := types.CurrencyPair{ - Base: "ATOM", - Quote: "USD", - } - - atomPrice := sdk.MustNewDecFromStr("29.93") - atomVolume := sdk.MustNewDecFromStr("894123.00") - - tickerPrices := make(map[string]types.TickerPrice, 1) - tickerPrices[pair.Base] = types.TickerPrice{ - Price: atomPrice, - Volume: atomVolume, - } - providerPrices[provider.ProviderBinance] = tickerPrices - - providerPair := map[provider.Name][]types.CurrencyPair{ - provider.ProviderBinance: {pair}, - } - - prices, err := ots.oracle.GetComputedPrices( - make(provider.AggregatedProviderCandles, 1), - providerPrices, - providerPair, - make(map[string]sdk.Dec), - ) - - require.NoError(ots.T(), err, "It should successfully get computed ticker prices") - require.Equal(ots.T(), prices[pair.Base], atomPrice) -} - -func (ots *OracleTestSuite) TestGetComputedPricesCandlesConversion() { - btcPair := types.CurrencyPair{ - Base: "BTC", - Quote: "ETH", - } - btcUSDPair := types.CurrencyPair{ - Base: "BTC", - Quote: "USD", - } - ethPair := types.CurrencyPair{ - Base: "ETH", - Quote: "USD", - } - btcEthPrice := sdk.MustNewDecFromStr("17.55") - btcUSDPrice := sdk.MustNewDecFromStr("20962.601") - ethUsdPrice := sdk.MustNewDecFromStr("1195.02") - volume := sdk.MustNewDecFromStr("894123.00") - providerCandles := make(provider.AggregatedProviderCandles, 4) - - // normal rates - binanceCandles := make(map[string][]types.CandlePrice, 2) - binanceCandles[btcPair.Base] = []types.CandlePrice{ - { - Price: btcEthPrice, - Volume: volume, - TimeStamp: provider.PastUnixTime(1 * time.Minute), - }, - } - binanceCandles[ethPair.Base] = []types.CandlePrice{ - { - Price: ethUsdPrice, - Volume: volume, - TimeStamp: provider.PastUnixTime(1 * time.Minute), - }, - } - providerCandles[provider.ProviderBinance] = binanceCandles - - // normal rates - gateCandles := make(map[string][]types.CandlePrice, 1) - gateCandles[ethPair.Base] = []types.CandlePrice{ - { - Price: ethUsdPrice, - Volume: volume, - TimeStamp: provider.PastUnixTime(1 * time.Minute), - }, - } - gateCandles[btcPair.Base] = []types.CandlePrice{ - { - Price: btcEthPrice, - Volume: volume, - TimeStamp: provider.PastUnixTime(1 * time.Minute), - }, - } - providerCandles[provider.ProviderGate] = gateCandles - - // abnormal eth rate - okxCandles := make(map[string][]types.CandlePrice, 1) - okxCandles[ethPair.Base] = []types.CandlePrice{ - { - Price: sdk.MustNewDecFromStr("1.0"), - Volume: volume, - TimeStamp: provider.PastUnixTime(1 * time.Minute), - }, - } - providerCandles[provider.ProviderOkx] = okxCandles - - // btc / usd rate - krakenCandles := make(map[string][]types.CandlePrice, 1) - krakenCandles[btcUSDPair.Base] = []types.CandlePrice{ - { - Price: btcUSDPrice, - Volume: volume, - TimeStamp: provider.PastUnixTime(1 * time.Minute), - }, - } - providerCandles[provider.ProviderKraken] = krakenCandles - - providerPair := map[provider.Name][]types.CurrencyPair{ - provider.ProviderBinance: {btcPair, ethPair}, - provider.ProviderGate: {ethPair}, - provider.ProviderOkx: {ethPair}, - provider.ProviderKraken: {btcUSDPair}, - } - - prices, err := ots.oracle.GetComputedPrices( - providerCandles, - make(provider.AggregatedProviderPrices, 1), - providerPair, - make(map[string]sdk.Dec), - ) - - require.NoError(ots.T(), err, - "It should successfully filter out bad candles and convert everything to USD", - ) - require.Equal(ots.T(), - ethUsdPrice.Mul( - btcEthPrice).Add(btcUSDPrice).Quo(sdk.MustNewDecFromStr("2")), - prices[btcPair.Base], - ) -} - -func (ots *OracleTestSuite) TestGetComputedPricesTickersConversion() { - btcPair := types.CurrencyPair{ - Base: "BTC", - Quote: "ETH", - } - btcUSDPair := types.CurrencyPair{ - Base: "BTC", - Quote: "USD", - } - ethPair := types.CurrencyPair{ - Base: "ETH", - Quote: "USD", - } - volume := sdk.MustNewDecFromStr("881272.00") - btcEthPrice := sdk.MustNewDecFromStr("72.55") - ethUsdPrice := sdk.MustNewDecFromStr("9989.02") - btcUSDPrice := sdk.MustNewDecFromStr("724603.401") - providerPrices := make(provider.AggregatedProviderPrices, 1) - - // normal rates - binanceTickerPrices := make(map[string]types.TickerPrice, 2) - binanceTickerPrices[btcPair.Base] = types.TickerPrice{ - Price: btcEthPrice, - Volume: volume, - } - binanceTickerPrices[ethPair.Base] = types.TickerPrice{ - Price: ethUsdPrice, - Volume: volume, - } - providerPrices[provider.ProviderBinance] = binanceTickerPrices - - // normal rates - gateTickerPrices := make(map[string]types.TickerPrice, 4) - gateTickerPrices[btcPair.Base] = types.TickerPrice{ - Price: btcEthPrice, - Volume: volume, - } - gateTickerPrices[ethPair.Base] = types.TickerPrice{ - Price: ethUsdPrice, - Volume: volume, - } - providerPrices[provider.ProviderGate] = gateTickerPrices - - // abnormal eth rate - okxTickerPrices := make(map[string]types.TickerPrice, 1) - okxTickerPrices[ethPair.Base] = types.TickerPrice{ - Price: sdk.MustNewDecFromStr("1.0"), - Volume: volume, - } - providerPrices[provider.ProviderOkx] = okxTickerPrices - - // btc / usd rate - krakenTickerPrices := make(map[string]types.TickerPrice, 1) - krakenTickerPrices[btcUSDPair.Base] = types.TickerPrice{ - Price: btcUSDPrice, - Volume: volume, - } - providerPrices[provider.ProviderKraken] = krakenTickerPrices - - providerPair := map[provider.Name][]types.CurrencyPair{ - provider.ProviderBinance: {ethPair, btcPair}, - provider.ProviderGate: {ethPair}, - provider.ProviderOkx: {ethPair}, - provider.ProviderKraken: {btcUSDPair}, - } - - prices, err := ots.oracle.GetComputedPrices( - make(provider.AggregatedProviderCandles, 1), - providerPrices, - providerPair, - make(map[string]sdk.Dec), - ) - - require.NoError(ots.T(), err, - "It should successfully filter out bad tickers and convert everything to USD", - ) - require.Equal(ots.T(), - ethUsdPrice.Mul( - btcEthPrice).Add(btcUSDPrice).Quo(sdk.MustNewDecFromStr("2")), - prices[btcPair.Base], - ) -} - -func (ots *OracleTestSuite) TestGetComputedPricesEmptyTvwap() { - symbolUSDT := "USDT" - symbolUSD := "USD" - symbolDAI := "DAI" - symbolETH := "ETH" - - pairETHtoUSDT := types.CurrencyPair{ - Base: symbolETH, - Quote: symbolUSDT, - } - pairETHtoDAI := types.CurrencyPair{ - Base: symbolETH, - Quote: symbolDAI, - } - pairETHtoUSD := types.CurrencyPair{ - Base: symbolETH, - Quote: symbolUSD, - } - basePairsETH := []types.CurrencyPair{ - pairETHtoUSDT, - pairETHtoDAI, - } - krakenPairsETH := append(basePairsETH, pairETHtoUSD) - - pairUSDTtoUSD := types.CurrencyPair{ - Base: symbolUSDT, - Quote: symbolUSD, - } - pairDAItoUSD := types.CurrencyPair{ - Base: symbolDAI, - Quote: symbolUSD, - } - stablecoinPairs := []types.CurrencyPair{ - pairUSDTtoUSD, - pairDAItoUSD, - } - - krakenPairs := append(krakenPairsETH, stablecoinPairs...) - - volume := sdk.MustNewDecFromStr("881272.00") - ethUsdPrice := sdk.MustNewDecFromStr("9989.02") - daiUsdPrice := sdk.MustNewDecFromStr("999890000000000000") - ethTime := provider.PastUnixTime(1 * time.Minute) - - ethCandle := []types.CandlePrice{ - { - Price: ethUsdPrice, - Volume: volume, - TimeStamp: ethTime, - }, - { - Price: ethUsdPrice, - Volume: volume, - TimeStamp: ethTime, - }, - } - daiCandle := []types.CandlePrice{ - { - Price: daiUsdPrice, - Volume: volume, - TimeStamp: 1660829520000, - }, - } - - prices := provider.AggregatedProviderPrices{} - - pairs := map[provider.Name][]types.CurrencyPair{ - provider.ProviderKraken: krakenPairs, - } - - testCases := map[string]struct { - expected string - candles provider.AggregatedProviderCandles - prices provider.AggregatedProviderPrices - pairs map[provider.Name][]types.CurrencyPair - }{ - "Empty tvwap": { - candles: provider.AggregatedProviderCandles{ - provider.ProviderKraken: { - "USDT": ethCandle, - "ETH": ethCandle, - "DAI": daiCandle, - }, - }, - prices: prices, - pairs: pairs, - expected: "error on computing tvwap for quote: DAI, base: ETH", - }, - "No valid conversion rates DAI": { - candles: provider.AggregatedProviderCandles{ - provider.ProviderKraken: { - "USDT": ethCandle, - "ETH": ethCandle, - }, - }, - prices: prices, - pairs: pairs, - expected: "there are no valid conversion rates for DAI", - }, - } - - for name, tc := range testCases { - tc := tc - - ots.Run(name, func() { - _, err := ots.oracle.GetComputedPrices( - tc.candles, - tc.prices, - tc.pairs, - make(map[string]sdk.Dec), - ) - - require.ErrorContains(ots.T(), err, tc.expected) - }) - } -} diff --git a/price-feeder/oracle/param.go b/price-feeder/oracle/param.go deleted file mode 100644 index 40e5976df2..0000000000 --- a/price-feeder/oracle/param.go +++ /dev/null @@ -1,45 +0,0 @@ -package oracle - -import oracletypes "github.com/umee-network/umee/v4/x/oracle/types" - -const ( - // paramsCacheInterval represents the amount of blocks - // during which we will cache the oracle params. - paramsCacheInterval = int64(200) -) - -// ParamCache is used to cache oracle param data for -// an amount of blocks, defined by paramsCacheInterval. -type ParamCache struct { - params *oracletypes.Params - lastUpdatedBlock int64 -} - -// Update retrieves the most recent oracle params and -// updates the instance. -func (paramCache *ParamCache) Update(currentBlockHeigh int64, params oracletypes.Params) { - paramCache.lastUpdatedBlock = currentBlockHeigh - paramCache.params = ¶ms -} - -// IsOutdated checks whether or not the current -// param data was fetched in the last 200 blocks. -func (paramCache *ParamCache) IsOutdated(currentBlockHeigh int64) bool { - if paramCache.params == nil { - return true - } - - if currentBlockHeigh < paramsCacheInterval { - return false - } - - // This is an edge case, which should never happen. - // The current blockchain height is lower - // than the last updated block, to fix we should - // just update the cached params again. - if currentBlockHeigh < paramCache.lastUpdatedBlock { - return true - } - - return (currentBlockHeigh - paramCache.lastUpdatedBlock) > paramsCacheInterval -} diff --git a/price-feeder/oracle/param_test.go b/price-feeder/oracle/param_test.go deleted file mode 100644 index a16187ad92..0000000000 --- a/price-feeder/oracle/param_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package oracle - -import ( - "testing" - - "github.com/stretchr/testify/require" - oracletypes "github.com/umee-network/umee/v4/x/oracle/types" -) - -func TestParamCacheIsOutdated(t *testing.T) { - testCases := map[string]struct { - paramCache ParamCache - currentBlockHeigh int64 - expected bool - }{ - "Params Nil": { - paramCache: ParamCache{ - params: nil, - lastUpdatedBlock: 0, - }, - currentBlockHeigh: 10, - expected: true, - }, - "currentBlockHeigh < cacheOnChainBlockQuantity": { - paramCache: ParamCache{ - params: &oracletypes.Params{}, - lastUpdatedBlock: 0, - }, - currentBlockHeigh: 199, - expected: false, - }, - "currentBlockHeigh < lastUpdatedBlock": { - paramCache: ParamCache{ - params: &oracletypes.Params{}, - lastUpdatedBlock: 205, - }, - currentBlockHeigh: 203, - expected: true, - }, - "Outdated": { - paramCache: ParamCache{ - params: &oracletypes.Params{}, - lastUpdatedBlock: 200, - }, - currentBlockHeigh: 401, - expected: true, - }, - "Limit to keep in cache": { - paramCache: ParamCache{ - params: &oracletypes.Params{}, - lastUpdatedBlock: 200, - }, - currentBlockHeigh: 400, - expected: false, - }, - } - - for name, tc := range testCases { - tc := tc - - t.Run(name, func(t *testing.T) { - require.Equal(t, tc.expected, tc.paramCache.IsOutdated(tc.currentBlockHeigh)) - }) - } -} diff --git a/price-feeder/oracle/prices.go b/price-feeder/oracle/prices.go deleted file mode 100644 index 6e2fd2dd8d..0000000000 --- a/price-feeder/oracle/prices.go +++ /dev/null @@ -1,47 +0,0 @@ -package oracle - -import ( - "sync" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/umee-network/umee/price-feeder/v2/oracle/provider" -) - -type ( - PricesByProvider map[provider.Name]map[string]sdk.Dec - - PricesWithMutex struct { - prices PricesByProvider - mx sync.RWMutex - } -) - -// SetPrices sets the PricesWithMutex.prices value surrounded by a write lock -func (pwm *PricesWithMutex) SetPrices(prices PricesByProvider) { - pwm.mx.Lock() - defer pwm.mx.Unlock() - - pwm.prices = prices -} - -// GetPricesClone retrieves a clone of PricesWithMutex.prices -// surrounded by a read lock -func (pwm *PricesWithMutex) GetPricesClone() PricesByProvider { - pwm.mx.RLock() - defer pwm.mx.RUnlock() - return pwm.clonePrices() -} - -// clonePrices returns a deep copy of PricesWithMutex.prices -func (pwm *PricesWithMutex) clonePrices() PricesByProvider { - clone := make(PricesByProvider, len(pwm.prices)) - for provider, prices := range pwm.prices { - pricesClone := make(map[string]sdk.Dec, len(prices)) - for denom, price := range prices { - pricesClone[denom] = price - } - clone[provider] = pricesClone - } - return clone -} diff --git a/price-feeder/oracle/provider/binance.go b/price-feeder/oracle/provider/binance.go deleted file mode 100644 index 1596e22df5..0000000000 --- a/price-feeder/oracle/provider/binance.go +++ /dev/null @@ -1,387 +0,0 @@ -package provider - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - "strings" - "sync" - - "github.com/gorilla/websocket" - "github.com/rs/zerolog" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -const ( - binanceWSHost = "stream.binance.com:9443" - binanceUSWSHost = "stream.binance.us:9443" - binanceWSPath = "/ws/umeestream" - binanceRestHost = "https://api1.binance.com" - binanceRestUSHost = "https://api.binance.us" - binanceRestPath = "/api/v3/ticker/price" -) - -var _ Provider = (*BinanceProvider)(nil) - -type ( - // BinanceProvider defines an Oracle provider implemented by the Binance public - // API. - // - // REF: https://binance-docs.github.io/apidocs/spot/en/#individual-symbol-mini-ticker-stream - // REF: https://binance-docs.github.io/apidocs/spot/en/#kline-candlestick-streams - BinanceProvider struct { - wsc *WebsocketController - logger zerolog.Logger - mtx sync.RWMutex - endpoints Endpoint - tickers map[string]BinanceTicker // Symbol => BinanceTicker - candles map[string][]BinanceCandle // Symbol => BinanceCandle - subscribedPairs map[string]types.CurrencyPair // Symbol => types.CurrencyPair - } - - // BinanceTicker ticker price response. https://pkg.go.dev/encoding/json#Unmarshal - // Unmarshal matches incoming object keys to the keys used by Marshal (either the - // struct field name or its tag), preferring an exact match but also accepting a - // case-insensitive match. C field which is Statistics close time is not used, but - // it avoids to implement specific UnmarshalJSON. - BinanceTicker struct { - Symbol string `json:"s"` // Symbol ex.: BTCUSDT - LastPrice string `json:"c"` // Last price ex.: 0.0025 - Volume string `json:"v"` // Total traded base asset volume ex.: 1000 - C uint64 `json:"C"` // Statistics close time - } - - // BinanceCandleMetadata candle metadata used to compute tvwap price. - BinanceCandleMetadata struct { - Close string `json:"c"` // Price at close - TimeStamp int64 `json:"T"` // Close time in unix epoch ex.: 1645756200000 - Volume string `json:"v"` // Volume during period - } - - // BinanceCandle candle binance websocket channel "kline_1m" response. - BinanceCandle struct { - Symbol string `json:"s"` // Symbol ex.: BTCUSDT - Metadata BinanceCandleMetadata `json:"k"` // Metadata for candle - } - - // BinanceSubscribeMsg Msg to subscribe all the tickers channels. - BinanceSubscriptionMsg struct { - Method string `json:"method"` // SUBSCRIBE/UNSUBSCRIBE - Params []string `json:"params"` // streams to subscribe ex.: usdtatom@ticker - ID uint16 `json:"id"` // identify messages going back and forth - } - - // BinanceSubscriptionResp the response structure for a binance subscription response - BinanceSubscriptionResp struct { - Result string `json:"result"` - ID uint16 `json:"id"` - } - - // BinancePairSummary defines the response structure for a Binance pair - // summary. - BinancePairSummary struct { - Symbol string `json:"symbol"` - } -) - -func NewBinanceProvider( - ctx context.Context, - logger zerolog.Logger, - endpoints Endpoint, - binanceUS bool, - pairs ...types.CurrencyPair, -) (*BinanceProvider, error) { - if (endpoints.Name) != ProviderBinance { - if !binanceUS { - endpoints = Endpoint{ - Name: ProviderBinance, - Rest: binanceRestHost, - Websocket: binanceWSHost, - } - } else { - endpoints = Endpoint{ - Name: ProviderBinanceUS, - Rest: binanceRestUSHost, - Websocket: binanceUSWSHost, - } - } - } - - wsURL := url.URL{ - Scheme: "wss", - Host: endpoints.Websocket, - Path: binanceWSPath, - } - - binanceLogger := logger.With().Str("provider", string(ProviderBinance)).Logger() - - provider := &BinanceProvider{ - logger: binanceLogger, - endpoints: endpoints, - tickers: map[string]BinanceTicker{}, - candles: map[string][]BinanceCandle{}, - subscribedPairs: map[string]types.CurrencyPair{}, - } - - provider.setSubscribedPairs(pairs...) - - provider.wsc = NewWebsocketController( - ctx, - endpoints.Name, - wsURL, - provider.getSubscriptionMsgs(pairs...), - provider.messageReceived, - disabledPingDuration, - websocket.PingMessage, - binanceLogger, - ) - provider.wsc.StartConnections() - - return provider, nil -} - -func (p *BinanceProvider) getSubscriptionMsgs(cps ...types.CurrencyPair) []interface{} { - subscriptionMsgs := make([]interface{}, 0, len(p.subscribedPairs)*2) - for _, cp := range cps { - binanceTickerPair := currencyPairToBinanceTickerPair(cp) - subscriptionMsgs = append(subscriptionMsgs, newBinanceSubscriptionMsg(binanceTickerPair)) - - binanceCandlePair := currencyPairToBinanceCandlePair(cp) - subscriptionMsgs = append(subscriptionMsgs, newBinanceSubscriptionMsg(binanceCandlePair)) - } - return subscriptionMsgs -} - -// SubscribeCurrencyPairs sends the new subscription messages to the websocket -// and adds them to the providers subscribedPairs array -func (p *BinanceProvider) SubscribeCurrencyPairs(cps ...types.CurrencyPair) { - p.mtx.Lock() - defer p.mtx.Unlock() - - newPairs := []types.CurrencyPair{} - for _, cp := range cps { - if _, ok := p.subscribedPairs[cp.String()]; !ok { - newPairs = append(newPairs, cp) - } - } - - newSubscriptionMsgs := p.getSubscriptionMsgs(newPairs...) - p.wsc.AddWebsocketConnection( - newSubscriptionMsgs, - p.messageReceived, - disabledPingDuration, - websocket.PingMessage, - ) - p.setSubscribedPairs(newPairs...) -} - -// GetTickerPrices returns the tickerPrices based on the provided pairs. -func (p *BinanceProvider) GetTickerPrices(pairs ...types.CurrencyPair) (map[string]types.TickerPrice, error) { - tickerPrices := make(map[string]types.TickerPrice, len(pairs)) - - tickerErrs := 0 - for _, cp := range pairs { - key := cp.String() - price, err := p.getTickerPrice(key) - if err != nil { - p.logger.Warn().Err(err) - tickerErrs++ - continue - } - tickerPrices[key] = price - } - - if tickerErrs == len(pairs) { - return nil, fmt.Errorf( - types.ErrNoTickers.Error(), - p.endpoints.Name, - pairs, - ) - } - return tickerPrices, nil -} - -// GetCandlePrices returns the candlePrices based on the provided pairs. -func (p *BinanceProvider) GetCandlePrices(pairs ...types.CurrencyPair) (map[string][]types.CandlePrice, error) { - candlePrices := make(map[string][]types.CandlePrice, len(pairs)) - - candleErrs := 0 - for _, cp := range pairs { - key := cp.String() - prices, err := p.getCandlePrices(key) - if err != nil { - p.logger.Warn().Err(err) - candleErrs++ - continue - } - candlePrices[key] = prices - } - - if candleErrs == len(pairs) { - return nil, fmt.Errorf( - types.ErrNoCandles.Error(), - p.endpoints.Name, - pairs, - ) - } - return candlePrices, nil -} - -func (p *BinanceProvider) getTickerPrice(key string) (types.TickerPrice, error) { - p.mtx.RLock() - defer p.mtx.RUnlock() - - ticker, ok := p.tickers[key] - if !ok { - return types.TickerPrice{}, fmt.Errorf( - types.ErrTickerNotFound.Error(), - p.endpoints.Name, - key, - ) - } - - return ticker.toTickerPrice() -} - -func (p *BinanceProvider) getCandlePrices(key string) ([]types.CandlePrice, error) { - p.mtx.RLock() - defer p.mtx.RUnlock() - - candles, ok := p.candles[key] - if !ok { - return []types.CandlePrice{}, fmt.Errorf( - types.ErrCandleNotFound.Error(), - p.endpoints.Name, - key, - ) - } - - candleList := []types.CandlePrice{} - for _, candle := range candles { - cp, err := candle.toCandlePrice() - if err != nil { - return []types.CandlePrice{}, err - } - candleList = append(candleList, cp) - } - return candleList, nil -} - -func (p *BinanceProvider) messageReceived(_ int, _ *WebsocketConnection, bz []byte) { - var ( - tickerResp BinanceTicker - tickerErr error - candleResp BinanceCandle - candleErr error - subscribeResp BinanceSubscriptionResp - subscribeRespErr error - ) - - tickerErr = json.Unmarshal(bz, &tickerResp) - if len(tickerResp.LastPrice) != 0 { - p.setTickerPair(tickerResp) - telemetryWebsocketMessage(ProviderBinance, MessageTypeTicker) - return - } - - candleErr = json.Unmarshal(bz, &candleResp) - if len(candleResp.Metadata.Close) != 0 { - p.setCandlePair(candleResp) - telemetryWebsocketMessage(ProviderBinance, MessageTypeCandle) - return - } - - subscribeRespErr = json.Unmarshal(bz, &subscribeResp) - if subscribeResp.ID == 1 { - return - } - - p.logger.Error(). - Int("length", len(bz)). - AnErr("ticker", tickerErr). - AnErr("candle", candleErr). - AnErr("subscribeResp", subscribeRespErr). - Msg("Error on receive message") -} - -func (p *BinanceProvider) setTickerPair(ticker BinanceTicker) { - p.mtx.Lock() - defer p.mtx.Unlock() - p.tickers[ticker.Symbol] = ticker -} - -func (p *BinanceProvider) setCandlePair(candle BinanceCandle) { - p.mtx.Lock() - defer p.mtx.Unlock() - staleTime := PastUnixTime(providerCandlePeriod) - candleList := []BinanceCandle{} - candleList = append(candleList, candle) - - for _, c := range p.candles[candle.Symbol] { - if staleTime < c.Metadata.TimeStamp { - candleList = append(candleList, c) - } - } - p.candles[candle.Symbol] = candleList -} - -func (ticker BinanceTicker) toTickerPrice() (types.TickerPrice, error) { - return types.NewTickerPrice(string(ProviderBinance), ticker.Symbol, ticker.LastPrice, ticker.Volume) -} - -func (candle BinanceCandle) toCandlePrice() (types.CandlePrice, error) { - return types.NewCandlePrice(string(ProviderBinance), candle.Symbol, candle.Metadata.Close, candle.Metadata.Volume, - candle.Metadata.TimeStamp) -} - -// setSubscribedPairs sets N currency pairs to the map of subscribed pairs. -func (p *BinanceProvider) setSubscribedPairs(cps ...types.CurrencyPair) { - for _, cp := range cps { - p.subscribedPairs[cp.String()] = cp - } -} - -// GetAvailablePairs returns all pairs to which the provider can subscribe. -// ex.: map["ATOMUSDT" => {}, "UMEEUSDC" => {}]. -func (p *BinanceProvider) GetAvailablePairs() (map[string]struct{}, error) { - resp, err := http.Get(p.endpoints.Rest + binanceRestPath) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var pairsSummary []BinancePairSummary - if err := json.NewDecoder(resp.Body).Decode(&pairsSummary); err != nil { - return nil, err - } - - availablePairs := make(map[string]struct{}, len(pairsSummary)) - for _, pairName := range pairsSummary { - availablePairs[strings.ToUpper(pairName.Symbol)] = struct{}{} - } - - return availablePairs, nil -} - -// currencyPairToBinanceTickerPair receives a currency pair and return binance -// ticker symbol atomusdt@ticker. -func currencyPairToBinanceTickerPair(cp types.CurrencyPair) string { - return strings.ToLower(cp.String() + "@ticker") -} - -// currencyPairToBinanceCandlePair receives a currency pair and return binance -// candle symbol atomusdt@kline_1m. -func currencyPairToBinanceCandlePair(cp types.CurrencyPair) string { - return strings.ToLower(cp.String() + "@kline_1m") -} - -// newBinanceSubscriptionMsg returns a new subscription Msg. -func newBinanceSubscriptionMsg(params ...string) BinanceSubscriptionMsg { - return BinanceSubscriptionMsg{ - Method: "SUBSCRIBE", - Params: params, - ID: 1, - } -} diff --git a/price-feeder/oracle/provider/binance_test.go b/price-feeder/oracle/provider/binance_test.go deleted file mode 100644 index f2cb2c9f21..0000000000 --- a/price-feeder/oracle/provider/binance_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package provider - -import ( - "context" - "encoding/json" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -func TestBinanceProvider_GetTickerPrices(t *testing.T) { - p, err := NewBinanceProvider( - context.TODO(), - zerolog.Nop(), - Endpoint{}, - true, - types.CurrencyPair{Base: "ATOM", Quote: "USDT"}, - ) - require.NoError(t, err) - - t.Run("valid_request_single_ticker", func(t *testing.T) { - lastPrice := "34.69000000" - volume := "2396974.02000000" - - tickerMap := map[string]BinanceTicker{} - tickerMap["ATOMUSDT"] = BinanceTicker{ - Symbol: "ATOMUSDT", - LastPrice: lastPrice, - Volume: volume, - } - - p.tickers = tickerMap - - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "ATOM", Quote: "USDT"}) - require.NoError(t, err) - require.Len(t, prices, 1) - require.Equal(t, sdk.MustNewDecFromStr(lastPrice), prices["ATOMUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr(volume), prices["ATOMUSDT"].Volume) - }) - - t.Run("valid_request_multi_ticker", func(t *testing.T) { - lastPriceAtom := "34.69000000" - lastPriceLuna := "41.35000000" - volume := "2396974.02000000" - - tickerMap := map[string]BinanceTicker{} - tickerMap["ATOMUSDT"] = BinanceTicker{ - Symbol: "ATOMUSDT", - LastPrice: lastPriceAtom, - Volume: volume, - } - - tickerMap["LUNAUSDT"] = BinanceTicker{ - Symbol: "LUNAUSDT", - LastPrice: lastPriceLuna, - Volume: volume, - } - - p.tickers = tickerMap - prices, err := p.GetTickerPrices( - types.CurrencyPair{Base: "ATOM", Quote: "USDT"}, - types.CurrencyPair{Base: "LUNA", Quote: "USDT"}, - ) - require.NoError(t, err) - require.Len(t, prices, 2) - require.Equal(t, sdk.MustNewDecFromStr(lastPriceAtom), prices["ATOMUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr(volume), prices["ATOMUSDT"].Volume) - require.Equal(t, sdk.MustNewDecFromStr(lastPriceLuna), prices["LUNAUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr(volume), prices["LUNAUSDT"].Volume) - }) - - t.Run("invalid_request_invalid_ticker", func(t *testing.T) { - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "FOO", Quote: "BAR"}) - require.EqualError(t, err, "binanceus has no ticker data for requested pairs: [FOOBAR]") - require.Nil(t, prices) - }) -} - -func TestBinanceCurrencyPairToBinancePair(t *testing.T) { - cp := types.CurrencyPair{Base: "ATOM", Quote: "USDT"} - binanceSymbol := currencyPairToBinanceTickerPair(cp) - require.Equal(t, binanceSymbol, "atomusdt@ticker") -} - -func TestBinanceProvider_getSubscriptionMsgs(t *testing.T) { - provider := &BinanceProvider{ - subscribedPairs: map[string]types.CurrencyPair{}, - } - cps := []types.CurrencyPair{ - {Base: "ATOM", Quote: "USDT"}, - } - - subMsgs := provider.getSubscriptionMsgs(cps...) - - msg, _ := json.Marshal(subMsgs[0]) - require.Equal(t, "{\"method\":\"SUBSCRIBE\",\"params\":[\"atomusdt@ticker\"],\"id\":1}", string(msg)) - - msg, _ = json.Marshal(subMsgs[1]) - require.Equal(t, "{\"method\":\"SUBSCRIBE\",\"params\":[\"atomusdt@kline_1m\"],\"id\":1}", string(msg)) -} diff --git a/price-feeder/oracle/provider/bitget.go b/price-feeder/oracle/provider/bitget.go deleted file mode 100644 index 967426a398..0000000000 --- a/price-feeder/oracle/provider/bitget.go +++ /dev/null @@ -1,457 +0,0 @@ -package provider - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - "strconv" - "sync" - "time" - - "github.com/gorilla/websocket" - "github.com/rs/zerolog" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -const ( - bitgetWSHost = "ws.bitget.com" - bitgetWSPath = "/spot/v1/stream" - bitgetReconnectTime = time.Minute * 2 - bitgetRestHost = "https://api.bitget.com" - bitgetRestPath = "/api/spot/v1/public/products" - tickerChannel = "ticker" - candleChannel = "candle5m" - instType = "SP" -) - -var _ Provider = (*BitgetProvider)(nil) - -type ( - // BitgetProvider defines an Oracle provider implemented by the Bitget public - // API. - // - // REF: https://bitgetlimited.github.io/apidoc/en/spot/#tickers-channel - // REF: https://bitgetlimited.github.io/apidoc/en/spot/#candlesticks-channel - BitgetProvider struct { - wsc *WebsocketController - logger zerolog.Logger - mtx sync.RWMutex - endpoints Endpoint - tickers map[string]BitgetTicker // Symbol => BitgetTicker - candles map[string][]BitgetCandle // Symbol => BitgetCandle - subscribedPairs map[string]types.CurrencyPair // Symbol => types.CurrencyPair - } - - // BitgetSubscriptionMsg Msg to subscribe all at once. - BitgetSubscriptionMsg struct { - Operation string `json:"op"` // Operation (e.x. "subscribe") - Args []BitgetSubscriptionArg `json:"args"` // Arguments to subscribe to - } - BitgetSubscriptionArg struct { - InstType string `json:"instType"` // Instrument type (e.g. "sp") - Channel string `json:"channel"` // Channel (e.x. "ticker" / "candle5m") - InstID string `json:"instId"` // Instrument ID (e.x. BTCUSDT) - } - - // BitgetErrResponse is the structure for bitget subscription errors. - BitgetErrResponse struct { - Event string `json:"event"` // e.x. "error" - Code uint64 `json:"code"` // e.x. 30003 for invalid op - Msg string `json:"msg"` // e.x. "INVALID op" - } - // BitgetSubscriptionResponse is the structure for bitget subscription confirmations. - BitgetSubscriptionResponse struct { - Event string `json:"event"` // e.x. "subscribe" - Arg BitgetSubscriptionArg `json:"arg"` // subscription event argument - } - - // BitgetTickerResponse is the structure for bitget ticker messages. - BitgetTicker struct { - Action string `json:"action"` // e.x. "snapshot" - Arg BitgetSubscriptionArg `json:"arg"` // subscription event argument - Data []BitgetTickerData `json:"data"` // ticker data - } - BitgetTickerData struct { - InstID string `json:"instId"` // e.x. BTCUSD - Price string `json:"last"` // last price e.x. "12.3907" - Volume string `json:"baseVolume"` // volume in base asset (e.x. "112247.9173") - } - - // BitgetCandleResponse is the response structure for the bitget ticker message. - BitgetCandleResponse struct { - Action string `json:"action"` // e.x. "snapshot" - Arg BitgetSubscriptionArg `json:"arg"` // subscription event argument - Data [][]string `json:"data"` // candle data in an array at data[0]. - } - BitgetCandle struct { - Arg BitgetSubscriptionArg // subscription event argument - TimeStamp int64 // unix timestamp in milliseconds e.x. 1597026383085 - Close string // Most recent price e.x. "8533.02" - Volume string // volume e.x. "45247" - } - - // BitgetPairsSummary defines the response structure for a Bitget pairs - // summary. - BitgetPairsSummary struct { - RespCode string `json:"code"` - Data []BitgetPairData `json:"data"` - } - BitgetPairData struct { - Base string `json:"baseCoin"` - Quote string `json:"quoteCoin"` - } -) - -// NewBitgetProvider returns a new Bitget provider with the WS connection -// and msg handler. -func NewBitgetProvider( - ctx context.Context, - logger zerolog.Logger, - endpoints Endpoint, - pairs ...types.CurrencyPair, -) (*BitgetProvider, error) { - if endpoints.Name != ProviderBitget { - endpoints = Endpoint{ - Name: ProviderBitget, - Rest: bitgetRestHost, - Websocket: bitgetWSHost, - } - } - - wsURL := url.URL{ - Scheme: "wss", - Host: endpoints.Websocket, - Path: bitgetWSPath, - } - - bitgetLogger := logger.With().Str("provider", string(ProviderBitget)).Logger() - - provider := &BitgetProvider{ - logger: bitgetLogger, - endpoints: endpoints, - tickers: map[string]BitgetTicker{}, - candles: map[string][]BitgetCandle{}, - subscribedPairs: map[string]types.CurrencyPair{}, - } - - provider.setSubscribedPairs(pairs...) - - provider.wsc = NewWebsocketController( - ctx, - endpoints.Name, - wsURL, - provider.getSubscriptionMsgs(pairs...), - provider.messageReceived, - defaultPingDuration, - websocket.TextMessage, - bitgetLogger, - ) - provider.wsc.StartConnections() - - return provider, nil -} - -func (p *BitgetProvider) getSubscriptionMsgs(cps ...types.CurrencyPair) []interface{} { - subscriptionMsgs := make([]interface{}, 0, 1) - bitgetTickerSubscriptionMsg := newBitgetTickerSubscriptionMsg(cps) - subscriptionMsgs = append(subscriptionMsgs, bitgetTickerSubscriptionMsg) - - return subscriptionMsgs -} - -// SubscribeCurrencyPairs sends the new subscription messages to the websocket -// and adds them to the providers subscribedPairs array -func (p *BitgetProvider) SubscribeCurrencyPairs(cps ...types.CurrencyPair) { - p.mtx.Lock() - defer p.mtx.Unlock() - - newPairs := []types.CurrencyPair{} - for _, cp := range cps { - if _, ok := p.subscribedPairs[cp.String()]; !ok { - newPairs = append(newPairs, cp) - } - } - - newSubscriptionMsgs := p.getSubscriptionMsgs(newPairs...) - p.wsc.AddWebsocketConnection( - newSubscriptionMsgs, - p.messageReceived, - defaultPingDuration, - websocket.PingMessage, - ) - p.setSubscribedPairs(newPairs...) -} - -// GetTickerPrices returns the tickerPrices based on the provided pairs. -func (p *BitgetProvider) GetTickerPrices(pairs ...types.CurrencyPair) (map[string]types.TickerPrice, error) { - tickerPrices := make(map[string]types.TickerPrice, len(pairs)) - - tickerErrs := 0 - for _, cp := range pairs { - price, err := p.getTickerPrice(cp) - if err != nil { - p.logger.Warn().Err(err) - tickerErrs++ - continue - } - tickerPrices[cp.String()] = price - } - - if tickerErrs == len(pairs) { - return nil, fmt.Errorf( - types.ErrNoTickers.Error(), - p.endpoints.Name, - pairs, - ) - } - return tickerPrices, nil -} - -// GetCandlePrices returns the candlePrices based on the provided pairs. -func (p *BitgetProvider) GetCandlePrices(pairs ...types.CurrencyPair) (map[string][]types.CandlePrice, error) { - candlePrices := make(map[string][]types.CandlePrice, len(pairs)) - - candleErrs := 0 - for _, cp := range pairs { - prices, err := p.getCandlePrices(cp) - if err != nil { - p.logger.Warn().Err(err) - candleErrs++ - continue - } - candlePrices[cp.String()] = prices - } - - if candleErrs == len(pairs) { - return nil, fmt.Errorf( - types.ErrNoCandles.Error(), - p.endpoints.Name, - pairs, - ) - } - return candlePrices, nil -} - -// messageReceived handles the received data from the Bitget websocket. -func (p *BitgetProvider) messageReceived(_ int, _ *WebsocketConnection, bz []byte) { - var ( - tickerResp BitgetTicker - tickerErr error - candleResp BitgetCandleResponse - candleErr error - errResponse BitgetErrResponse - subscriptionResponse BitgetSubscriptionResponse - ) - - err := json.Unmarshal(bz, &errResponse) - if err == nil && errResponse.Code != 0 { - p.logger.Error(). - Int("length", len(bz)). - Str("msg", errResponse.Msg). - Str("body", string(bz)). - Msg("Error on receive bitget message") - return - } - - err = json.Unmarshal(bz, &subscriptionResponse) - if err == nil && subscriptionResponse.Event == "subscribe" { - p.logger.Debug(). - Str("InstID", subscriptionResponse.Arg.InstID). - Str("Channel", subscriptionResponse.Arg.Channel). - Str("InstType", subscriptionResponse.Arg.InstType). - Msg("Bitget subscription confirmed") - return - } - - tickerErr = json.Unmarshal(bz, &tickerResp) - if tickerResp.Arg.Channel == tickerChannel { - p.setTickerPair(tickerResp) - telemetryWebsocketMessage(ProviderBitget, MessageTypeTicker) - return - } - - candleErr = json.Unmarshal(bz, &candleResp) - if candleResp.Arg.Channel == candleChannel { - candle, err := candleResp.ToBitgetCandle() - if err != nil { - p.logger.Error(). - Int("length", len(bz)). - AnErr("candle", err). - Msg("Unable to parse bitget candle") - } - p.setCandlePair(candle) - telemetryWebsocketMessage(ProviderBitget, MessageTypeCandle) - return - } - - p.logger.Error(). - Int("length", len(bz)). - AnErr("ticker", tickerErr). - AnErr("candle", candleErr). - Msg("Error on receive message") -} - -// ToBitgetCandle turns a BitgetCandleResponse into a more-readable -// BitgetCandle. The Close and Volume responses are at the [0][4] and -// [0][5] indexes respectively. -// Ref: https://bitgetlimited.github.io/apidoc/en/spot/#candlesticks-channel -func (bcr BitgetCandleResponse) ToBitgetCandle() (BitgetCandle, error) { - if len(bcr.Data) < 1 || len(bcr.Data[0]) < 6 { - return BitgetCandle{}, fmt.Errorf("invalid candle response") - } - - ts, err := strconv.ParseInt(bcr.Data[0][0], 10, 64) - if err != nil { - return BitgetCandle{}, err - } - - return BitgetCandle{ - Arg: bcr.Arg, - TimeStamp: ts, - Close: bcr.Data[0][4], - Volume: bcr.Data[0][5], - }, nil -} - -func (p *BitgetProvider) setTickerPair(ticker BitgetTicker) { - p.mtx.Lock() - defer p.mtx.Unlock() - p.tickers[ticker.Arg.InstID] = ticker -} - -func (p *BitgetProvider) setCandlePair(candle BitgetCandle) { - p.mtx.Lock() - defer p.mtx.Unlock() - staleTime := PastUnixTime(providerCandlePeriod) - candleList := []BitgetCandle{} - candleList = append(candleList, candle) - - for _, c := range p.candles[candle.Arg.InstID] { - if staleTime < c.TimeStamp { - candleList = append(candleList, c) - } - } - p.candles[candle.Arg.InstID] = candleList -} - -func (p *BitgetProvider) getTickerPrice(cp types.CurrencyPair) (types.TickerPrice, error) { - p.mtx.RLock() - defer p.mtx.RUnlock() - - ticker, ok := p.tickers[cp.String()] - if !ok { - return types.TickerPrice{}, fmt.Errorf( - types.ErrTickerNotFound.Error(), - p.endpoints.Name, - cp.String(), - ) - } - - return ticker.toTickerPrice() -} - -func (p *BitgetProvider) getCandlePrices(cp types.CurrencyPair) ([]types.CandlePrice, error) { - p.mtx.RLock() - defer p.mtx.RUnlock() - - candles, ok := p.candles[cp.String()] - if !ok { - return []types.CandlePrice{}, fmt.Errorf( - types.ErrCandleNotFound.Error(), - p.endpoints.Name, - cp.String(), - ) - } - - candleList := []types.CandlePrice{} - for _, candle := range candles { - cp, err := candle.toCandlePrice() - if err != nil { - return []types.CandlePrice{}, err - } - candleList = append(candleList, cp) - } - return candleList, nil -} - -// setSubscribedPairs sets N currency pairs to the map of subscribed pairs. -func (p *BitgetProvider) setSubscribedPairs(cps ...types.CurrencyPair) { - for _, cp := range cps { - p.subscribedPairs[cp.String()] = cp - } -} - -// GetAvailablePairs returns all pairs to which the provider can subscribe. -func (p *BitgetProvider) GetAvailablePairs() (map[string]struct{}, error) { - resp, err := http.Get(p.endpoints.Rest + bitgetRestPath) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var pairsSummary BitgetPairsSummary - if err := json.NewDecoder(resp.Body).Decode(&pairsSummary); err != nil { - return nil, err - } - if pairsSummary.RespCode != "00000" { - return nil, fmt.Errorf("unable to get bitget available pairs") - } - - availablePairs := make(map[string]struct{}, len(pairsSummary.Data)) - for _, pair := range pairsSummary.Data { - cp := types.CurrencyPair{ - Base: pair.Base, - Quote: pair.Quote, - } - availablePairs[cp.String()] = struct{}{} - } - - return availablePairs, nil -} - -// toTickerPrice converts current BitgetTicker to TickerPrice. -func (ticker BitgetTicker) toTickerPrice() (types.TickerPrice, error) { - if len(ticker.Data) < 1 { - return types.TickerPrice{}, fmt.Errorf("ticker has no data") - } - return types.NewTickerPrice( - string(ProviderBitget), - ticker.Arg.InstID, - ticker.Data[0].Price, - ticker.Data[0].Volume, - ) -} - -func (candle BitgetCandle) toCandlePrice() (types.CandlePrice, error) { - return types.NewCandlePrice( - string(ProviderBitget), - candle.Arg.InstID, - candle.Close, - candle.Volume, - candle.TimeStamp, - ) -} - -// newBitgetTickerSubscriptionMsg returns a new ticker subscription Msg. -func newBitgetTickerSubscriptionMsg(cps []types.CurrencyPair) BitgetSubscriptionMsg { - args := []BitgetSubscriptionArg{} - for _, cp := range cps { - args = append(args, BitgetSubscriptionArg{ - InstType: instType, - Channel: tickerChannel, - InstID: cp.String(), - }) - args = append(args, BitgetSubscriptionArg{ - InstType: instType, - Channel: candleChannel, - InstID: cp.String(), - }) - } - - return BitgetSubscriptionMsg{ - Operation: "subscribe", - Args: args, - } -} diff --git a/price-feeder/oracle/provider/bitget_test.go b/price-feeder/oracle/provider/bitget_test.go deleted file mode 100644 index 600bbf0c01..0000000000 --- a/price-feeder/oracle/provider/bitget_test.go +++ /dev/null @@ -1,182 +0,0 @@ -package provider - -import ( - "context" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -func TestBitgetProvider_GetTickerPrices(t *testing.T) { - p, err := NewBitgetProvider( - context.TODO(), - zerolog.Nop(), - Endpoint{}, - types.CurrencyPair{Base: "BTC", Quote: "USDT"}, - ) - require.NoError(t, err) - - t.Run("valid_request_single_ticker", func(t *testing.T) { - lastPrice := "34.69000000" - volume := "2396974.02000000" - instID := "ATOMUSDT" - - tickerMap := map[string]BitgetTicker{} - tickerMap[instID] = BitgetTicker{ - Arg: BitgetSubscriptionArg{ - Channel: "tickers", - InstID: instID, - }, - Data: []BitgetTickerData{ - { - InstID: instID, - Price: lastPrice, - Volume: volume, - }, - }, - } - - p.tickers = tickerMap - - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "ATOM", Quote: "USDT"}) - require.NoError(t, err) - require.Len(t, prices, 1) - require.Equal(t, sdk.MustNewDecFromStr(lastPrice), prices["ATOMUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr(volume), prices["ATOMUSDT"].Volume) - }) - - t.Run("valid_request_multi_ticker", func(t *testing.T) { - atomInstID := "ATOMUSDT" - atomLastPrice := "34.69000000" - lunaInstID := "LUNAUSDT" - lunaLastPrice := "41.35000000" - volume := "2396974.02000000" - - tickerMap := map[string]BitgetTicker{} - tickerMap[atomInstID] = BitgetTicker{ - Arg: BitgetSubscriptionArg{ - Channel: "tickers", - InstID: atomInstID, - }, - Data: []BitgetTickerData{ - { - InstID: atomInstID, - Price: atomLastPrice, - Volume: volume, - }, - }, - } - tickerMap[lunaInstID] = BitgetTicker{ - Arg: BitgetSubscriptionArg{ - Channel: "tickers", - InstID: lunaInstID, - }, - Data: []BitgetTickerData{ - { - InstID: lunaInstID, - Price: lunaLastPrice, - Volume: volume, - }, - }, - } - p.tickers = tickerMap - prices, err := p.GetTickerPrices( - types.CurrencyPair{Base: "ATOM", Quote: "USDT"}, - types.CurrencyPair{Base: "LUNA", Quote: "USDT"}, - ) - - require.NoError(t, err) - require.Len(t, prices, 2) - require.Equal(t, sdk.MustNewDecFromStr(atomLastPrice), prices["ATOMUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr(volume), prices["ATOMUSDT"].Volume) - require.Equal(t, sdk.MustNewDecFromStr(lunaLastPrice), prices["LUNAUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr(volume), prices["LUNAUSDT"].Volume) - }) - - t.Run("invalid_request_invalid_ticker", func(t *testing.T) { - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "FOO", Quote: "BAR"}) - require.EqualError(t, err, "bitget has no ticker data for requested pairs: [FOOBAR]") - require.Nil(t, prices) - }) -} - -func TestBitgetProvider_GetCandlePrices(t *testing.T) { - p, err := NewBitgetProvider( - context.TODO(), - zerolog.Nop(), - Endpoint{}, - types.CurrencyPair{Base: "ATOM", Quote: "USDT"}, - ) - require.NoError(t, err) - - t.Run("valid_request_single_candle", func(t *testing.T) { - price := "34.689998626708984000" - volume := "2396974.000000000000000000" - timeStamp := int64(1000000) - - candle := BitgetCandle{ - TimeStamp: timeStamp, - Close: price, - Volume: volume, - Arg: BitgetSubscriptionArg{ - Channel: "candle15m", - InstID: "ATOMUSDT", - }, - } - - p.setCandlePair(candle) - - prices, err := p.GetCandlePrices(types.CurrencyPair{Base: "ATOM", Quote: "USDT"}) - require.NoError(t, err) - require.Len(t, prices, 1) - require.Equal(t, sdk.MustNewDecFromStr(price), prices["ATOMUSDT"][0].Price) - require.Equal(t, sdk.MustNewDecFromStr(volume), prices["ATOMUSDT"][0].Volume) - require.Equal(t, timeStamp, prices["ATOMUSDT"][0].TimeStamp) - }) - - t.Run("invalid_request_invalid_candle", func(t *testing.T) { - prices, err := p.GetCandlePrices(types.CurrencyPair{Base: "FOO", Quote: "BAR"}) - require.EqualError(t, err, "bitget has no candle data for requested pairs: [FOOBAR]") - require.Nil(t, prices) - }) -} - -func TestBitgetProvider_AvailablePairs(t *testing.T) { - p, err := NewBitgetProvider( - context.TODO(), - zerolog.Nop(), - Endpoint{}, - types.CurrencyPair{}, - ) - require.NoError(t, err) - - pairs, err := p.GetAvailablePairs() - require.NoError(t, err) - - require.NotEmpty(t, pairs) -} - -func TestBitgetProvider_NewSubscriptionMsg(t *testing.T) { - cps := []types.CurrencyPair{ - { - Base: "ATOM", Quote: "USDT", - }, - { - Base: "FOO", Quote: "BAR", - }, - } - sub := newBitgetTickerSubscriptionMsg(cps) - - require.Equal(t, len(sub.Args), 2*len(cps)) - require.Equal(t, sub.Args[0].InstID, "ATOMUSDT") - require.Equal(t, sub.Args[0].Channel, "ticker") - require.Equal(t, sub.Args[1].InstID, "ATOMUSDT") - require.Equal(t, sub.Args[1].Channel, "candle5m") - require.Equal(t, sub.Args[2].InstID, "FOOBAR") - require.Equal(t, sub.Args[2].Channel, "ticker") - require.Equal(t, sub.Args[3].InstID, "FOOBAR") - require.Equal(t, sub.Args[3].Channel, "candle5m") -} diff --git a/price-feeder/oracle/provider/coinbase.go b/price-feeder/oracle/provider/coinbase.go deleted file mode 100644 index 64752fb627..0000000000 --- a/price-feeder/oracle/provider/coinbase.go +++ /dev/null @@ -1,447 +0,0 @@ -package provider - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - "sort" - "strings" - "sync" - "time" - - "github.com/gorilla/websocket" - "github.com/rs/zerolog" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -const ( - coinbaseWSHost = "ws-feed.exchange.coinbase.com" - coinbasePingCheck = time.Second * 28 // should be < 30 - coinbaseRestHost = "https://api.exchange.coinbase.com" - coinbaseRestPath = "/products" - coinbaseTimeFmt = "2006-01-02T15:04:05.000000Z" - unixMinute = 60000 -) - -var _ Provider = (*CoinbaseProvider)(nil) - -type ( - // CoinbaseProvider defines an Oracle provider implemented by the Coinbase public - // API. - // - // REF: https://www.coinbase.io/docs/websocket/index.html - CoinbaseProvider struct { - wsc *WebsocketController - logger zerolog.Logger - reconnectTimer *time.Ticker - mtx sync.RWMutex - endpoints Endpoint - trades map[string][]CoinbaseTrade // Symbol => []CoinbaseTrade - tickers map[string]CoinbaseTicker // Symbol => CoinbaseTicker - subscribedPairs map[string]types.CurrencyPair // Symbol => types.CurrencyPair - } - - // CoinbaseSubscriptionMsg Msg to subscribe to all channels. - CoinbaseSubscriptionMsg struct { - Type string `json:"type"` // ex. "subscribe" - ProductIDs []string `json:"product_ids"` // streams to subscribe ex.: ["BOT-USDT", ...] - Channels []string `json:"channels"` // channels to subscribe to ex.: "ticker" - } - - // CoinbaseMatchResponse defines the response body for coinbase trades. - CoinbaseTradeResponse struct { - Type string `json:"type"` // "last_match" or "match" - ProductID string `json:"product_id"` // ex.: ATOM-USDT - Time string `json:"time"` // Time in format 2006-01-02T15:04:05.000000Z - Size string `json:"size"` // Size of the trade ex.: 10.41 - Price string `json:"price"` // ex.: 14.02 - } - - // CoinbaseTrade defines the trade info we'd like to save. - CoinbaseTrade struct { - ProductID string // ex.: ATOM-USDT - Time int64 // Time in unix epoch ex.: 164732388700 - Size string // Size of the trade ex.: 10.41 - Price string // ex.: 14.02 - } - - // CoinbaseTicker defines the ticker info we'd like to save. - CoinbaseTicker struct { - ProductID string `json:"product_id"` // ex.: ATOM-USDT - Price string `json:"price"` // ex.: 523.0 - Volume string `json:"volume_24h"` // 24-hour volume - } - - // CoinbaseErrResponse defines the response body for errors. - CoinbaseErrResponse struct { - Type string `json:"type"` // should be "error" - Reason string `json:"reason"` // ex.: "tickers" is not a valid channel - } - - // CoinbasePairSummary defines the response structure for a Coinbase pair summary. - CoinbasePairSummary struct { - Base string `json:"base_currency"` - Quote string `json:"quote_currency"` - } -) - -// NewCoinbaseProvider creates a new CoinbaseProvider. -func NewCoinbaseProvider( - ctx context.Context, - logger zerolog.Logger, - endpoints Endpoint, - pairs ...types.CurrencyPair, -) (*CoinbaseProvider, error) { - if endpoints.Name != ProviderCoinbase { - endpoints = Endpoint{ - Name: ProviderCoinbase, - Rest: coinbaseRestHost, - Websocket: coinbaseWSHost, - } - } - wsURL := url.URL{ - Scheme: "wss", - Host: endpoints.Websocket, - } - - coinbaseLogger := logger.With().Str("provider", string(ProviderCoinbase)).Logger() - - provider := &CoinbaseProvider{ - logger: coinbaseLogger, - reconnectTimer: time.NewTicker(coinbasePingCheck), - endpoints: endpoints, - trades: map[string][]CoinbaseTrade{}, - tickers: map[string]CoinbaseTicker{}, - subscribedPairs: map[string]types.CurrencyPair{}, - } - - provider.setSubscribedPairs(pairs...) - - provider.wsc = NewWebsocketController( - ctx, - endpoints.Name, - wsURL, - provider.getSubscriptionMsgs(pairs...), - provider.messageReceived, - defaultPingDuration, - websocket.PingMessage, - coinbaseLogger, - ) - provider.wsc.StartConnections() - - return provider, nil -} - -func (p *CoinbaseProvider) getSubscriptionMsgs(cps ...types.CurrencyPair) []interface{} { - subscriptionMsgs := make([]interface{}, 0, 1) - - topics := make([]string, len(cps)) - index := 0 - - for _, cp := range cps { - topics[index] = currencyPairToCoinbasePair(cp) - index++ - } - msg := newCoinbaseSubscription(topics...) - subscriptionMsgs = append(subscriptionMsgs, msg) - return subscriptionMsgs -} - -// SubscribeCurrencyPairs sends the new subscription messages to the websocket -// and adds them to the providers subscribedPairs array -func (p *CoinbaseProvider) SubscribeCurrencyPairs(cps ...types.CurrencyPair) { - p.mtx.Lock() - defer p.mtx.Unlock() - - newPairs := []types.CurrencyPair{} - for _, cp := range cps { - if _, ok := p.subscribedPairs[cp.String()]; !ok { - newPairs = append(newPairs, cp) - } - } - - newSubscriptionMsgs := p.getSubscriptionMsgs(newPairs...) - p.wsc.AddWebsocketConnection( - newSubscriptionMsgs, - p.messageReceived, - defaultPingDuration, - websocket.PingMessage, - ) - p.setSubscribedPairs(newPairs...) -} - -// GetTickerPrices returns the tickerPrices based on the provided pairs. -func (p *CoinbaseProvider) GetTickerPrices(pairs ...types.CurrencyPair) (map[string]types.TickerPrice, error) { - tickerPrices := make(map[string]types.TickerPrice, len(pairs)) - - tickerErrs := 0 - for _, cp := range pairs { - price, err := p.getTickerPrice(cp) - if err != nil { - p.logger.Warn().Err(err) - tickerErrs++ - continue - } - tickerPrices[cp.String()] = price - } - - if tickerErrs == len(pairs) { - return nil, fmt.Errorf( - types.ErrNoTickers.Error(), - p.endpoints.Name, - pairs, - ) - } - return tickerPrices, nil -} - -// GetCandlePrices returns candles based off of the saved trades map. -// Candles need to be cut up into one-minute intervals. -func (p *CoinbaseProvider) GetCandlePrices(pairs ...types.CurrencyPair) (map[string][]types.CandlePrice, error) { - tradeMap := make(map[string][]CoinbaseTrade, len(pairs)) - - tradeErrs := 0 - for _, cp := range pairs { - key := currencyPairToCoinbasePair(cp) - tradeSet, err := p.getTradePrices(key) - if err != nil { - p.logger.Warn().Err(err) - tradeErrs++ - continue - } - tradeMap[key] = tradeSet - } - if tradeErrs == len(pairs) { - return nil, fmt.Errorf( - types.ErrNoTickers.Error(), - p.endpoints.Name, - pairs, - ) - } - - candles := make(map[string][]types.CandlePrice) - - for cp := range tradeMap { - trades := tradeMap[cp] - // sort oldest -> newest - sort.Slice(trades, func(i, j int) bool { - return time.Unix(trades[i].Time, 0).Before(time.Unix(trades[j].Time, 0)) - }) - - candleSlice := []types.CandlePrice{ - { - Price: sdk.ZeroDec(), - Volume: sdk.ZeroDec(), - }, - } - startTime := trades[0].Time - index := 0 - - // divide into chunks by minute - for _, trade := range trades { - // every minute, reset the time period - if trade.Time-startTime > unixMinute { - index++ - startTime = trade.Time - candleSlice = append(candleSlice, types.CandlePrice{ - Price: sdk.ZeroDec(), - Volume: sdk.ZeroDec(), - }) - } - - size, err := sdk.NewDecFromStr(trade.Size) - if err != nil { - return nil, err - } - price, err := sdk.NewDecFromStr(trade.Price) - if err != nil { - return nil, err - } - - volume := candleSlice[index].Volume.Add(size) - candleSlice[index] = types.CandlePrice{ - Volume: volume, // aggregate size - Price: price, // most recent price - TimeStamp: trade.Time, // most recent timestamp - } - } - - candles[coinbasePairToCurrencyPair(cp)] = candleSlice - } - - return candles, nil -} - -// GetAvailablePairs returns all pairs to which the provider can subscribe. -func (p *CoinbaseProvider) GetAvailablePairs() (map[string]struct{}, error) { - resp, err := http.Get(p.endpoints.Rest + coinbaseRestPath) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var pairsSummary []CoinbasePairSummary - if err := json.NewDecoder(resp.Body).Decode(&pairsSummary); err != nil { - return nil, err - } - - availablePairs := make(map[string]struct{}, len(pairsSummary)) - for _, pair := range pairsSummary { - cp := types.CurrencyPair{ - Base: strings.ToUpper(pair.Base), - Quote: strings.ToUpper(pair.Quote), - } - availablePairs[cp.String()] = struct{}{} - } - - return availablePairs, nil -} - -func (p *CoinbaseProvider) getTickerPrice(cp types.CurrencyPair) (types.TickerPrice, error) { - p.mtx.RLock() - defer p.mtx.RUnlock() - - gp := currencyPairToCoinbasePair(cp) - if tickerPair, ok := p.tickers[gp]; ok { - return tickerPair.toTickerPrice() - } - - return types.TickerPrice{}, fmt.Errorf( - types.ErrTickerNotFound.Error(), - p.endpoints.Name, - gp, - ) -} - -func (p *CoinbaseProvider) getTradePrices(key string) ([]CoinbaseTrade, error) { - p.mtx.RLock() - defer p.mtx.RUnlock() - - trades, ok := p.trades[key] - if !ok { - return []CoinbaseTrade{}, fmt.Errorf("failed to get trades for %s", key) - } - - return trades, nil -} - -func (p *CoinbaseProvider) messageReceived(_ int, _ *WebsocketConnection, bz []byte) { - var coinbaseTrade CoinbaseTradeResponse - if err := json.Unmarshal(bz, &coinbaseTrade); err != nil { - p.logger.Error().Err(err).Msg("unable to unmarshal response") - return - } - - if coinbaseTrade.Type == "error" { - var coinbaseErr CoinbaseErrResponse - if err := json.Unmarshal(bz, &coinbaseErr); err != nil { - p.logger.Debug().Err(err).Msg("unable to unmarshal error response") - } - p.logger.Error().Msg(coinbaseErr.Reason) - return - } - - if coinbaseTrade.Type == "subscriptions" { // successful subscription message - return - } - - if coinbaseTrade.Type == "ticker" { - var coinbaseTicker CoinbaseTicker - if err := json.Unmarshal(bz, &coinbaseTicker); err != nil { - p.logger.Error().Err(err).Msg("unable to unmarshal response") - return - } - - p.setTickerPair(coinbaseTicker) - telemetryWebsocketMessage(ProviderCoinbase, MessageTypeTicker) - return - } - - telemetryWebsocketMessage(ProviderCoinbase, MessageTypeTrade) - p.setTradePair(coinbaseTrade) -} - -// timeToUnix converts a Time in format "2006-01-02T15:04:05.000000Z" to unix -func (tr CoinbaseTradeResponse) timeToUnix() int64 { - t, err := time.Parse(coinbaseTimeFmt, tr.Time) - if err != nil { - return 0 - } - return t.UnixMilli() -} - -func (tr CoinbaseTradeResponse) toTrade() CoinbaseTrade { - return CoinbaseTrade{ - Time: tr.timeToUnix(), - Price: tr.Price, - ProductID: tr.ProductID, - Size: tr.Size, - } -} - -func (p *CoinbaseProvider) setTickerPair(ticker CoinbaseTicker) { - p.mtx.Lock() - defer p.mtx.Unlock() - - p.tickers[ticker.ProductID] = ticker -} - -// setTradePair takes a CoinbaseTradeResponse, converts its date into unix epoch, -// and then will add it to a copy of the trade slice. Then it filters out any -// "stale" trades, and sets the trade slice in memory to the copy. -func (p *CoinbaseProvider) setTradePair(tradeResponse CoinbaseTradeResponse) { - p.mtx.Lock() - defer p.mtx.Unlock() - staleTime := PastUnixTime(providerCandlePeriod) - tradeList := []CoinbaseTrade{ - tradeResponse.toTrade(), - } - - for _, t := range p.trades[tradeResponse.ProductID] { - if staleTime < t.Time { - tradeList = append(tradeList, t) - } - } - p.trades[tradeResponse.ProductID] = tradeList -} - -// setSubscribedPairs sets N currency pairs to the map of subscribed pairs. -func (p *CoinbaseProvider) setSubscribedPairs(cps ...types.CurrencyPair) { - for _, cp := range cps { - p.subscribedPairs[cp.String()] = cp - } -} - -func (ticker CoinbaseTicker) toTickerPrice() (types.TickerPrice, error) { - return types.NewTickerPrice( - string(ProviderCoinbase), - coinbasePairToCurrencyPair(ticker.ProductID), - ticker.Price, - ticker.Volume, - ) -} - -// currencyPairToCoinbasePair returns the expected pair for Coinbase -// ex.: "ATOM-USDT". -func currencyPairToCoinbasePair(pair types.CurrencyPair) string { - return pair.Base + "-" + pair.Quote -} - -// coinbasePairToCurrencyPair returns the currency pair string -// ex.: "ATOMUSDT". -func coinbasePairToCurrencyPair(coinbasePair string) string { - return strings.ReplaceAll(coinbasePair, "-", "") -} - -// newCoinbaseSubscription returns a new subscription topic for matches/tickers. -func newCoinbaseSubscription(cp ...string) CoinbaseSubscriptionMsg { - return CoinbaseSubscriptionMsg{ - Type: "subscribe", - ProductIDs: cp, - Channels: []string{"matches", "ticker"}, - } -} diff --git a/price-feeder/oracle/provider/coinbase_test.go b/price-feeder/oracle/provider/coinbase_test.go deleted file mode 100644 index 7f9d2cbb4c..0000000000 --- a/price-feeder/oracle/provider/coinbase_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package provider - -import ( - "context" - "encoding/json" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -func TestCoinbaseProvider_GetTickerPrices(t *testing.T) { - p, err := NewCoinbaseProvider( - context.TODO(), - zerolog.Nop(), - Endpoint{}, - types.CurrencyPair{Base: "BTC", Quote: "USDT"}, - ) - require.NoError(t, err) - - t.Run("valid_request_single_ticker", func(t *testing.T) { - lastPrice := "34.69000000" - volume := "2396974.02000000" - - tickerMap := map[string]CoinbaseTicker{} - tickerMap["ATOM-USDT"] = CoinbaseTicker{ - Price: lastPrice, - Volume: volume, - } - - p.tickers = tickerMap - - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "ATOM", Quote: "USDT"}) - require.NoError(t, err) - require.Len(t, prices, 1) - require.Equal(t, sdk.MustNewDecFromStr(lastPrice), prices["ATOMUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr(volume), prices["ATOMUSDT"].Volume) - }) - - t.Run("valid_request_multi_ticker", func(t *testing.T) { - lastPriceAtom := "34.69000000" - lastPriceUmee := "41.35000000" - volume := "2396974.02000000" - - tickerMap := map[string]CoinbaseTicker{} - tickerMap["ATOM-USDT"] = CoinbaseTicker{ - Price: lastPriceAtom, - Volume: volume, - } - - tickerMap["UMEE-USDT"] = CoinbaseTicker{ - Price: lastPriceUmee, - Volume: volume, - } - - p.tickers = tickerMap - prices, err := p.GetTickerPrices( - types.CurrencyPair{Base: "ATOM", Quote: "USDT"}, - types.CurrencyPair{Base: "UMEE", Quote: "USDT"}, - ) - require.NoError(t, err) - require.Len(t, prices, 2) - require.Equal(t, sdk.MustNewDecFromStr(lastPriceAtom), prices["ATOMUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr(volume), prices["ATOMUSDT"].Volume) - require.Equal(t, sdk.MustNewDecFromStr(lastPriceUmee), prices["UMEEUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr(volume), prices["UMEEUSDT"].Volume) - }) - - t.Run("invalid_request_invalid_ticker", func(t *testing.T) { - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "FOO", Quote: "BAR"}) - require.EqualError(t, err, "coinbase has no ticker data for requested pairs: [FOOBAR]") - require.Nil(t, prices) - }) -} - -func TestCoinbasePairToCurrencyPair(t *testing.T) { - cp := types.CurrencyPair{Base: "ATOM", Quote: "USDT"} - currencyPairSymbol := coinbasePairToCurrencyPair("ATOM-USDT") - require.Equal(t, cp.String(), currencyPairSymbol) -} - -func TestCurrencyPairToCoinbasePair(t *testing.T) { - cp := types.CurrencyPair{Base: "ATOM", Quote: "USDT"} - coinbaseSymbol := currencyPairToCoinbasePair(cp) - require.Equal(t, coinbaseSymbol, "ATOM-USDT") -} - -func TestCoinbaseProvider_getSubscriptionMsgs(t *testing.T) { - provider := &CoinbaseProvider{ - subscribedPairs: map[string]types.CurrencyPair{}, - } - cps := []types.CurrencyPair{ - {Base: "ATOM", Quote: "USDT"}, - } - subMsgs := provider.getSubscriptionMsgs(cps...) - - msg, _ := json.Marshal(subMsgs[0]) - require.Equal(t, "{\"type\":\"subscribe\",\"product_ids\":[\"ATOM-USDT\"],\"channels\":[\"matches\",\"ticker\"]}", string(msg)) -} diff --git a/price-feeder/oracle/provider/crypto.go b/price-feeder/oracle/provider/crypto.go deleted file mode 100644 index f067804daf..0000000000 --- a/price-feeder/oracle/provider/crypto.go +++ /dev/null @@ -1,449 +0,0 @@ -package provider - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - "strings" - "sync" - "time" - - "github.com/gorilla/websocket" - "github.com/rs/zerolog" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -const ( - cryptoWSHost = "stream.crypto.com" - cryptoWSPath = "/v2/market" - cryptoReconnectTime = time.Second * 30 - cryptoRestHost = "https://api.crypto.com" - cryptoRestPath = "/v2/public/get-ticker" - cryptoTickerChannel = "ticker" - cryptoCandleChannel = "candlestick" - cryptoHeartbeatMethod = "public/heartbeat" - cryptoHeartbeatReqMethod = "public/respond-heartbeat" - cryptoTickerMsgPrefix = "ticker." - cryptoCandleMsgPrefix = "candlestick.5m." -) - -var _ Provider = (*CryptoProvider)(nil) - -type ( - // CryptoProvider defines an Oracle provider implemented by the Crypto.com public - // API. - // - // REF: https://exchange-docs.crypto.com/spot/index.html#introduction - CryptoProvider struct { - wsc *WebsocketController - logger zerolog.Logger - mtx sync.RWMutex - endpoints Endpoint - tickers map[string]types.TickerPrice // Symbol => TickerPrice - candles map[string][]types.CandlePrice // Symbol => CandlePrice - subscribedPairs map[string]types.CurrencyPair // Symbol => types.CurrencyPair - } - - CryptoTickerResponse struct { - Result CryptoTickerResult `json:"result"` - } - CryptoTickerResult struct { - InstrumentName string `json:"instrument_name"` // ex.: ATOM_USDT - Channel string `json:"channel"` // ex.: ticker - Data []CryptoTicker `json:"data"` // ticker data - } - CryptoTicker struct { - InstrumentName string `json:"i"` // Instrument Name, e.g. BTC_USDT, ETH_CRO, etc. - Volume string `json:"v"` // The total 24h traded volume - LatestTrade string `json:"a"` // The price of the latest trade, null if there weren't any trades - } - - CryptoCandleResponse struct { - Result CryptoCandleResult `json:"result"` - } - CryptoCandleResult struct { - InstrumentName string `json:"instrument_name"` // ex.: ATOM_USDT - Channel string `json:"channel"` // ex.: candlestick - Data []CryptoCandle `json:"data"` // candlestick data - } - CryptoCandle struct { - Close string `json:"c"` // Price at close - Volume string `json:"v"` // Volume during interval - Timestamp int64 `json:"t"` // End time of candlestick (Unix timestamp) - } - - CryptoSubscriptionMsg struct { - ID int64 `json:"id"` - Method string `json:"method"` // subscribe, unsubscribe - Params CryptoSubscriptionParams `json:"params"` - Nonce int64 `json:"nonce"` // Current timestamp (milliseconds since the Unix epoch) - } - CryptoSubscriptionParams struct { - Channels []string `json:"channels"` // Channels to be subscribed ex. ticker.ATOM_USDT - } - - CryptoPairsSummary struct { - Result CryptoInstruments `json:"result"` - } - CryptoInstruments struct { - Data []CryptoTicker `json:"data"` - } - - CryptoHeartbeatResponse struct { - ID int64 `json:"id"` - Method string `json:"method"` // public/heartbeat - } - CryptoHeartbeatRequest struct { - ID int64 `json:"id"` - Method string `json:"method"` // public/respond-heartbeat - } -) - -func NewCryptoProvider( - ctx context.Context, - logger zerolog.Logger, - endpoints Endpoint, - pairs ...types.CurrencyPair, -) (*CryptoProvider, error) { - if endpoints.Name != ProviderCrypto { - endpoints = Endpoint{ - Name: ProviderCrypto, - Rest: cryptoRestHost, - Websocket: cryptoWSHost, - } - } - - wsURL := url.URL{ - Scheme: "wss", - Host: endpoints.Websocket, - Path: cryptoWSPath, - } - - cryptoLogger := logger.With().Str("provider", "crypto").Logger() - - provider := &CryptoProvider{ - logger: cryptoLogger, - endpoints: endpoints, - tickers: map[string]types.TickerPrice{}, - candles: map[string][]types.CandlePrice{}, - subscribedPairs: map[string]types.CurrencyPair{}, - } - - provider.setSubscribedPairs(pairs...) - - provider.wsc = NewWebsocketController( - ctx, - endpoints.Name, - wsURL, - provider.getSubscriptionMsgs(pairs...), - provider.messageReceived, - disabledPingDuration, - websocket.PingMessage, - cryptoLogger, - ) - provider.wsc.StartConnections() - - return provider, nil -} - -func (p *CryptoProvider) getSubscriptionMsgs(cps ...types.CurrencyPair) []interface{} { - subscriptionMsgs := make([]interface{}, 0, len(cps)*2) - for _, cp := range cps { - cryptoPair := currencyPairToCryptoPair(cp) - channel := cryptoTickerMsgPrefix + cryptoPair - msg := newCryptoSubscriptionMsg([]string{channel}) - subscriptionMsgs = append(subscriptionMsgs, msg) - - cryptoPair = currencyPairToCryptoPair(cp) - channel = cryptoCandleMsgPrefix + cryptoPair - msg = newCryptoSubscriptionMsg([]string{channel}) - subscriptionMsgs = append(subscriptionMsgs, msg) - } - return subscriptionMsgs -} - -// SubscribeCurrencyPairs sends the new subscription messages to the websocket -// and adds them to the providers subscribedPairs array -func (p *CryptoProvider) SubscribeCurrencyPairs(cps ...types.CurrencyPair) { - p.mtx.Lock() - defer p.mtx.Unlock() - - newPairs := []types.CurrencyPair{} - for _, cp := range cps { - if _, ok := p.subscribedPairs[cp.String()]; !ok { - newPairs = append(newPairs, cp) - } - } - - newSubscriptionMsgs := p.getSubscriptionMsgs(newPairs...) - p.wsc.AddWebsocketConnection( - newSubscriptionMsgs, - p.messageReceived, - disabledPingDuration, - websocket.PingMessage, - ) - p.setSubscribedPairs(newPairs...) -} - -// GetTickerPrices returns the tickerPrices based on the provided pairs. -func (p *CryptoProvider) GetTickerPrices(pairs ...types.CurrencyPair) (map[string]types.TickerPrice, error) { - tickerPrices := make(map[string]types.TickerPrice, len(pairs)) - - tickerErrs := 0 - for _, cp := range pairs { - key := currencyPairToCryptoPair(cp) - price, err := p.getTickerPrice(key) - if err != nil { - p.logger.Warn().Err(err) - tickerErrs++ - continue - } - tickerPrices[cp.String()] = price - } - - if tickerErrs == len(pairs) { - return nil, fmt.Errorf( - types.ErrNoTickers.Error(), - p.endpoints.Name, - pairs, - ) - } - return tickerPrices, nil -} - -// GetCandlePrices returns the candlePrices based on the provided pairs. -func (p *CryptoProvider) GetCandlePrices(pairs ...types.CurrencyPair) (map[string][]types.CandlePrice, error) { - candlePrices := make(map[string][]types.CandlePrice, len(pairs)) - - candleErrs := 0 - for _, cp := range pairs { - key := currencyPairToCryptoPair(cp) - prices, err := p.getCandlePrices(key) - if err != nil { - p.logger.Warn().Err(err) - candleErrs++ - continue - } - candlePrices[cp.String()] = prices - } - - if candleErrs == len(pairs) { - return nil, fmt.Errorf( - types.ErrNoCandles.Error(), - p.endpoints.Name, - pairs, - ) - } - return candlePrices, nil -} - -func (p *CryptoProvider) getTickerPrice(key string) (types.TickerPrice, error) { - p.mtx.RLock() - defer p.mtx.RUnlock() - - ticker, ok := p.tickers[key] - if !ok { - return types.TickerPrice{}, fmt.Errorf( - types.ErrTickerNotFound.Error(), - p.endpoints.Name, - key, - ) - } - - return ticker, nil -} - -func (p *CryptoProvider) getCandlePrices(key string) ([]types.CandlePrice, error) { - p.mtx.RLock() - defer p.mtx.RUnlock() - - candles, ok := p.candles[key] - if !ok { - return []types.CandlePrice{}, fmt.Errorf( - types.ErrCandleNotFound.Error(), - p.endpoints.Name, - key, - ) - } - - candleList := []types.CandlePrice{} - candleList = append(candleList, candles...) - - return candleList, nil -} - -func (p *CryptoProvider) messageReceived(messageType int, conn *WebsocketConnection, bz []byte) { - if messageType != websocket.TextMessage { - return - } - - var ( - heartbeatResp CryptoHeartbeatResponse - heartbeatErr error - tickerResp CryptoTickerResponse - tickerErr error - candleResp CryptoCandleResponse - candleErr error - ) - - // sometimes the message received is not a ticker or a candle response. - heartbeatErr = json.Unmarshal(bz, &heartbeatResp) - if heartbeatResp.Method == cryptoHeartbeatMethod { - p.pong(conn, heartbeatResp) - return - } - - tickerErr = json.Unmarshal(bz, &tickerResp) - if tickerResp.Result.Channel == cryptoTickerChannel { - for _, tickerPair := range tickerResp.Result.Data { - p.setTickerPair( - tickerResp.Result.InstrumentName, - tickerPair, - ) - telemetryWebsocketMessage(ProviderCrypto, MessageTypeTicker) - } - return - } - - candleErr = json.Unmarshal(bz, &candleResp) - if candleResp.Result.Channel == cryptoCandleChannel { - for _, candlePair := range candleResp.Result.Data { - p.setCandlePair( - candleResp.Result.InstrumentName, - candlePair, - ) - telemetryWebsocketMessage(ProviderCrypto, MessageTypeCandle) - } - return - } - - p.logger.Error(). - Int("length", len(bz)). - AnErr("heartbeat", heartbeatErr). - AnErr("ticker", tickerErr). - AnErr("candle", candleErr). - Msg("Error on receive message") -} - -// pongReceived return a heartbeat message when a "ping" is received and reset the -// recconnect ticker because the connection is alive. After connected to crypto.com's -// Websocket server, the server will send heartbeat periodically (30s interval). -// When client receives an heartbeat message, it must respond back with the -// public/respond-heartbeat method, using the same matching id, -// within 5 seconds, or the connection will break. -func (p *CryptoProvider) pong(conn *WebsocketConnection, heartbeatResp CryptoHeartbeatResponse) { - heartbeatReq := CryptoHeartbeatRequest{ - ID: heartbeatResp.ID, - Method: cryptoHeartbeatReqMethod, - } - - if err := conn.SendJSON(heartbeatReq); err != nil { - p.logger.Err(err).Msg("could not send pong message back") - } -} - -func (p *CryptoProvider) setTickerPair(symbol string, tickerPair CryptoTicker) { - p.mtx.Lock() - defer p.mtx.Unlock() - - tickerPrice, err := types.NewTickerPrice( - string(ProviderCrypto), - symbol, - tickerPair.LatestTrade, - tickerPair.Volume, - ) - if err != nil { - p.logger.Warn().Err(err).Msg("crypto: failed to parse ticker") - return - } - - p.tickers[symbol] = tickerPrice -} - -func (p *CryptoProvider) setCandlePair(symbol string, candlePair CryptoCandle) { - p.mtx.Lock() - defer p.mtx.Unlock() - - candle, err := types.NewCandlePrice( - string(ProviderCrypto), - symbol, - candlePair.Close, - candlePair.Volume, - SecondsToMilli(candlePair.Timestamp), - ) - if err != nil { - p.logger.Warn().Err(err).Msg("crypto: failed to parse candle") - return - } - - staleTime := PastUnixTime(providerCandlePeriod) - candleList := []types.CandlePrice{} - candleList = append(candleList, candle) - - for _, c := range p.candles[symbol] { - if staleTime < c.TimeStamp { - candleList = append(candleList, c) - } - } - - p.candles[symbol] = candleList -} - -// setSubscribedPairs sets N currency pairs to the map of subscribed pairs. -func (p *CryptoProvider) setSubscribedPairs(cps ...types.CurrencyPair) { - for _, cp := range cps { - p.subscribedPairs[cp.String()] = cp - } -} - -// GetAvailablePairs returns all pairs to which the provider can subscribe. -// ex.: map["ATOMUSDT" => {}, "UMEEUSDC" => {}]. -func (p *CryptoProvider) GetAvailablePairs() (map[string]struct{}, error) { - resp, err := http.Get(p.endpoints.Rest + cryptoRestPath) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var pairsSummary CryptoPairsSummary - if err := json.NewDecoder(resp.Body).Decode(&pairsSummary); err != nil { - return nil, err - } - - availablePairs := make(map[string]struct{}, len(pairsSummary.Result.Data)) - for _, pair := range pairsSummary.Result.Data { - splitInstName := strings.Split(pair.InstrumentName, "_") - if len(splitInstName) != 2 { - continue - } - - cp := types.CurrencyPair{ - Base: strings.ToUpper(splitInstName[0]), - Quote: strings.ToUpper(splitInstName[1]), - } - - availablePairs[cp.String()] = struct{}{} - } - - return availablePairs, nil -} - -// currencyPairToCryptoPair receives a currency pair and return crypto -// ticker symbol atomusdt@ticker. -func currencyPairToCryptoPair(cp types.CurrencyPair) string { - return strings.ToUpper(cp.Base + "_" + cp.Quote) -} - -// newCryptoSubscriptionMsg returns a new subscription Msg. -func newCryptoSubscriptionMsg(channels []string) CryptoSubscriptionMsg { - return CryptoSubscriptionMsg{ - ID: 1, - Method: "subscribe", - Params: CryptoSubscriptionParams{ - Channels: channels, - }, - Nonce: time.Now().UnixMilli(), - } -} diff --git a/price-feeder/oracle/provider/crypto_test.go b/price-feeder/oracle/provider/crypto_test.go deleted file mode 100644 index 9ed94b6f85..0000000000 --- a/price-feeder/oracle/provider/crypto_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package provider - -import ( - "context" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -func TestCryptoProvider_GetTickerPrices(t *testing.T) { - p, err := NewCryptoProvider( - context.TODO(), - zerolog.Nop(), - Endpoint{}, - types.CurrencyPair{Base: "ATOM", Quote: "USDT"}, - ) - require.NoError(t, err) - - t.Run("valid_request_single_ticker", func(t *testing.T) { - lastPrice := sdk.MustNewDecFromStr("34.69000000") - volume := sdk.MustNewDecFromStr("2396974.02000000") - - tickerMap := map[string]types.TickerPrice{} - tickerMap["ATOM_USDT"] = types.TickerPrice{ - Price: lastPrice, - Volume: volume, - } - - p.tickers = tickerMap - - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "ATOM", Quote: "USDT"}) - require.NoError(t, err) - require.Len(t, prices, 1) - require.Equal(t, lastPrice, prices["ATOMUSDT"].Price) - require.Equal(t, volume, prices["ATOMUSDT"].Volume) - }) - - t.Run("valid_request_multi_ticker", func(t *testing.T) { - lastPriceAtom := sdk.MustNewDecFromStr("34.69000000") - lastPriceLuna := sdk.MustNewDecFromStr("41.35000000") - volume := sdk.MustNewDecFromStr("2396974.02000000") - - tickerMap := map[string]types.TickerPrice{} - tickerMap["ATOM_USDT"] = types.TickerPrice{ - Price: lastPriceAtom, - Volume: volume, - } - - tickerMap["LUNA_USDT"] = types.TickerPrice{ - Price: lastPriceLuna, - Volume: volume, - } - - p.tickers = tickerMap - prices, err := p.GetTickerPrices( - types.CurrencyPair{Base: "ATOM", Quote: "USDT"}, - types.CurrencyPair{Base: "LUNA", Quote: "USDT"}, - ) - require.NoError(t, err) - require.Len(t, prices, 2) - require.Equal(t, lastPriceAtom, prices["ATOMUSDT"].Price) - require.Equal(t, volume, prices["ATOMUSDT"].Volume) - require.Equal(t, lastPriceLuna, prices["LUNAUSDT"].Price) - require.Equal(t, volume, prices["LUNAUSDT"].Volume) - }) - - t.Run("invalid_request_invalid_ticker", func(t *testing.T) { - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "FOO", Quote: "BAR"}) - require.Error(t, err) - require.Equal(t, "crypto has no ticker data for requested pairs: [FOOBAR]", err.Error()) - require.Nil(t, prices) - }) -} - -func TestCryptoProvider_GetCandlePrices(t *testing.T) { - p, err := NewCryptoProvider( - context.TODO(), - zerolog.Nop(), - Endpoint{}, - types.CurrencyPair{Base: "ATOM", Quote: "USDT"}, - ) - require.NoError(t, err) - - t.Run("valid_request_single_candle", func(t *testing.T) { - price := "34.689998626708984000" - volume := "2396974.000000000000000000" - timeStamp := int64(1000000) - - candle := CryptoCandle{ - Volume: volume, - Close: price, - Timestamp: timeStamp, - } - - p.setCandlePair("ATOM_USDT", candle) - - prices, err := p.GetCandlePrices(types.CurrencyPair{Base: "ATOM", Quote: "USDT"}) - require.NoError(t, err) - require.Len(t, prices, 1) - priceDec, _ := sdk.NewDecFromStr(price) - volumeDec, _ := sdk.NewDecFromStr(volume) - - require.Equal(t, priceDec, prices["ATOMUSDT"][0].Price) - require.Equal(t, volumeDec, prices["ATOMUSDT"][0].Volume) - require.Equal(t, timeStamp*1000, prices["ATOMUSDT"][0].TimeStamp) - }) - - t.Run("invalid_request_invalid_candle", func(t *testing.T) { - prices, err := p.GetCandlePrices(types.CurrencyPair{Base: "FOO", Quote: "BAR"}) - require.EqualError(t, err, "crypto has no candle data for requested pairs: [FOOBAR]") - require.Nil(t, prices) - }) -} - -func TestCryptoCurrencyPairToCryptoPair(t *testing.T) { - cp := types.CurrencyPair{Base: "ATOM", Quote: "USDT"} - cryptoSymbol := currencyPairToCryptoPair(cp) - require.Equal(t, cryptoSymbol, "ATOM_USDT") -} diff --git a/price-feeder/oracle/provider/gate.go b/price-feeder/oracle/provider/gate.go deleted file mode 100644 index f8827b469e..0000000000 --- a/price-feeder/oracle/provider/gate.go +++ /dev/null @@ -1,513 +0,0 @@ -package provider - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - "strings" - "sync" - "time" - - "github.com/gorilla/websocket" - "github.com/rs/zerolog" - - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -const ( - gateWSHost = "ws.gate.io" - gateWSPath = "/v4" - gatePingCheck = time.Second * 28 // should be < 30 - gateRestHost = "https://api.gateio.ws" - gateRestPath = "/api/v4/spot/currency_pairs" -) - -var _ Provider = (*GateProvider)(nil) - -type ( - // GateProvider defines an Oracle provider implemented by the Gate public - // API. - // - // REF: https://www.gate.io/docs/websocket/index.html - GateProvider struct { - wsc *WebsocketController - logger zerolog.Logger - reconnectTimer *time.Ticker - mtx sync.RWMutex - endpoints Endpoint - tickers map[string]GateTicker // Symbol => GateTicker - candles map[string][]GateCandle // Symbol => GateCandle - subscribedPairs map[string]types.CurrencyPair // Symbol => types.CurrencyPair - } - - GateTicker struct { - Last string `json:"last"` // Last traded price ex.: 43508.9 - Vol string `json:"baseVolume"` // Trading volume ex.: 11159.87127845 - Symbol string `json:"symbol"` // Symbol ex.: ATOM_UDST - } - - GateCandle struct { - Close string // Closing price - TimeStamp int64 // Unix timestamp - Volume string // Total candle volume - Symbol string // Total symbol - } - - // GateTickerSubscriptionMsg Msg to subscribe all the tickers channels. - GateTickerSubscriptionMsg struct { - Method string `json:"method"` // ticker.subscribe - Params []string `json:"params"` // streams to subscribe ex.: BOT_USDT - ID uint16 `json:"id"` // identify messages going back and forth - } - - // GateCandleSubscriptionMsg Msg to subscribe to a candle channel. - GateCandleSubscriptionMsg struct { - Method string `json:"method"` // ticker.subscribe - Params []interface{} `json:"params"` // streams to subscribe ex.: ["BOT_USDT": 1800] - ID uint16 `json:"id"` // identify messages going back and forth - } - - // GateTickerResponse defines the response body for gate tickers. - GateTickerResponse struct { - Method string `json:"method"` - Params []interface{} `json:"params"` - } - - // GateTickerResponse defines the response body for gate tickers. - // The Params response is a 2D slice of multiple candles and their data. - // - // REF: https://www.gate.io/docs/websocket/index.html - GateCandleResponse struct { - Method string `json:"method"` - Params [][]interface{} `json:"params"` - } - - // GateEvent defines the response body for gate subscription statuses. - GateEvent struct { - ID int `json:"id"` // subscription id, ex.: 123 - Result GateEventResult `json:"result"` // event result body - } - // GateEventResult defines the Result body for the GateEvent response. - GateEventResult struct { - Status string `json:"status"` // ex. "successful" - } - - // GatePairSummary defines the response structure for a Gate pair summary. - GatePairSummary struct { - Base string `json:"base"` - Quote string `json:"quote"` - } -) - -// NewGateProvider creates a new GateProvider. -func NewGateProvider( - ctx context.Context, - logger zerolog.Logger, - endpoints Endpoint, - pairs ...types.CurrencyPair, -) (*GateProvider, error) { - if endpoints.Name != ProviderGate { - endpoints = Endpoint{ - Name: ProviderGate, - Rest: gateRestHost, - Websocket: gateWSHost, - } - } - - wsURL := url.URL{ - Scheme: "wss", - Host: endpoints.Websocket, - Path: gateWSPath, - } - - gateLogger := logger.With().Str("provider", string(ProviderGate)).Logger() - - provider := &GateProvider{ - logger: gateLogger, - reconnectTimer: time.NewTicker(gatePingCheck), - endpoints: endpoints, - tickers: map[string]GateTicker{}, - candles: map[string][]GateCandle{}, - subscribedPairs: map[string]types.CurrencyPair{}, - } - - provider.setSubscribedPairs(pairs...) - - provider.wsc = NewWebsocketController( - ctx, - endpoints.Name, - wsURL, - provider.getSubscriptionMsgs(pairs...), - provider.messageReceived, - defaultPingDuration, - websocket.PingMessage, - gateLogger, - ) - provider.wsc.StartConnections() - - return provider, nil -} - -func (p *GateProvider) getSubscriptionMsgs(cps ...types.CurrencyPair) []interface{} { - subscriptionMsgs := make([]interface{}, 0, len(cps)*2) - for _, cp := range cps { - gatePair := currencyPairToGatePair(cp) - subscriptionMsgs = append(subscriptionMsgs, newGateTickerSubscription(gatePair)) - subscriptionMsgs = append(subscriptionMsgs, newGateCandleSubscription(gatePair)) - } - return subscriptionMsgs -} - -// SubscribeCurrencyPairs sends the new subscription messages to the websocket -// and adds them to the providers subscribedPairs array -func (p *GateProvider) SubscribeCurrencyPairs(cps ...types.CurrencyPair) { - p.mtx.Lock() - defer p.mtx.Unlock() - - newPairs := []types.CurrencyPair{} - for _, cp := range cps { - if _, ok := p.subscribedPairs[cp.String()]; !ok { - newPairs = append(newPairs, cp) - } - } - - newSubscriptionMsgs := p.getSubscriptionMsgs(newPairs...) - p.wsc.AddWebsocketConnection( - newSubscriptionMsgs, - p.messageReceived, - defaultPingDuration, - websocket.PingMessage, - ) - p.setSubscribedPairs(newPairs...) -} - -// GetTickerPrices returns the tickerPrices based on the provided pairs. -func (p *GateProvider) GetTickerPrices(pairs ...types.CurrencyPair) (map[string]types.TickerPrice, error) { - tickerPrices := make(map[string]types.TickerPrice, len(pairs)) - - tickerErrs := 0 - for _, cp := range pairs { - price, err := p.getTickerPrice(cp) - if err != nil { - p.logger.Warn().Err(err) - tickerErrs++ - continue - } - tickerPrices[cp.String()] = price - } - - if tickerErrs == len(pairs) { - return nil, fmt.Errorf( - types.ErrNoTickers.Error(), - p.endpoints.Name, - pairs, - ) - } - return tickerPrices, nil -} - -// GetCandlePrices returns the candlePrices based on the provided pairs. -func (p *GateProvider) GetCandlePrices(pairs ...types.CurrencyPair) (map[string][]types.CandlePrice, error) { - candlePrices := make(map[string][]types.CandlePrice, len(pairs)) - - candleErrs := 0 - for _, cp := range pairs { - prices, err := p.getCandlePrices(cp) - if err != nil { - p.logger.Warn().Err(err) - candleErrs++ - continue - } - candlePrices[cp.String()] = prices - } - - if candleErrs == len(pairs) { - return nil, fmt.Errorf( - types.ErrNoCandles.Error(), - p.endpoints.Name, - pairs, - ) - } - return candlePrices, nil -} - -func (p *GateProvider) getCandlePrices(cp types.CurrencyPair) ([]types.CandlePrice, error) { - p.mtx.RLock() - defer p.mtx.RUnlock() - - candles, ok := p.candles[currencyPairToGatePair(cp)] - if !ok { - return []types.CandlePrice{}, fmt.Errorf( - types.ErrCandleNotFound.Error(), - p.endpoints.Name, - cp.String(), - ) - } - - candleList := []types.CandlePrice{} - for _, candle := range candles { - cp, err := candle.toCandlePrice() - if err != nil { - return []types.CandlePrice{}, err - } - - candleList = append(candleList, cp) - } - - return candleList, nil -} - -func (p *GateProvider) getTickerPrice(cp types.CurrencyPair) (types.TickerPrice, error) { - p.mtx.RLock() - defer p.mtx.RUnlock() - - gp := currencyPairToGatePair(cp) - if tickerPair, ok := p.tickers[gp]; ok { - return tickerPair.toTickerPrice() - } - - return types.TickerPrice{}, fmt.Errorf( - types.ErrTickerNotFound.Error(), - p.endpoints.Name, - cp.String(), - ) -} - -func (p *GateProvider) messageReceived(_ int, _ *WebsocketConnection, bz []byte) { - var ( - gateEvent GateEvent - gateErr error - tickerErr error - candleErr error - ) - - gateErr = json.Unmarshal(bz, &gateEvent) - if gateErr == nil { - switch gateEvent.Result.Status { - case "success": - return - case "": - default: - return - } - } - - tickerErr = p.messageReceivedTickerPrice(bz) - if tickerErr == nil { - return - } - - candleErr = p.messageReceivedCandle(bz) - if candleErr == nil { - return - } - - p.logger.Error(). - Int("length", len(bz)). - AnErr("ticker", tickerErr). - AnErr("candle", candleErr). - AnErr("event", gateErr). - Msg("Error on receive message") -} - -// messageReceivedTickerPrice handles the ticker price msg. -// The provider response is a slice with different types at each index. -// -// REF: https://www.gate.io/docs/websocket/index.html -func (p *GateProvider) messageReceivedTickerPrice(bz []byte) error { - var tickerMessage GateTickerResponse - if err := json.Unmarshal(bz, &tickerMessage); err != nil { - return err - } - - if tickerMessage.Method != "ticker.update" { - return fmt.Errorf("message is not a ticker update") - } - - tickerBz, err := json.Marshal(tickerMessage.Params[1]) - if err != nil { - p.logger.Err(err).Msg("could not marshal ticker message") - return err - } - - var gateTicker GateTicker - if err := json.Unmarshal(tickerBz, &gateTicker); err != nil { - p.logger.Err(err).Msg("could not unmarshal ticker message") - return err - } - - symbol, ok := tickerMessage.Params[0].(string) - if !ok { - return fmt.Errorf("symbol should be a string") - } - gateTicker.Symbol = symbol - - p.setTickerPair(gateTicker) - telemetryWebsocketMessage(ProviderGate, MessageTypeTicker) - return nil -} - -// UnmarshalParams is a helper function which unmarshals the 2d slice of interfaces -// from a GateCandleResponse into the GateCandle. -func (candle *GateCandle) UnmarshalParams(params [][]interface{}) error { - var tmp []interface{} - - if len(params) == 0 { - return fmt.Errorf("no candles in response") - } - - // use the most recent candle - tmp = params[len(params)-1] - if len(tmp) != 8 { - return fmt.Errorf("wrong number of fields in candle") - } - - time := int64(tmp[0].(float64)) - if time == 0 { - return fmt.Errorf("time field must be a float") - } - candle.TimeStamp = time - - close, ok := tmp[1].(string) - if !ok { - return fmt.Errorf("close field must be a string") - } - candle.Close = close - - volume, ok := tmp[5].(string) - if !ok { - return fmt.Errorf("volume field must be a string") - } - candle.Volume = volume - - symbol, ok := tmp[7].(string) - if !ok { - return fmt.Errorf("symbol field must be a string") - } - candle.Symbol = symbol - - return nil -} - -// messageReceivedCandle handles the candle price msg. -// The provider response is a slice with different types at each index. -// -// REF: https://www.gate.io/docs/websocket/index.html -func (p *GateProvider) messageReceivedCandle(bz []byte) error { - var candleMessage GateCandleResponse - if err := json.Unmarshal(bz, &candleMessage); err != nil { - return err - } - - if candleMessage.Method != "kline.update" { - return fmt.Errorf("message is not a kline update") - } - - var gateCandle GateCandle - if err := gateCandle.UnmarshalParams(candleMessage.Params); err != nil { - return err - } - - p.setCandlePair(gateCandle) - telemetryWebsocketMessage(ProviderGate, MessageTypeCandle) - return nil -} - -func (p *GateProvider) setTickerPair(ticker GateTicker) { - p.mtx.Lock() - defer p.mtx.Unlock() - p.tickers[ticker.Symbol] = ticker -} - -func (p *GateProvider) setCandlePair(candle GateCandle) { - p.mtx.Lock() - defer p.mtx.Unlock() - // convert gate timestamp seconds -> milliseconds - candle.TimeStamp = SecondsToMilli(candle.TimeStamp) - staleTime := PastUnixTime(providerCandlePeriod) - candleList := []GateCandle{} - - candleList = append(candleList, candle) - for _, c := range p.candles[candle.Symbol] { - if staleTime < c.TimeStamp { - candleList = append(candleList, c) - } - } - p.candles[candle.Symbol] = candleList -} - -// setSubscribedPairs sets N currency pairs to the map of subscribed pairs. -func (p *GateProvider) setSubscribedPairs(cps ...types.CurrencyPair) { - for _, cp := range cps { - p.subscribedPairs[cp.String()] = cp - } -} - -// GetAvailablePairs returns all pairs to which the provider can subscribe. -func (p *GateProvider) GetAvailablePairs() (map[string]struct{}, error) { - resp, err := http.Get(p.endpoints.Rest + gateRestPath) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var pairsSummary []GatePairSummary - if err := json.NewDecoder(resp.Body).Decode(&pairsSummary); err != nil { - return nil, err - } - - availablePairs := make(map[string]struct{}, len(pairsSummary)) - for _, pair := range pairsSummary { - cp := types.CurrencyPair{ - Base: strings.ToUpper(pair.Base), - Quote: strings.ToUpper(pair.Quote), - } - availablePairs[cp.String()] = struct{}{} - } - - return availablePairs, nil -} - -func (ticker GateTicker) toTickerPrice() (types.TickerPrice, error) { - return types.NewTickerPrice(string(ProviderGate), ticker.Symbol, ticker.Last, ticker.Vol) -} - -func (candle GateCandle) toCandlePrice() (types.CandlePrice, error) { - return types.NewCandlePrice( - string(ProviderGate), - candle.Symbol, - candle.Close, - candle.Volume, - candle.TimeStamp, - ) -} - -// currencyPairToGatePair returns the expected pair for Gate -// ex.: "ATOM_USDT". -func currencyPairToGatePair(pair types.CurrencyPair) string { - return pair.Base + "_" + pair.Quote -} - -// newGateTickerSubscription returns a new subscription topic for tickers. -func newGateTickerSubscription(cp ...string) GateTickerSubscriptionMsg { - return GateTickerSubscriptionMsg{ - Method: "ticker.subscribe", - Params: cp, - ID: 1, - } -} - -// newGateCandleSubscription returns a new subscription topic for candles. -func newGateCandleSubscription(gatePair string) GateCandleSubscriptionMsg { - params := []interface{}{ - gatePair, // currency pair ex. "ATOM_USDT" - 60, // time interval in seconds - } - return GateCandleSubscriptionMsg{ - Method: "kline.subscribe", - Params: params, - ID: 2, - } -} diff --git a/price-feeder/oracle/provider/gate_test.go b/price-feeder/oracle/provider/gate_test.go deleted file mode 100644 index 9342ef28f4..0000000000 --- a/price-feeder/oracle/provider/gate_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package provider - -import ( - "context" - "encoding/json" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -func TestGateProvider_GetTickerPrices(t *testing.T) { - p, err := NewGateProvider( - context.TODO(), - zerolog.Nop(), - Endpoint{}, - types.CurrencyPair{Base: "ATOM", Quote: "USDT"}, - ) - require.NoError(t, err) - - t.Run("valid_request_single_ticker", func(t *testing.T) { - lastPrice := "34.69000000" - volume := "2396974.02000000" - - tickerMap := map[string]GateTicker{} - tickerMap["ATOM_USDT"] = GateTicker{ - Symbol: "ATOM_USDT", - Last: lastPrice, - Vol: volume, - } - - p.tickers = tickerMap - - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "ATOM", Quote: "USDT"}) - require.NoError(t, err) - require.Len(t, prices, 1) - require.Equal(t, sdk.MustNewDecFromStr(lastPrice), prices["ATOMUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr(volume), prices["ATOMUSDT"].Volume) - }) - - t.Run("valid_request_multi_ticker", func(t *testing.T) { - lastPriceAtom := "34.69000000" - lastPriceUMEE := "41.35000000" - volume := "2396974.02000000" - - tickerMap := map[string]GateTicker{} - tickerMap["ATOM_USDT"] = GateTicker{ - Symbol: "ATOM_USDT", - Last: lastPriceAtom, - Vol: volume, - } - - tickerMap["UMEE_USDT"] = GateTicker{ - Symbol: "UMEE_USDT", - Last: lastPriceUMEE, - Vol: volume, - } - - p.tickers = tickerMap - prices, err := p.GetTickerPrices( - types.CurrencyPair{Base: "ATOM", Quote: "USDT"}, - types.CurrencyPair{Base: "UMEE", Quote: "USDT"}, - ) - require.NoError(t, err) - require.Len(t, prices, 2) - require.Equal(t, sdk.MustNewDecFromStr(lastPriceAtom), prices["ATOMUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr(volume), prices["ATOMUSDT"].Volume) - require.Equal(t, sdk.MustNewDecFromStr(lastPriceUMEE), prices["UMEEUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr(volume), prices["UMEEUSDT"].Volume) - }) - - t.Run("invalid_request_invalid_ticker", func(t *testing.T) { - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "FOO", Quote: "BAR"}) - require.EqualError(t, err, "gate has no ticker data for requested pairs: [FOOBAR]") - require.Nil(t, prices) - }) -} - -func TestGateCurrencyPairToGatePair(t *testing.T) { - cp := types.CurrencyPair{Base: "ATOM", Quote: "USDT"} - GateSymbol := currencyPairToGatePair(cp) - require.Equal(t, GateSymbol, "ATOM_USDT") -} - -func TestGateProvider_getSubscriptionMsgs(t *testing.T) { - provider := &GateProvider{ - subscribedPairs: map[string]types.CurrencyPair{}, - } - cps := []types.CurrencyPair{ - {Base: "ATOM", Quote: "USDT"}, - } - subMsgs := provider.getSubscriptionMsgs(cps...) - - msg, _ := json.Marshal(subMsgs[0]) - require.Equal(t, "{\"method\":\"ticker.subscribe\",\"params\":[\"ATOM_USDT\"],\"id\":1}", string(msg)) - - msg, _ = json.Marshal(subMsgs[1]) - require.Equal(t, "{\"method\":\"kline.subscribe\",\"params\":[\"ATOM_USDT\",60],\"id\":2}", string(msg)) -} diff --git a/price-feeder/oracle/provider/huobi.go b/price-feeder/oracle/provider/huobi.go deleted file mode 100644 index 7d81b790da..0000000000 --- a/price-feeder/oracle/provider/huobi.go +++ /dev/null @@ -1,452 +0,0 @@ -package provider - -import ( - "bytes" - "compress/gzip" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "strconv" - "strings" - "sync" - "time" - - "github.com/gorilla/websocket" - "github.com/rs/zerolog" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -const ( - huobiWSHost = "api-aws.huobi.pro" - huobiWSPath = "/ws" - huobiReconnectTime = time.Minute * 2 - huobiRestHost = "https://api.huobi.pro" - huobiRestPath = "/market/tickers" -) - -var _ Provider = (*HuobiProvider)(nil) - -type ( - // HuobiProvider defines an Oracle provider implemented by the Huobi public - // API. - // - // REF: https://huobiapi.github.io/docs/spot/v1/en/#market-ticker - // REF: https://huobiapi.github.io/docs/spot/v1/en/#get-klines-candles - HuobiProvider struct { - wsc *WebsocketController - logger zerolog.Logger - mtx sync.RWMutex - endpoints Endpoint - tickers map[string]HuobiTicker // market.$symbol.ticker => HuobiTicker - candles map[string][]HuobiCandle // market.$symbol.kline.$period => HuobiCandle - subscribedPairs map[string]types.CurrencyPair // Symbol => types.CurrencyPair - } - - // HuobiTicker defines the response type for the channel and the tick object for a - // given ticker/symbol. - HuobiTicker struct { - CH string `json:"ch"` // Channel name. Format:market.$symbol.ticker - Tick HuobiTick `json:"tick"` - } - - // HuobiTick defines the response type for the last 24h market summary and the last - // traded price for a given ticker/symbol. - HuobiTick struct { - Vol float64 `json:"vol"` // Accumulated trading value of last 24 hours - LastPrice float64 `json:"lastPrice"` // Last traded price - } - - // HuobiCandle defines the response type for the channel and the tick object for a - // given ticker/symbol. - HuobiCandle struct { - CH string `json:"ch"` // Channel name. Format:market.$symbol.kline.$period - Tick HuobiCandleTick `json:"tick"` - } - - // HuobiCandleTick defines the response type for the candle. - HuobiCandleTick struct { - Close float64 `json:"close"` // Closing price during this period - TimeStamp int64 `json:"id"` // TimeStamp for this as an ID - Volume float64 `json:"vol"` // Volume during this period - } - - // HuobiSubscriptionMsg Msg to subscribe to one ticker channel at time. - HuobiSubscriptionMsg struct { - Sub string `json:"sub"` // channel to subscribe market.$symbol.ticker - } - - // HuobiSubscriptionResp the response structure for a Huobi subscription response - HuobiSubscriptionResp struct { - Status string `json:"status"` - } - - // HuobiPairsSummary defines the response structure for an Huobi pairs - // summary. - HuobiPairsSummary struct { - Data []HuobiPairData `json:"data"` - } - - // HuobiPairData defines the data response structure for an Huobi pair. - HuobiPairData struct { - Symbol string `json:"symbol"` - } -) - -// NewHuobiProvider returns a new Huobi provider with the WS connection and msg handler. -func NewHuobiProvider( - ctx context.Context, - logger zerolog.Logger, - endpoints Endpoint, - pairs ...types.CurrencyPair, -) (*HuobiProvider, error) { - if endpoints.Name != ProviderHuobi { - endpoints = Endpoint{ - Name: ProviderHuobi, - Rest: huobiRestHost, - Websocket: huobiWSHost, - } - } - - wsURL := url.URL{ - Scheme: "wss", - Host: endpoints.Websocket, - Path: huobiWSPath, - } - - huobiLogger := logger.With().Str("provider", string(ProviderHuobi)).Logger() - - provider := &HuobiProvider{ - logger: huobiLogger, - endpoints: endpoints, - tickers: map[string]HuobiTicker{}, - candles: map[string][]HuobiCandle{}, - subscribedPairs: map[string]types.CurrencyPair{}, - } - - provider.setSubscribedPairs(pairs...) - - provider.wsc = NewWebsocketController( - ctx, - endpoints.Name, - wsURL, - provider.getSubscriptionMsgs(pairs...), - provider.messageReceived, - disabledPingDuration, - websocket.PingMessage, - huobiLogger, - ) - provider.wsc.StartConnections() - - return provider, nil -} - -func (p *HuobiProvider) getSubscriptionMsgs(cps ...types.CurrencyPair) []interface{} { - subscriptionMsgs := make([]interface{}, 0, len(cps)*2) - for _, cp := range cps { - subscriptionMsgs = append(subscriptionMsgs, newHuobiTickerSubscriptionMsg(cp)) - subscriptionMsgs = append(subscriptionMsgs, newHuobiCandleSubscriptionMsg(cp)) - } - return subscriptionMsgs -} - -// SubscribeCurrencyPairs sends the new subscription messages to the websocket -// and adds them to the providers subscribedPairs array -func (p *HuobiProvider) SubscribeCurrencyPairs(cps ...types.CurrencyPair) { - p.mtx.Lock() - defer p.mtx.Unlock() - - newPairs := []types.CurrencyPair{} - for _, cp := range cps { - if _, ok := p.subscribedPairs[cp.String()]; !ok { - newPairs = append(newPairs, cp) - } - } - - newSubscriptionMsgs := p.getSubscriptionMsgs(newPairs...) - p.wsc.AddWebsocketConnection( - newSubscriptionMsgs, - p.messageReceived, - disabledPingDuration, - websocket.PingMessage, - ) - p.setSubscribedPairs(newPairs...) -} - -// GetTickerPrices returns the tickerPrices based on the provided pairs. -func (p *HuobiProvider) GetTickerPrices(pairs ...types.CurrencyPair) (map[string]types.TickerPrice, error) { - tickerPrices := make(map[string]types.TickerPrice, len(pairs)) - - tickerErrs := 0 - for _, cp := range pairs { - price, err := p.getTickerPrice(cp) - if err != nil { - p.logger.Warn().Err(err) - tickerErrs++ - continue - } - tickerPrices[cp.String()] = price - } - - if tickerErrs == len(pairs) { - return nil, fmt.Errorf( - types.ErrNoTickers.Error(), - p.endpoints.Name, - pairs, - ) - } - return tickerPrices, nil -} - -// GetCandlePrices returns the candlePrices based on the provided pairs. -func (p *HuobiProvider) GetCandlePrices(pairs ...types.CurrencyPair) (map[string][]types.CandlePrice, error) { - candlePrices := make(map[string][]types.CandlePrice, len(pairs)) - - candleErrs := 0 - for _, cp := range pairs { - prices, err := p.getCandlePrices(cp) - if err != nil { - p.logger.Warn().Err(err) - candleErrs++ - continue - } - candlePrices[cp.String()] = prices - } - - if candleErrs == len(pairs) { - return nil, fmt.Errorf( - types.ErrNoCandles.Error(), - p.endpoints.Name, - pairs, - ) - } - return candlePrices, nil -} - -// messageReceived handles the received data from the Huobi websocket. All return -// data of websocket Market APIs are compressed with GZIP so they need to be -// decompressed. -func (p *HuobiProvider) messageReceived(messageType int, conn *WebsocketConnection, bz []byte) { - if messageType != websocket.BinaryMessage { - return - } - - bz, err := decompressGzip(bz) - if err != nil { - p.logger.Err(err).Msg("failed to decompress gzipped message") - return - } - - var ( - tickerResp HuobiTicker - tickerErr error - candleResp HuobiCandle - candleErr error - subscribeResp HuobiSubscriptionResp - ) - - if bytes.Contains(bz, ping) { - p.pong(conn, bz) - return - } - - // sometimes the message received is not a ticker or a candle response. - tickerErr = json.Unmarshal(bz, &tickerResp) - if tickerResp.Tick.LastPrice != 0 { - p.setTickerPair(tickerResp) - telemetryWebsocketMessage(ProviderHuobi, MessageTypeTicker) - return - } - - candleErr = json.Unmarshal(bz, &candleResp) - if candleResp.Tick.Close != 0 { - p.setCandlePair(candleResp) - telemetryWebsocketMessage(ProviderHuobi, MessageTypeCandle) - return - } - - err = json.Unmarshal(bz, &subscribeResp) - if subscribeResp.Status == "ok" { - return - } - - p.logger.Error(). - Int("length", len(bz)). - AnErr("ticker", tickerErr). - AnErr("candle", candleErr). - AnErr("subscribeResp", err). - Msg("Error on receive message") -} - -// pongReceived return a heartbeat message when a "ping" is received and reset the -// reconnect ticker because the connection is alive. After connected to Huobi's -// Websocket server, the server will send heartbeat periodically (5s interval). -// When client receives an heartbeat message, it should respond with a matching -// "pong" message which has the same integer in it, e.g. {"ping": 1492420473027} -// and then the return pong message should be {"pong": 1492420473027}. -func (p *HuobiProvider) pong(conn *WebsocketConnection, bz []byte) { - var heartbeat struct { - Ping uint64 `json:"ping"` - } - - if err := json.Unmarshal(bz, &heartbeat); err != nil { - p.logger.Err(err).Msg("could not unmarshal heartbeat") - return - } - - if err := conn.SendJSON(struct { - Pong uint64 `json:"pong"` - }{Pong: heartbeat.Ping}); err != nil { - p.logger.Err(err).Msg("could not send pong message back") - } -} - -func (p *HuobiProvider) setTickerPair(ticker HuobiTicker) { - p.mtx.Lock() - defer p.mtx.Unlock() - p.tickers[ticker.CH] = ticker -} - -func (p *HuobiProvider) setCandlePair(candle HuobiCandle) { - p.mtx.Lock() - defer p.mtx.Unlock() - // convert huobi timestamp seconds -> milliseconds - candle.Tick.TimeStamp = SecondsToMilli(candle.Tick.TimeStamp) - staleTime := PastUnixTime(providerCandlePeriod) - candleList := []HuobiCandle{} - candleList = append(candleList, candle) - - for _, c := range p.candles[candle.CH] { - if staleTime < c.Tick.TimeStamp { - candleList = append(candleList, c) - } - } - p.candles[candle.CH] = candleList -} - -func (p *HuobiProvider) getTickerPrice(cp types.CurrencyPair) (types.TickerPrice, error) { - p.mtx.RLock() - defer p.mtx.RUnlock() - - ticker, ok := p.tickers[currencyPairToHuobiTickerPair(cp)] - if !ok { - return types.TickerPrice{}, fmt.Errorf( - types.ErrTickerNotFound.Error(), - p.endpoints.Name, - cp.String(), - ) - } - - return ticker.toTickerPrice() -} - -func (p *HuobiProvider) getCandlePrices(cp types.CurrencyPair) ([]types.CandlePrice, error) { - p.mtx.RLock() - defer p.mtx.RUnlock() - - candles, ok := p.candles[currencyPairToHuobiCandlePair(cp)] - if !ok { - return []types.CandlePrice{}, fmt.Errorf( - types.ErrCandleNotFound.Error(), - p.endpoints.Name, - cp.String(), - ) - } - - candleList := []types.CandlePrice{} - for _, candle := range candles { - cp, err := candle.toCandlePrice() - if err != nil { - return []types.CandlePrice{}, err - } - candleList = append(candleList, cp) - } - return candleList, nil -} - -// setSubscribedPairs sets N currency pairs to the map of subscribed pairs. -func (p *HuobiProvider) setSubscribedPairs(cps ...types.CurrencyPair) { - for _, cp := range cps { - p.subscribedPairs[cp.String()] = cp - } -} - -// GetAvailablePairs returns all pairs to which the provider can subscribe. -func (p *HuobiProvider) GetAvailablePairs() (map[string]struct{}, error) { - resp, err := http.Get(p.endpoints.Rest + huobiRestPath) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var pairsSummary HuobiPairsSummary - if err := json.NewDecoder(resp.Body).Decode(&pairsSummary); err != nil { - return nil, err - } - - availablePairs := make(map[string]struct{}, len(pairsSummary.Data)) - for _, pair := range pairsSummary.Data { - availablePairs[strings.ToUpper(pair.Symbol)] = struct{}{} - } - - return availablePairs, nil -} - -// decompressGzip uncompress gzip compressed messages. All data returned from the -// websocket Market APIs is compressed with GZIP, so it needs to be unzipped. -func decompressGzip(bz []byte) ([]byte, error) { - r, err := gzip.NewReader(bytes.NewReader(bz)) - if err != nil { - return nil, err - } - - return io.ReadAll(r) -} - -// toTickerPrice converts current HuobiTicker to TickerPrice. -func (ticker HuobiTicker) toTickerPrice() (types.TickerPrice, error) { - return types.NewTickerPrice( - string(ProviderHuobi), - ticker.CH, - strconv.FormatFloat(ticker.Tick.LastPrice, 'f', -1, 64), - strconv.FormatFloat(ticker.Tick.Vol, 'f', -1, 64), - ) -} - -func (candle HuobiCandle) toCandlePrice() (types.CandlePrice, error) { - return types.NewCandlePrice( - string(ProviderHuobi), - candle.CH, - strconv.FormatFloat(candle.Tick.Close, 'f', -1, 64), - strconv.FormatFloat(candle.Tick.Volume, 'f', -1, 64), - candle.Tick.TimeStamp, - ) -} - -// newHuobiTickerSubscriptionMsg returns a new ticker subscription Msg. -func newHuobiTickerSubscriptionMsg(cp types.CurrencyPair) HuobiSubscriptionMsg { - return HuobiSubscriptionMsg{ - Sub: currencyPairToHuobiTickerPair(cp), - } -} - -// currencyPairToHuobiTickerPair returns the channel name in the following format: -// "market.$symbol.ticker". -func currencyPairToHuobiTickerPair(cp types.CurrencyPair) string { - return strings.ToLower("market." + cp.String() + ".ticker") -} - -// newHuobiSubscriptionMsg returns a new candle subscription Msg. -func newHuobiCandleSubscriptionMsg(cp types.CurrencyPair) HuobiSubscriptionMsg { - return HuobiSubscriptionMsg{ - Sub: currencyPairToHuobiCandlePair(cp), - } -} - -// currencyPairToHuobiCandlePair returns the channel name in the following format: -// "market.$symbol.line.$period". -func currencyPairToHuobiCandlePair(cp types.CurrencyPair) string { - return strings.ToLower("market." + cp.String() + ".kline.1min") -} diff --git a/price-feeder/oracle/provider/huobi_test.go b/price-feeder/oracle/provider/huobi_test.go deleted file mode 100644 index 89599116bc..0000000000 --- a/price-feeder/oracle/provider/huobi_test.go +++ /dev/null @@ -1,107 +0,0 @@ -package provider - -import ( - "context" - "encoding/json" - "testing" - - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" - "github.com/umee-network/umee/v4/util/coin" -) - -func TestHuobiProvider_GetTickerPrices(t *testing.T) { - p, err := NewHuobiProvider( - context.TODO(), - zerolog.Nop(), - Endpoint{}, - types.CurrencyPair{Base: "ATOM", Quote: "USDT"}, - ) - require.NoError(t, err) - - t.Run("valid_request_single_ticker", func(t *testing.T) { - lastPrice := 34.69000000 - volume := 2396974.02000000 - - tickerMap := map[string]HuobiTicker{} - tickerMap["market.atomusdt.ticker"] = HuobiTicker{ - CH: "market.atomusdt.ticker", - Tick: HuobiTick{ - LastPrice: lastPrice, - Vol: volume, - }, - } - - p.tickers = tickerMap - - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "ATOM", Quote: "USDT"}) - require.NoError(t, err) - require.Len(t, prices, 1) - require.Equal(t, coin.MustNewDecFromFloat(lastPrice), prices["ATOMUSDT"].Price) - require.Equal(t, coin.MustNewDecFromFloat(volume), prices["ATOMUSDT"].Volume) - }) - - t.Run("valid_request_multi_ticker", func(t *testing.T) { - lastPriceAtom := 34.69000000 - lastPriceLuna := 41.35000000 - volume := 2396974.02000000 - - tickerMap := map[string]HuobiTicker{} - tickerMap["market.atomusdt.ticker"] = HuobiTicker{ - CH: "market.atomusdt.ticker", - Tick: HuobiTick{ - LastPrice: lastPriceAtom, - Vol: volume, - }, - } - - tickerMap["market.lunausdt.ticker"] = HuobiTicker{ - CH: "market.lunausdt.ticker", - Tick: HuobiTick{ - LastPrice: lastPriceLuna, - Vol: volume, - }, - } - - p.tickers = tickerMap - prices, err := p.GetTickerPrices( - types.CurrencyPair{Base: "ATOM", Quote: "USDT"}, - types.CurrencyPair{Base: "LUNA", Quote: "USDT"}, - ) - require.NoError(t, err) - require.Len(t, prices, 2) - require.Equal(t, coin.MustNewDecFromFloat(lastPriceAtom), prices["ATOMUSDT"].Price) - require.Equal(t, coin.MustNewDecFromFloat(volume), prices["ATOMUSDT"].Volume) - require.Equal(t, coin.MustNewDecFromFloat(lastPriceLuna), prices["LUNAUSDT"].Price) - require.Equal(t, coin.MustNewDecFromFloat(volume), prices["LUNAUSDT"].Volume) - }) - - t.Run("invalid_request_invalid_ticker", func(t *testing.T) { - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "FOO", Quote: "BAR"}) - require.EqualError(t, err, "huobi has no ticker data for requested pairs: [FOOBAR]") - require.Nil(t, prices) - }) -} - -func TestHuobiCurrencyPairToHuobiPair(t *testing.T) { - cp := types.CurrencyPair{Base: "ATOM", Quote: "USDT"} - binanceSymbol := currencyPairToHuobiTickerPair(cp) - require.Equal(t, binanceSymbol, "market.atomusdt.ticker") -} - -func TestHuobiProvider_getSubscriptionMsgs(t *testing.T) { - provider := &HuobiProvider{ - subscribedPairs: map[string]types.CurrencyPair{}, - } - cps := []types.CurrencyPair{ - {Base: "ATOM", Quote: "USDT"}, - } - subMsgs := provider.getSubscriptionMsgs(cps...) - - msg, _ := json.Marshal(subMsgs[0]) - require.Equal(t, "{\"sub\":\"market.atomusdt.ticker\"}", string(msg)) - - msg, _ = json.Marshal(subMsgs[1]) - require.Equal(t, "{\"sub\":\"market.atomusdt.kline.1min\"}", string(msg)) -} diff --git a/price-feeder/oracle/provider/kraken.go b/price-feeder/oracle/provider/kraken.go deleted file mode 100644 index 0f5dfa224f..0000000000 --- a/price-feeder/oracle/provider/kraken.go +++ /dev/null @@ -1,575 +0,0 @@ -package provider - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - "strconv" - "strings" - "sync" - "time" - - "github.com/gorilla/websocket" - "github.com/rs/zerolog" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -const ( - krakenWSHost = "ws.kraken.com" - KrakenRestHost = "https://api.kraken.com" - KrakenRestPath = "/0/public/AssetPairs" - krakenEventSystemStatus = "systemStatus" - krakenEventSubscriptionStatus = "subscriptionStatus" -) - -var _ Provider = (*KrakenProvider)(nil) - -type ( - // KrakenProvider defines an Oracle provider implemented by the Kraken public - // API. - // - // REF: https://docs.kraken.com/websockets/#overview - KrakenProvider struct { - wsc *WebsocketController - logger zerolog.Logger - mtx sync.RWMutex - endpoints Endpoint - tickers map[string]types.TickerPrice // Symbol => TickerPrice - candles map[string][]KrakenCandle // Symbol => KrakenCandle - subscribedPairs map[string]types.CurrencyPair // Symbol => types.CurrencyPair - } - - // KrakenTicker ticker price response from Kraken ticker channel. - // REF: https://docs.kraken.com/websockets/#message-ticker - KrakenTicker struct { - C []string `json:"c"` // Close with Price in the first position - V []string `json:"v"` // Volume with the value over last 24 hours in the second position - } - - // KrakenCandle candle response from Kraken candle channel. - // REF: https://docs.kraken.com/websockets/#message-ohlc - KrakenCandle struct { - Close string // Close price during this period - TimeStamp int64 // Linux epoch timestamp - Volume string // Volume during this period - Symbol string // Symbol for this candle - } - - // KrakenSubscriptionMsg Msg to subscribe to all the pairs at once. - KrakenSubscriptionMsg struct { - Event string `json:"event"` // subscribe/unsubscribe - Pair []string `json:"pair"` // Array of currency pairs ex.: "BTC/USDT", - Subscription KrakenSubscriptionChannel `json:"subscription"` // subscription object - } - - // KrakenSubscriptionChannel Msg with the channel name to be subscribed. - KrakenSubscriptionChannel struct { - Name string `json:"name"` // channel to be subscribed ex.: ticker - } - - // KrakenEvent wraps the possible events from the provider. - KrakenEvent struct { - Event string `json:"event"` // events from kraken ex.: systemStatus | subscriptionStatus - } - - // KrakenEventSubscriptionStatus parse the subscriptionStatus event message. - KrakenEventSubscriptionStatus struct { - Status string `json:"status"` // subscribed|unsubscribed|error - Pair string `json:"pair"` // Pair symbol base/quote ex.: "XBT/USD" - ErrorMessage string `json:"errorMessage"` // error description - } - - // KrakenPairsSummary defines the response structure for an Kraken pairs summary. - KrakenPairsSummary struct { - Result map[string]KrakenPairData `json:"result"` - } - - // KrakenPairData defines the data response structure for an Kraken pair. - KrakenPairData struct { - WsName string `json:"wsname"` - } -) - -// NewKrakenProvider returns a new Kraken provider with the WS connection and msg handler. -func NewKrakenProvider( - ctx context.Context, - logger zerolog.Logger, - endpoints Endpoint, - pairs ...types.CurrencyPair, -) (*KrakenProvider, error) { - if endpoints.Name != ProviderKraken { - endpoints = Endpoint{ - Name: ProviderKraken, - Rest: KrakenRestHost, - Websocket: krakenWSHost, - } - } - - wsURL := url.URL{ - Scheme: "wss", - Host: endpoints.Websocket, - } - - krakenLogger := logger.With().Str("provider", string(ProviderKraken)).Logger() - - provider := &KrakenProvider{ - logger: krakenLogger, - endpoints: endpoints, - tickers: map[string]types.TickerPrice{}, - candles: map[string][]KrakenCandle{}, - subscribedPairs: map[string]types.CurrencyPair{}, - } - - provider.setSubscribedPairs(pairs...) - - provider.wsc = NewWebsocketController( - ctx, - endpoints.Name, - wsURL, - provider.getSubscriptionMsgs(pairs...), - provider.messageReceived, - time.Duration(0), - websocket.PingMessage, - krakenLogger, - ) - provider.wsc.StartConnections() - - return provider, nil -} - -func (p *KrakenProvider) getSubscriptionMsgs(cps ...types.CurrencyPair) []interface{} { - subscriptionMsgs := make([]interface{}, 0, len(cps)*2) - for _, cp := range cps { - krakenPair := currencyPairToKrakenPair(cp) - subscriptionMsgs = append(subscriptionMsgs, newKrakenTickerSubscriptionMsg(krakenPair)) - subscriptionMsgs = append(subscriptionMsgs, newKrakenCandleSubscriptionMsg(krakenPair)) - } - return subscriptionMsgs -} - -// SubscribeCurrencyPairs sends the new subscription messages to the websocket -// and adds them to the providers subscribedPairs array -func (p *KrakenProvider) SubscribeCurrencyPairs(cps ...types.CurrencyPair) { - p.mtx.Lock() - defer p.mtx.Unlock() - - newPairs := []types.CurrencyPair{} - for _, cp := range cps { - if _, ok := p.subscribedPairs[cp.String()]; !ok { - newPairs = append(newPairs, cp) - } - } - - newSubscriptionMsgs := p.getSubscriptionMsgs(newPairs...) - p.wsc.AddWebsocketConnection( - newSubscriptionMsgs, - p.messageReceived, - time.Duration(0), - websocket.PingMessage, - ) - p.setSubscribedPairs(newPairs...) -} - -// GetTickerPrices returns the tickerPrices based on the provided pairs. -func (p *KrakenProvider) GetTickerPrices(pairs ...types.CurrencyPair) (map[string]types.TickerPrice, error) { - tickerPrices := make(map[string]types.TickerPrice, len(pairs)) - - tickerErrs := 0 - for _, cp := range pairs { - key := cp.String() - price, ok := p.tickers[key] - if !ok { - p.logger.Warn().Err(fmt.Errorf( - types.ErrTickerNotFound.Error(), - p.endpoints.Name, - key, - )) - tickerErrs++ - continue - } - tickerPrices[key] = price - } - - if tickerErrs == len(pairs) { - return nil, fmt.Errorf( - types.ErrNoTickers.Error(), - p.endpoints.Name, - pairs, - ) - } - return tickerPrices, nil -} - -// GetCandlePrices returns the candlePrices based on the provided pairs. -func (p *KrakenProvider) GetCandlePrices(pairs ...types.CurrencyPair) (map[string][]types.CandlePrice, error) { - candlePrices := make(map[string][]types.CandlePrice, len(pairs)) - - candleErrs := 0 - for _, cp := range pairs { - key := cp.String() - prices, err := p.getCandlePrices(key) - if err != nil { - p.logger.Warn().Err(err) - candleErrs++ - continue - } - candlePrices[key] = prices - } - - if candleErrs == len(pairs) { - return nil, fmt.Errorf( - types.ErrNoCandles.Error(), - p.endpoints.Name, - pairs, - ) - } - return candlePrices, nil -} - -func (candle KrakenCandle) toCandlePrice() (types.CandlePrice, error) { - return types.NewCandlePrice( - string(ProviderKraken), - candle.Symbol, - candle.Close, - candle.Volume, - candle.TimeStamp, - ) -} - -func (p *KrakenProvider) getCandlePrices(key string) ([]types.CandlePrice, error) { - p.mtx.RLock() - defer p.mtx.RUnlock() - - candles, ok := p.candles[key] - if !ok { - return []types.CandlePrice{}, fmt.Errorf( - types.ErrCandleNotFound.Error(), - p.endpoints.Name, - key, - ) - } - - candleList := []types.CandlePrice{} - for _, candle := range candles { - cp, err := candle.toCandlePrice() - if err != nil { - return []types.CandlePrice{}, err - } - candleList = append(candleList, cp) - } - return candleList, nil -} - -// messageReceived handles any message sent by the provider. -func (p *KrakenProvider) messageReceived(messageType int, _ *WebsocketConnection, bz []byte) { - if messageType != websocket.TextMessage { - return - } - - var ( - krakenEvent KrakenEvent - krakenErr error - tickerErr error - candleErr error - ) - - krakenErr = json.Unmarshal(bz, &krakenEvent) - if krakenErr == nil { - switch krakenEvent.Event { - case krakenEventSystemStatus: - return - case krakenEventSubscriptionStatus: - p.messageReceivedSubscriptionStatus(bz) - return - } - return - } - - tickerErr = p.messageReceivedTickerPrice(bz) - if tickerErr == nil { - return - } - - candleErr = p.messageReceivedCandle(bz) - if candleErr == nil { - return - } - - p.logger.Error(). - Int("length", len(bz)). - AnErr("ticker", tickerErr). - AnErr("candle", candleErr). - AnErr("event", krakenErr). - Msg("Error on receive message") -} - -// messageReceivedTickerPrice handles the ticker price msg. -func (p *KrakenProvider) messageReceivedTickerPrice(bz []byte) error { - // the provider response is an array with different types at each index - // kraken documentation https://docs.kraken.com/websockets/#message-ticker - var tickerMessage []interface{} - if err := json.Unmarshal(bz, &tickerMessage); err != nil { - return err - } - - if len(tickerMessage) != 4 { - return fmt.Errorf("received an unexpected structure") - } - - channelName, ok := tickerMessage[2].(string) - if !ok || channelName != "ticker" { - return fmt.Errorf("received an unexpected channel name") - } - - tickerBz, err := json.Marshal(tickerMessage[1]) - if err != nil { - p.logger.Err(err).Msg("could not marshal ticker message") - return err - } - - var krakenTicker KrakenTicker - if err := json.Unmarshal(tickerBz, &krakenTicker); err != nil { - p.logger.Err(err).Msg("could not unmarshal ticker message") - return err - } - - krakenPair, ok := tickerMessage[3].(string) - if !ok { - p.logger.Debug().Msg("received an unexpected pair") - return err - } - - krakenPair = normalizeKrakenBTCPair(krakenPair) - currencyPairSymbol := krakenPairToCurrencyPairSymbol(krakenPair) - - tickerPrice, err := krakenTicker.toTickerPrice(currencyPairSymbol) - if err != nil { - p.logger.Err(err).Msg("could not parse kraken ticker to ticker price") - return err - } - - p.setTickerPair(currencyPairSymbol, tickerPrice) - telemetryWebsocketMessage(ProviderKraken, MessageTypeTicker) - return nil -} - -func (candle *KrakenCandle) UnmarshalJSON(buf []byte) error { - var tmp []interface{} - if err := json.Unmarshal(buf, &tmp); err != nil { - return err - } - if len(tmp) != 9 { - return fmt.Errorf("wrong number of fields in candle") - } - - // timestamps come as a float string - time, ok := tmp[1].(string) - if !ok { - return fmt.Errorf("time field must be a string") - } - timeFloat, err := strconv.ParseFloat(time, 64) - if err != nil { - return fmt.Errorf("unable to convert time to float") - } - candle.TimeStamp = int64(timeFloat) - - close, ok := tmp[5].(string) - if !ok { - return fmt.Errorf("close field must be a string") - } - candle.Close = close - - volume, ok := tmp[7].(string) - if !ok { - return fmt.Errorf("volume field must be a string") - } - candle.Volume = volume - - return nil -} - -// messageReceivedCandle handles the candle msg. -func (p *KrakenProvider) messageReceivedCandle(bz []byte) error { - // the provider response is an array with different types at each index - // kraken documentation https://docs.kraken.com/websockets/#message-ohlc - var candleMessage []interface{} - if err := json.Unmarshal(bz, &candleMessage); err != nil { - return err - } - - if len(candleMessage) != 4 { - return fmt.Errorf("received something different than candle") - } - - channelName, ok := candleMessage[2].(string) - if !ok || channelName != "ohlc-1" { - return fmt.Errorf("received an unexpected channel name") - } - - tickerBz, err := json.Marshal(candleMessage[1]) - if err != nil { - return fmt.Errorf("could not marshal candle message") - } - - var krakenCandle KrakenCandle - if err := krakenCandle.UnmarshalJSON(tickerBz); err != nil { - return err - } - - krakenPair, ok := candleMessage[3].(string) - if !ok { - return fmt.Errorf("received an unexpected pair") - } - - krakenPair = normalizeKrakenBTCPair(krakenPair) - currencyPairSymbol := krakenPairToCurrencyPairSymbol(krakenPair) - krakenCandle.Symbol = currencyPairSymbol - - telemetryWebsocketMessage(ProviderKraken, MessageTypeCandle) - p.setCandlePair(krakenCandle) - return nil -} - -// messageReceivedSubscriptionStatus handle the subscription status message -// sent by the provider. -func (p *KrakenProvider) messageReceivedSubscriptionStatus(bz []byte) { - var subscriptionStatus KrakenEventSubscriptionStatus - if err := json.Unmarshal(bz, &subscriptionStatus); err != nil { - p.logger.Err(err).Msg("provider could not unmarshal KrakenEventSubscriptionStatus") - return - } - - switch subscriptionStatus.Status { - case "error": - p.logger.Error().Msg(subscriptionStatus.ErrorMessage) - p.removeSubscribedTickers(krakenPairToCurrencyPairSymbol(subscriptionStatus.Pair)) - return - case "unsubscribed": - p.logger.Debug().Msgf("ticker %s was unsubscribed", subscriptionStatus.Pair) - p.removeSubscribedTickers(krakenPairToCurrencyPairSymbol(subscriptionStatus.Pair)) - return - } -} - -// setTickerPair sets an ticker to the map thread safe by the mutex. -func (p *KrakenProvider) setTickerPair(symbol string, ticker types.TickerPrice) { - p.mtx.Lock() - defer p.mtx.Unlock() - p.tickers[symbol] = ticker -} - -func (p *KrakenProvider) setCandlePair(candle KrakenCandle) { - p.mtx.Lock() - defer p.mtx.Unlock() - // convert kraken timestamp seconds -> milliseconds - candle.TimeStamp = SecondsToMilli(candle.TimeStamp) - staleTime := PastUnixTime(providerCandlePeriod) - candleList := []KrakenCandle{} - - candleList = append(candleList, candle) - for _, c := range p.candles[candle.Symbol] { - if staleTime < c.TimeStamp { - candleList = append(candleList, c) - } - } - p.candles[candle.Symbol] = candleList -} - -// setSubscribedPairs sets N currency pairs to the map of subscribed pairs. -func (p *KrakenProvider) setSubscribedPairs(cps ...types.CurrencyPair) { - for _, cp := range cps { - p.subscribedPairs[cp.String()] = cp - } -} - -// removeSubscribedTickers delete N pairs from the subscribed map. -func (p *KrakenProvider) removeSubscribedTickers(tickerSymbols ...string) { - p.mtx.Lock() - defer p.mtx.Unlock() - - for _, tickerSymbol := range tickerSymbols { - delete(p.subscribedPairs, tickerSymbol) - } -} - -// GetAvailablePairs returns all pairs to which the provider can subscribe. -func (p *KrakenProvider) GetAvailablePairs() (map[string]struct{}, error) { - resp, err := http.Get(p.endpoints.Rest + KrakenRestPath) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var pairsSummary KrakenPairsSummary - if err := json.NewDecoder(resp.Body).Decode(&pairsSummary); err != nil { - return nil, err - } - - availablePairs := make(map[string]struct{}, len(pairsSummary.Result)) - for _, pair := range pairsSummary.Result { - splitPair := strings.Split(pair.WsName, "/") - if len(splitPair) != 2 { - continue - } - - cp := types.CurrencyPair{ - Base: strings.ToUpper(splitPair[0]), - Quote: strings.ToUpper(splitPair[1]), - } - availablePairs[cp.String()] = struct{}{} - } - - return availablePairs, nil -} - -// toTickerPrice return a TickerPrice based on the KrakenTicker. -func (ticker KrakenTicker) toTickerPrice(symbol string) (types.TickerPrice, error) { - if len(ticker.C) != 2 || len(ticker.V) != 2 { - return types.TickerPrice{}, fmt.Errorf("error converting KrakenTicker to TickerPrice") - } - // ticker.C has the Price in the first position. - // ticker.V has the totla Value over last 24 hours in the second position. - return types.NewTickerPrice(string(ProviderKraken), symbol, ticker.C[0], ticker.V[1]) -} - -// newKrakenTickerSubscriptionMsg returns a new subscription Msg. -func newKrakenTickerSubscriptionMsg(pairs ...string) KrakenSubscriptionMsg { - return KrakenSubscriptionMsg{ - Event: "subscribe", - Pair: pairs, - Subscription: KrakenSubscriptionChannel{ - Name: "ticker", - }, - } -} - -// newKrakenSubscriptionMsg returns a new subscription Msg. -func newKrakenCandleSubscriptionMsg(pairs ...string) KrakenSubscriptionMsg { - return KrakenSubscriptionMsg{ - Event: "subscribe", - Pair: pairs, - Subscription: KrakenSubscriptionChannel{ - Name: "ohlc", - }, - } -} - -// krakenPairToCurrencyPairSymbol receives a kraken pair formatted -// ex.: ATOM/USDT and return currencyPair Symbol ATOMUSDT. -func krakenPairToCurrencyPairSymbol(krakenPair string) string { - return strings.ReplaceAll(krakenPair, "/", "") -} - -// currencyPairToKrakenPair receives a currency pair -// and return kraken ticker symbol ATOM/USDT. -func currencyPairToKrakenPair(cp types.CurrencyPair) string { - return strings.ToUpper(cp.Base + "/" + cp.Quote) -} - -// normalizeKrakenBTCPair changes XBT pairs to BTC, -// since other providers list bitcoin as BTC. -func normalizeKrakenBTCPair(ticker string) string { - return strings.Replace(ticker, "XBT", "BTC", 1) -} diff --git a/price-feeder/oracle/provider/kraken_test.go b/price-feeder/oracle/provider/kraken_test.go deleted file mode 100644 index 24db328ef8..0000000000 --- a/price-feeder/oracle/provider/kraken_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package provider - -import ( - "context" - "encoding/json" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -func TestKrakenProvider_GetTickerPrices(t *testing.T) { - p, err := NewKrakenProvider( - context.TODO(), - zerolog.Nop(), - Endpoint{}, - types.CurrencyPair{Base: "BTC", Quote: "USDT"}, - ) - require.NoError(t, err) - - t.Run("valid_request_single_ticker", func(t *testing.T) { - lastPrice := sdk.MustNewDecFromStr("34.69000000") - volume := sdk.MustNewDecFromStr("2396974.02000000") - - tickerMap := map[string]types.TickerPrice{} - tickerMap["ATOMUSDT"] = types.TickerPrice{ - Price: lastPrice, - Volume: volume, - } - - p.tickers = tickerMap - - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "ATOM", Quote: "USDT"}) - require.NoError(t, err) - require.Len(t, prices, 1) - require.Equal(t, lastPrice, prices["ATOMUSDT"].Price) - require.Equal(t, volume, prices["ATOMUSDT"].Volume) - }) - - t.Run("valid_request_multi_ticker", func(t *testing.T) { - lastPriceAtom := sdk.MustNewDecFromStr("34.69000000") - lastPriceLuna := sdk.MustNewDecFromStr("41.35000000") - volume := sdk.MustNewDecFromStr("2396974.02000000") - - tickerMap := map[string]types.TickerPrice{} - tickerMap["ATOMUSDT"] = types.TickerPrice{ - Price: lastPriceAtom, - Volume: volume, - } - - tickerMap["LUNAUSDT"] = types.TickerPrice{ - Price: lastPriceLuna, - Volume: volume, - } - - p.tickers = tickerMap - prices, err := p.GetTickerPrices( - types.CurrencyPair{Base: "ATOM", Quote: "USDT"}, - types.CurrencyPair{Base: "LUNA", Quote: "USDT"}, - ) - require.NoError(t, err) - require.Len(t, prices, 2) - require.Equal(t, lastPriceAtom, prices["ATOMUSDT"].Price) - require.Equal(t, volume, prices["ATOMUSDT"].Volume) - require.Equal(t, lastPriceLuna, prices["LUNAUSDT"].Price) - require.Equal(t, volume, prices["LUNAUSDT"].Volume) - }) - - t.Run("invalid_request_invalid_ticker", func(t *testing.T) { - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "FOO", Quote: "BAR"}) - require.EqualError(t, err, "kraken has no ticker data for requested pairs: [FOOBAR]") - require.Nil(t, prices) - }) -} - -func TestKrakenPairToCurrencyPairSymbol(t *testing.T) { - cp := types.CurrencyPair{Base: "ATOM", Quote: "USDT"} - currencyPairSymbol := krakenPairToCurrencyPairSymbol("ATOM/USDT") - require.Equal(t, cp.String(), currencyPairSymbol) -} - -func TestKrakenCurrencyPairToKrakenPair(t *testing.T) { - cp := types.CurrencyPair{Base: "ATOM", Quote: "USDT"} - krakenSymbol := currencyPairToKrakenPair(cp) - require.Equal(t, krakenSymbol, "ATOM/USDT") -} - -func TestNormalizeKrakenBTCPair(t *testing.T) { - btcSymbol := normalizeKrakenBTCPair("XBT/USDT") - require.Equal(t, btcSymbol, "BTC/USDT") - - atomSymbol := normalizeKrakenBTCPair("ATOM/USDT") - require.Equal(t, atomSymbol, "ATOM/USDT") -} - -func TestKrakenProvider_getSubscriptionMsgs(t *testing.T) { - provider := &KrakenProvider{ - subscribedPairs: map[string]types.CurrencyPair{}, - } - cps := []types.CurrencyPair{ - {Base: "ATOM", Quote: "USDT"}, - } - subMsgs := provider.getSubscriptionMsgs(cps...) - - msg, _ := json.Marshal(subMsgs[0]) - require.Equal(t, "{\"event\":\"subscribe\",\"pair\":[\"ATOM/USDT\"],\"subscription\":{\"name\":\"ticker\"}}", string(msg)) - - msg, _ = json.Marshal(subMsgs[1]) - require.Equal(t, "{\"event\":\"subscribe\",\"pair\":[\"ATOM/USDT\"],\"subscription\":{\"name\":\"ohlc\"}}", string(msg)) -} diff --git a/price-feeder/oracle/provider/mexc.go b/price-feeder/oracle/provider/mexc.go deleted file mode 100644 index 68f50a9d0f..0000000000 --- a/price-feeder/oracle/provider/mexc.go +++ /dev/null @@ -1,388 +0,0 @@ -package provider - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - "strings" - "sync" - - "github.com/gorilla/websocket" - "github.com/rs/zerolog" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" - - "github.com/umee-network/umee/v4/util/coin" -) - -const ( - mexcWSHost = "wbs.mexc.com" - mexcWSPath = "/raw/ws" - mexcRestHost = "https://www.mexc.com" - mexcRestPath = "/open/api/v2/market/ticker" -) - -var _ Provider = (*MexcProvider)(nil) - -type ( - // MexcProvider defines an Oracle provider implemented by the Mexc public - // API. - // - // REF: https://mxcdevelop.github.io/apidocs/spot_v2_en/#ticker-information - // REF: https://mxcdevelop.github.io/apidocs/spot_v2_en/#k-line - // REF: https://mxcdevelop.github.io/apidocs/spot_v2_en/#overview - MexcProvider struct { - wsc *WebsocketController - logger zerolog.Logger - mtx sync.RWMutex - endpoints Endpoint - tickers map[string]types.TickerPrice // Symbol => TickerPrice - candles map[string][]types.CandlePrice // Symbol => CandlePrice - subscribedPairs map[string]types.CurrencyPair // Symbol => types.CurrencyPair - } - - // MexcTickerResponse is the ticker price response object. - MexcTickerResponse struct { - Symbol map[string]MexcTicker `json:"data"` // e.x. ATOM_USDT - } - MexcTicker struct { - LastPrice float64 `json:"p"` // Last price ex.: 0.0025 - Volume float64 `json:"v"` // Total traded base asset volume ex.: 1000 - } - - // MexcCandle is the candle websocket response object. - MexcCandleResponse struct { - Symbol string `json:"symbol"` // Symbol ex.: ATOM_USDT - Metadata MexcCandle `json:"data"` // Metadata for candle - } - MexcCandle struct { - Close float64 `json:"c"` // Price at close - TimeStamp int64 `json:"t"` // Close time in unix epoch ex.: 1645756200000 - Volume float64 `json:"v"` // Volume during period - } - - // MexcCandleSubscription Msg to subscribe all the candle channels. - MexcCandleSubscription struct { - OP string `json:"op"` // kline - Symbol string `json:"symbol"` // streams to subscribe ex.: atom_usdt - Interval string `json:"interval"` // Min1、Min5、Min15、Min30 - } - - // MexcTickerSubscription Msg to subscribe all the ticker channels. - MexcTickerSubscription struct { - OP string `json:"op"` // kline - } - - // MexcPairSummary defines the response structure for a Mexc pair - // summary. - MexcPairSummary struct { - Symbol string `json:"symbol"` - } -) - -func NewMexcProvider( - ctx context.Context, - logger zerolog.Logger, - endpoints Endpoint, - pairs ...types.CurrencyPair, -) (*MexcProvider, error) { - if (endpoints.Name) != ProviderMexc { - endpoints = Endpoint{ - Name: ProviderMexc, - Rest: mexcRestHost, - Websocket: mexcWSHost, - } - } - - wsURL := url.URL{ - Scheme: "wss", - Host: endpoints.Websocket, - Path: mexcWSPath, - } - - mexcLogger := logger.With().Str("provider", "mexc").Logger() - - provider := &MexcProvider{ - logger: mexcLogger, - endpoints: endpoints, - tickers: map[string]types.TickerPrice{}, - candles: map[string][]types.CandlePrice{}, - subscribedPairs: map[string]types.CurrencyPair{}, - } - - provider.setSubscribedPairs(pairs...) - - provider.wsc = NewWebsocketController( - ctx, - endpoints.Name, - wsURL, - provider.getSubscriptionMsgs(pairs...), - provider.messageReceived, - defaultPingDuration, - websocket.PingMessage, - mexcLogger, - ) - provider.wsc.StartConnections() - - return provider, nil -} - -func (p *MexcProvider) getSubscriptionMsgs(cps ...types.CurrencyPair) []interface{} { - subscriptionMsgs := make([]interface{}, 0, len(cps)+1) - for _, cp := range cps { - mexcPair := currencyPairToMexcPair(cp) - subscriptionMsgs = append(subscriptionMsgs, newMexcCandleSubscriptionMsg(mexcPair)) - } - subscriptionMsgs = append(subscriptionMsgs, newMexcTickerSubscriptionMsg()) - return subscriptionMsgs -} - -// SubscribeCurrencyPairs sends the new subscription messages to the websocket -// and adds them to the providers subscribedPairs array -func (p *MexcProvider) SubscribeCurrencyPairs(cps ...types.CurrencyPair) { - p.mtx.Lock() - defer p.mtx.Unlock() - - newPairs := []types.CurrencyPair{} - for _, cp := range cps { - if _, ok := p.subscribedPairs[cp.String()]; !ok { - newPairs = append(newPairs, cp) - } - } - - newSubscriptionMsgs := p.getSubscriptionMsgs(newPairs...) - p.wsc.AddWebsocketConnection( - newSubscriptionMsgs, - p.messageReceived, - defaultPingDuration, - websocket.PingMessage, - ) - p.setSubscribedPairs(newPairs...) -} - -// GetTickerPrices returns the tickerPrices based on the provided pairs. -func (p *MexcProvider) GetTickerPrices(pairs ...types.CurrencyPair) (map[string]types.TickerPrice, error) { - tickerPrices := make(map[string]types.TickerPrice, len(pairs)) - - tickerErrs := 0 - for _, cp := range pairs { - key := currencyPairToMexcPair(cp) - price, err := p.getTickerPrice(key) - if err != nil { - p.logger.Warn().Err(err) - tickerErrs++ - continue - } - tickerPrices[cp.String()] = price - } - - if tickerErrs == len(pairs) { - return nil, fmt.Errorf( - types.ErrNoTickers.Error(), - p.endpoints.Name, - pairs, - ) - } - return tickerPrices, nil -} - -// GetCandlePrices returns the candlePrices based on the provided pairs. -func (p *MexcProvider) GetCandlePrices(pairs ...types.CurrencyPair) (map[string][]types.CandlePrice, error) { - candlePrices := make(map[string][]types.CandlePrice, len(pairs)) - - candleErrs := 0 - for _, cp := range pairs { - key := currencyPairToMexcPair(cp) - prices, err := p.getCandlePrices(key) - if err != nil { - p.logger.Warn().Err(err) - candleErrs++ - continue - } - candlePrices[cp.String()] = prices - } - - if candleErrs == len(pairs) { - return nil, fmt.Errorf( - types.ErrNoCandles.Error(), - p.endpoints.Name, - pairs, - ) - } - return candlePrices, nil -} - -func (p *MexcProvider) getTickerPrice(key string) (types.TickerPrice, error) { - p.mtx.RLock() - defer p.mtx.RUnlock() - - ticker, ok := p.tickers[key] - if !ok { - return types.TickerPrice{}, fmt.Errorf( - types.ErrTickerNotFound.Error(), - p.endpoints.Name, - key, - ) - } - - return ticker, nil -} - -func (p *MexcProvider) getCandlePrices(key string) ([]types.CandlePrice, error) { - p.mtx.RLock() - defer p.mtx.RUnlock() - - candles, ok := p.candles[key] - if !ok { - return []types.CandlePrice{}, fmt.Errorf( - types.ErrCandleNotFound.Error(), - p.endpoints.Name, - key, - ) - } - - candleList := []types.CandlePrice{} - candleList = append(candleList, candles...) - - return candleList, nil -} - -func (p *MexcProvider) messageReceived(_ int, _ *WebsocketConnection, bz []byte) { - var ( - tickerResp MexcTickerResponse - tickerErr error - candleResp MexcCandleResponse - candleErr error - ) - - tickerErr = json.Unmarshal(bz, &tickerResp) - for _, cp := range p.subscribedPairs { - mexcPair := currencyPairToMexcPair(cp) - if tickerResp.Symbol[mexcPair].LastPrice != 0 { - p.setTickerPair( - mexcPair, - tickerResp.Symbol[mexcPair], - ) - telemetryWebsocketMessage(ProviderMexc, MessageTypeTicker) - return - } - } - - candleErr = json.Unmarshal(bz, &candleResp) - if candleResp.Metadata.Close != 0 { - p.setCandlePair(candleResp) - telemetryWebsocketMessage(ProviderMexc, MessageTypeCandle) - return - } - - if tickerErr != nil || candleErr != nil { - p.logger.Error(). - Int("length", len(bz)). - AnErr("ticker", tickerErr). - AnErr("candle", candleErr). - Msg("mexc: Error on receive message") - } -} - -func (p *MexcProvider) setTickerPair(symbol string, ticker MexcTicker) { - p.mtx.Lock() - defer p.mtx.Unlock() - - price, err := coin.NewDecFromFloat(ticker.LastPrice) - if err != nil { - p.logger.Warn().Err(err).Msg("mexc: failed to parse ticker price") - } - volume, err := coin.NewDecFromFloat(ticker.Volume) - if err != nil { - p.logger.Warn().Err(err).Msg("mexc: failed to parse ticker volume") - } - - p.tickers[symbol] = types.TickerPrice{ - Price: price, - Volume: volume, - } -} - -func (p *MexcProvider) setCandlePair(candleResp MexcCandleResponse) { - p.mtx.Lock() - defer p.mtx.Unlock() - - close, err := coin.NewDecFromFloat(candleResp.Metadata.Close) - if err != nil { - p.logger.Warn().Err(err).Msg("mexc: failed to parse candle close") - } - volume, err := coin.NewDecFromFloat(candleResp.Metadata.Volume) - if err != nil { - p.logger.Warn().Err(err).Msg("mexc: failed to parse candle volume") - } - candle := types.CandlePrice{ - Price: close, - Volume: volume, - // convert seconds -> milli - TimeStamp: SecondsToMilli(candleResp.Metadata.TimeStamp), - } - - staleTime := PastUnixTime(providerCandlePeriod) - candleList := []types.CandlePrice{} - candleList = append(candleList, candle) - - for _, c := range p.candles[candleResp.Symbol] { - if staleTime < c.TimeStamp { - candleList = append(candleList, c) - } - } - - p.candles[candleResp.Symbol] = candleList -} - -// setSubscribedPairs sets N currency pairs to the map of subscribed pairs. -func (p *MexcProvider) setSubscribedPairs(cps ...types.CurrencyPair) { - for _, cp := range cps { - p.subscribedPairs[cp.String()] = cp - } -} - -// GetAvailablePairs returns all pairs to which the provider can subscribe. -// ex.: map["ATOMUSDT" => {}, "UMEEUSDC" => {}]. -func (p *MexcProvider) GetAvailablePairs() (map[string]struct{}, error) { - resp, err := http.Get(p.endpoints.Rest + mexcRestPath) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var pairsSummary []MexcPairSummary - if err := json.NewDecoder(resp.Body).Decode(&pairsSummary); err != nil { - return nil, err - } - - availablePairs := make(map[string]struct{}, len(pairsSummary)) - for _, pairName := range pairsSummary { - availablePairs[strings.ToUpper(pairName.Symbol)] = struct{}{} - } - - return availablePairs, nil -} - -// currencyPairToMexcPair receives a currency pair and return mexc -// ticker symbol atomusdt@ticker. -func currencyPairToMexcPair(cp types.CurrencyPair) string { - return strings.ToUpper(cp.Base + "_" + cp.Quote) -} - -// newMexcCandleSubscriptionMsg returns a new candle subscription Msg. -func newMexcCandleSubscriptionMsg(param string) MexcCandleSubscription { - return MexcCandleSubscription{ - OP: "sub.kline", - Symbol: param, - Interval: "Min1", - } -} - -// newMexcTickerSubscriptionMsg returns a new ticker subscription Msg. -func newMexcTickerSubscriptionMsg() MexcTickerSubscription { - return MexcTickerSubscription{ - OP: "sub.overview", - } -} diff --git a/price-feeder/oracle/provider/mexc_test.go b/price-feeder/oracle/provider/mexc_test.go deleted file mode 100644 index 7ea6ce2cb0..0000000000 --- a/price-feeder/oracle/provider/mexc_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package provider - -import ( - "context" - "encoding/json" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -func TestMexcProvider_GetTickerPrices(t *testing.T) { - p, err := NewMexcProvider( - context.TODO(), - zerolog.Nop(), - Endpoint{}, - types.CurrencyPair{Base: "ATOM", Quote: "USDT"}, - ) - require.NoError(t, err) - - t.Run("valid_request_single_ticker", func(t *testing.T) { - lastPrice := sdk.MustNewDecFromStr("34.69000000") - volume := sdk.MustNewDecFromStr("2396974.02000000") - - tickerMap := map[string]types.TickerPrice{} - tickerMap["ATOM_USDT"] = types.TickerPrice{ - Price: lastPrice, - Volume: volume, - } - - p.tickers = tickerMap - - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "ATOM", Quote: "USDT"}) - require.NoError(t, err) - require.Len(t, prices, 1) - require.Equal(t, lastPrice, prices["ATOMUSDT"].Price) - require.Equal(t, volume, prices["ATOMUSDT"].Volume) - }) - - t.Run("valid_request_multi_ticker", func(t *testing.T) { - lastPriceAtom := sdk.MustNewDecFromStr("34.69000000") - lastPriceLuna := sdk.MustNewDecFromStr("41.35000000") - volume := sdk.MustNewDecFromStr("2396974.02000000") - - tickerMap := map[string]types.TickerPrice{} - tickerMap["ATOM_USDT"] = types.TickerPrice{ - Price: lastPriceAtom, - Volume: volume, - } - - tickerMap["LUNA_USDT"] = types.TickerPrice{ - Price: lastPriceLuna, - Volume: volume, - } - - p.tickers = tickerMap - prices, err := p.GetTickerPrices( - types.CurrencyPair{Base: "ATOM", Quote: "USDT"}, - types.CurrencyPair{Base: "LUNA", Quote: "USDT"}, - ) - require.NoError(t, err) - require.Len(t, prices, 2) - require.Equal(t, lastPriceAtom, prices["ATOMUSDT"].Price) - require.Equal(t, volume, prices["ATOMUSDT"].Volume) - require.Equal(t, lastPriceLuna, prices["LUNAUSDT"].Price) - require.Equal(t, volume, prices["LUNAUSDT"].Volume) - }) - - t.Run("invalid_request_invalid_ticker", func(t *testing.T) { - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "FOO", Quote: "BAR"}) - require.Error(t, err) - require.Equal(t, "mexc has no ticker data for requested pairs: [FOOBAR]", err.Error()) - require.Nil(t, prices) - }) -} - -func TestMexcCurrencyPairToMexcPair(t *testing.T) { - cp := types.CurrencyPair{Base: "ATOM", Quote: "USDT"} - MexcSymbol := currencyPairToMexcPair(cp) - require.Equal(t, MexcSymbol, "ATOM_USDT") -} - -func TestMexcProvider_getSubscriptionMsgs(t *testing.T) { - provider := &MexcProvider{ - subscribedPairs: map[string]types.CurrencyPair{}, - } - cps := []types.CurrencyPair{ - {Base: "ATOM", Quote: "USDT"}, - } - subMsgs := provider.getSubscriptionMsgs(cps...) - - msg, _ := json.Marshal(subMsgs[0]) - require.Equal(t, "{\"op\":\"sub.kline\",\"symbol\":\"ATOM_USDT\",\"interval\":\"Min1\"}", string(msg)) - - msg, _ = json.Marshal(subMsgs[1]) - require.Equal(t, "{\"op\":\"sub.overview\"}", string(msg)) -} diff --git a/price-feeder/oracle/provider/mock.go b/price-feeder/oracle/provider/mock.go deleted file mode 100644 index 34472d6346..0000000000 --- a/price-feeder/oracle/provider/mock.go +++ /dev/null @@ -1,150 +0,0 @@ -package provider - -import ( - "encoding/csv" - "fmt" - "net/http" - "strings" - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -const ( - // Google Sheets document containing mock exchange rates. - // - // Ref: https://docs.google.com/spreadsheets/d/1DfVh2Xwxfehcwo08h2sBgaqL-2Jem1ri_prsQ3ayFeE/edit?usp=sharing - mockBaseURL = "https://docs.google.com/spreadsheets/d/e/2PACX-1vQRVD0IMn8ZdRgmE2XeNkwjpSGglwelx1z0-hNV2ejfstVeuL2xF8i3EISBZfrGTjVTI0EXW9Wwq4F-/pub?output=csv" -) - -var _ Provider = (*MockProvider)(nil) - -type ( - // MockProvider defines a mocked exchange rate provider using a published - // Google sheets document to fetch mocked/fake exchange rates. - MockProvider struct { - baseURL string - client *http.Client - } -) - -func NewMockProvider() *MockProvider { - return &MockProvider{ - baseURL: mockBaseURL, - client: &http.Client{ - Timeout: defaultTimeout, - // the mock provider is the only one which allows redirects - // because it gets prices from a google spreadsheet, which redirects - }, - } -} - -// SubscribeCurrencyPairs performs a no-op since mock does not use websockets -func (p MockProvider) SubscribeCurrencyPairs(...types.CurrencyPair) {} - -func (p MockProvider) GetTickerPrices(pairs ...types.CurrencyPair) (map[string]types.TickerPrice, error) { - tickerPrices := make(map[string]types.TickerPrice, len(pairs)) - - resp, err := p.client.Get(p.baseURL) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - - csvReader := csv.NewReader(resp.Body) - records, err := csvReader.ReadAll() - if err != nil { - return nil, err - } - - tickerMap := make(map[string]struct{}) - for _, cp := range pairs { - tickerMap[strings.ToUpper(cp.String())] = struct{}{} - } - - // Records are of the form [base, quote, price, volume] and we skip the first - // record as that contains the header. - for _, r := range records[1:] { - ticker := strings.ToUpper(r[0] + r[1]) - if _, ok := tickerMap[ticker]; !ok { - // skip records that are not requested - continue - } - - price, err := sdk.NewDecFromStr(r[2]) - if err != nil { - return nil, fmt.Errorf("failed to read mock price (%s) for %s", r[2], ticker) - } - - volume, err := sdk.NewDecFromStr(r[3]) - if err != nil { - return nil, fmt.Errorf("failed to read mock volume (%s) for %s", r[3], ticker) - } - - if _, ok := tickerPrices[ticker]; ok { - return nil, fmt.Errorf("found duplicate ticker: %s", ticker) - } - - tickerPrices[ticker] = types.TickerPrice{Price: price, Volume: volume} - } - - for t := range tickerMap { - if _, ok := tickerPrices[t]; !ok { - return nil, fmt.Errorf(types.ErrMissingExchangeRate.Error(), t) - } - } - - return tickerPrices, nil -} - -func (p MockProvider) GetCandlePrices(pairs ...types.CurrencyPair) (map[string][]types.CandlePrice, error) { - price, err := p.GetTickerPrices(pairs...) - if err != nil { - return nil, err - } - candles := make(map[string][]types.CandlePrice) - for pair, price := range price { - candles[pair] = []types.CandlePrice{ - { - Price: price.Price, - Volume: price.Volume, - TimeStamp: PastUnixTime(1 * time.Minute), - }, - } - } - return candles, nil -} - -// GetAvailablePairs return all available pairs symbol to susbscribe. -func (p MockProvider) GetAvailablePairs() (map[string]struct{}, error) { - resp, err := http.Get(p.baseURL) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - csvReader := csv.NewReader(resp.Body) - records, err := csvReader.ReadAll() - if err != nil { - return nil, err - } - - // Records are of the form [base, quote, price, volume] and we skip the first - // record as that contains the header. - availablePairs := make(map[string]struct{}, len(records[1:])) - for _, r := range records[1:] { - if len(r) < 2 { - continue - } - - cp := types.CurrencyPair{ - Base: strings.ToUpper(r[0]), - Quote: strings.ToUpper(r[1]), - } - availablePairs[cp.String()] = struct{}{} - } - - return availablePairs, nil -} diff --git a/price-feeder/oracle/provider/mock_test.go b/price-feeder/oracle/provider/mock_test.go deleted file mode 100644 index a97e5771c6..0000000000 --- a/price-feeder/oracle/provider/mock_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package provider - -import ( - "net/http" - "net/http/httptest" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -func TestMockProvider_GetTickerPrices(t *testing.T) { - mp := NewMockProvider() - - t.Run("valid_request_single_ticker", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - require.Equal(t, "/", req.URL.String()) - resp := `Base,Quote,Price,Volume -UMEE,USDT,3.04,1827884.77 -ATOM,USDC,21.84,1827884.77 -` - rw.Write([]byte(resp)) - })) - defer server.Close() - - mp.client = server.Client() - mp.baseURL = server.URL - - prices, err := mp.GetTickerPrices(types.CurrencyPair{Base: "UMEE", Quote: "USDT"}) - require.NoError(t, err) - require.Len(t, prices, 1) - require.Equal(t, sdk.MustNewDecFromStr("3.04"), prices["UMEEUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr("1827884.77"), prices["UMEEUSDT"].Volume) - }) - - t.Run("valid_request_multi_ticker", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - require.Equal(t, "/", req.URL.String()) - resp := `Base,Quote,Price,Volume -UMEE,USDT,3.04,1827884.77 -ATOM,USDC,21.84,1827884.77 -` - rw.Write([]byte(resp)) - })) - defer server.Close() - - mp.client = server.Client() - mp.baseURL = server.URL - - prices, err := mp.GetTickerPrices( - types.CurrencyPair{Base: "UMEE", Quote: "USDT"}, - types.CurrencyPair{Base: "ATOM", Quote: "USDC"}, - ) - require.NoError(t, err) - require.Len(t, prices, 2) - require.Equal(t, sdk.MustNewDecFromStr("3.04"), prices["UMEEUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr("1827884.77"), prices["UMEEUSDT"].Volume) - require.Equal(t, sdk.MustNewDecFromStr("21.84"), prices["ATOMUSDC"].Price) - require.Equal(t, sdk.MustNewDecFromStr("1827884.77"), prices["ATOMUSDC"].Volume) - }) - - t.Run("invalid_request_bad_response", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - require.Equal(t, "/", req.URL.String()) - rw.Write([]byte(`FOO`)) - })) - defer server.Close() - - mp.client = server.Client() - mp.baseURL = server.URL - - prices, err := mp.GetTickerPrices(types.CurrencyPair{Base: "UMEE", Quote: "USDT"}) - require.Error(t, err) - require.Nil(t, prices) - }) -} diff --git a/price-feeder/oracle/provider/okx.go b/price-feeder/oracle/provider/okx.go deleted file mode 100644 index ff9405acf6..0000000000 --- a/price-feeder/oracle/provider/okx.go +++ /dev/null @@ -1,418 +0,0 @@ -package provider - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - "strconv" - "strings" - "sync" - - "github.com/gorilla/websocket" - "github.com/rs/zerolog" - - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -const ( - okxWSHost = "ws.okx.com:8443" - okxWSPath = "/ws/v5/public" - okxRestHost = "https://www.okx.com" - okxRestPath = "/api/v5/market/tickers?instType=SPOT" -) - -var _ Provider = (*OkxProvider)(nil) - -type ( - // OkxProvider defines an Oracle provider implemented by the Okx public - // API. - // - // REF: https://www.okx.com/docs-v5/en/#websocket-api-public-channel-tickers-channel - OkxProvider struct { - wsc *WebsocketController - logger zerolog.Logger - mtx sync.RWMutex - endpoints Endpoint - tickers map[string]OkxTickerPair // InstId => OkxTickerPair - candles map[string][]OkxCandlePair // InstId => 0kxCandlePair - subscribedPairs map[string]types.CurrencyPair // Symbol => types.CurrencyPair - } - - // OkxInstId defines the id Symbol of an pair. - OkxInstID struct { - InstID string `json:"instId"` // Instrument ID ex.: BTC-USDT - } - - // OkxTickerPair defines a ticker pair of Okx. - OkxTickerPair struct { - OkxInstID - Last string `json:"last"` // Last traded price ex.: 43508.9 - Vol24h string `json:"vol24h"` // 24h trading volume ex.: 11159.87127845 - } - - // OkxInst defines the structure containing ID information for the OkxResponses. - OkxID struct { - OkxInstID - Channel string `json:"channel"` - } - - // OkxTickerResponse defines the response structure of a Okx ticker request. - OkxTickerResponse struct { - Data []OkxTickerPair `json:"data"` - ID OkxID `json:"arg"` - } - - // OkxCandlePair defines a candle for Okx. - OkxCandlePair struct { - Close string `json:"c"` // Close price for this time period - TimeStamp int64 `json:"ts"` // Linux epoch timestamp - Volume string `json:"vol"` // Volume for this time period - InstID string `json:"instId"` // Instrument ID ex.: BTC-USDT - } - - // OkxCandleResponse defines the response structure of a Okx candle request. - OkxCandleResponse struct { - Data [][]string `json:"data"` - ID OkxID `json:"arg"` - } - - // OkxSubscriptionTopic Topic with the ticker to be subscribed/unsubscribed. - OkxSubscriptionTopic struct { - Channel string `json:"channel"` // Channel name ex.: tickers - InstID string `json:"instId"` // Instrument ID ex.: BTC-USDT - } - - // OkxSubscriptionMsg Message to subscribe/unsubscribe with N Topics. - OkxSubscriptionMsg struct { - Op string `json:"op"` // Operation ex.: subscribe - Args []OkxSubscriptionTopic `json:"args"` - } - - // OkxPairsSummary defines the response structure for an Okx pairs summary. - OkxPairsSummary struct { - Data []OkxInstID `json:"data"` - } -) - -// NewOkxProvider creates a new OkxProvider. -func NewOkxProvider( - ctx context.Context, - logger zerolog.Logger, - endpoints Endpoint, - pairs ...types.CurrencyPair, -) (*OkxProvider, error) { - if endpoints.Name != ProviderOkx { - endpoints = Endpoint{ - Name: ProviderOkx, - Rest: okxRestHost, - Websocket: okxWSHost, - } - } - - wsURL := url.URL{ - Scheme: "wss", - Host: endpoints.Websocket, - Path: okxWSPath, - } - - okxLogger := logger.With().Str("provider", string(ProviderOkx)).Logger() - - provider := &OkxProvider{ - logger: okxLogger, - endpoints: endpoints, - tickers: map[string]OkxTickerPair{}, - candles: map[string][]OkxCandlePair{}, - subscribedPairs: map[string]types.CurrencyPair{}, - } - - provider.setSubscribedPairs(pairs...) - - provider.wsc = NewWebsocketController( - ctx, - endpoints.Name, - wsURL, - provider.getSubscriptionMsgs(pairs...), - provider.messageReceived, - defaultPingDuration, - websocket.PingMessage, - okxLogger, - ) - provider.wsc.StartConnections() - - return provider, nil -} - -func (p *OkxProvider) getSubscriptionMsgs(cps ...types.CurrencyPair) []interface{} { - subscriptionMsgs := make([]interface{}, 0, len(cps)*2) - for _, cp := range cps { - okxPair := currencyPairToOkxPair(cp) - okxTopic := newOkxCandleSubscriptionTopic(okxPair) - subscriptionMsgs = append(subscriptionMsgs, newOkxSubscriptionMsg(okxTopic)) - - okxTopic = newOkxTickerSubscriptionTopic(okxPair) - subscriptionMsgs = append(subscriptionMsgs, newOkxSubscriptionMsg(okxTopic)) - } - return subscriptionMsgs -} - -// SubscribeCurrencyPairs sends the new subscription messages to the websocket -// and adds them to the providers subscribedPairs array -func (p *OkxProvider) SubscribeCurrencyPairs(cps ...types.CurrencyPair) { - p.mtx.Lock() - defer p.mtx.Unlock() - - newPairs := []types.CurrencyPair{} - for _, cp := range cps { - if _, ok := p.subscribedPairs[cp.String()]; !ok { - newPairs = append(newPairs, cp) - } - } - - newSubscriptionMsgs := p.getSubscriptionMsgs(newPairs...) - p.wsc.AddWebsocketConnection( - newSubscriptionMsgs, - p.messageReceived, - defaultPingDuration, - websocket.PingMessage, - ) - p.setSubscribedPairs(newPairs...) -} - -// GetTickerPrices returns the tickerPrices based on the saved map. -func (p *OkxProvider) GetTickerPrices(pairs ...types.CurrencyPair) (map[string]types.TickerPrice, error) { - tickerPrices := make(map[string]types.TickerPrice, len(pairs)) - - tickerErrs := 0 - for _, cp := range pairs { - price, err := p.getTickerPrice(cp) - if err != nil { - p.logger.Warn().Err(err) - tickerErrs++ - continue - } - tickerPrices[cp.String()] = price - } - - if tickerErrs == len(pairs) { - return nil, fmt.Errorf( - types.ErrNoTickers.Error(), - p.endpoints.Name, - pairs, - ) - } - return tickerPrices, nil -} - -// GetCandlePrices returns the candlePrices based on the saved map -func (p *OkxProvider) GetCandlePrices(pairs ...types.CurrencyPair) (map[string][]types.CandlePrice, error) { - candlePrices := make(map[string][]types.CandlePrice, len(pairs)) - - candleErrs := 0 - for _, cp := range pairs { - prices, err := p.getCandlePrices(cp) - if err != nil { - p.logger.Warn().Err(err) - candleErrs++ - continue - } - candlePrices[cp.String()] = prices - } - - if candleErrs == len(pairs) { - return nil, fmt.Errorf( - types.ErrNoCandles.Error(), - p.endpoints.Name, - pairs, - ) - } - return candlePrices, nil -} - -func (p *OkxProvider) getTickerPrice(cp types.CurrencyPair) (types.TickerPrice, error) { - p.mtx.RLock() - defer p.mtx.RUnlock() - - instrumentID := currencyPairToOkxPair(cp) - tickerPair, ok := p.tickers[instrumentID] - if !ok { - return types.TickerPrice{}, fmt.Errorf( - types.ErrTickerNotFound.Error(), - p.endpoints.Name, - instrumentID, - ) - } - - return tickerPair.toTickerPrice() -} - -func (p *OkxProvider) getCandlePrices(cp types.CurrencyPair) ([]types.CandlePrice, error) { - p.mtx.RLock() - defer p.mtx.RUnlock() - - instrumentID := currencyPairToOkxPair(cp) - candles, ok := p.candles[instrumentID] - if !ok { - return []types.CandlePrice{}, fmt.Errorf( - types.ErrCandleNotFound.Error(), - p.endpoints.Name, - instrumentID, - ) - } - candleList := []types.CandlePrice{} - for _, candle := range candles { - cp, err := candle.toCandlePrice() - if err != nil { - return []types.CandlePrice{}, err - } - candleList = append(candleList, cp) - } - - return candleList, nil -} - -func (p *OkxProvider) messageReceived(_ int, _ *WebsocketConnection, bz []byte) { - var ( - tickerResp OkxTickerResponse - tickerErr error - candleResp OkxCandleResponse - candleErr error - ) - - // sometimes the message received is not a ticker or a candle response. - tickerErr = json.Unmarshal(bz, &tickerResp) - if tickerResp.ID.Channel == "tickers" { - for _, tickerPair := range tickerResp.Data { - p.setTickerPair(tickerPair) - telemetryWebsocketMessage(ProviderOkx, MessageTypeTicker) - } - return - } - - candleErr = json.Unmarshal(bz, &candleResp) - if candleResp.ID.Channel == "candle1m" { - for _, candlePair := range candleResp.Data { - p.setCandlePair(candlePair, candleResp.ID.InstID) - telemetryWebsocketMessage(ProviderOkx, MessageTypeCandle) - } - return - } - - p.logger.Error(). - Int("length", len(bz)). - AnErr("ticker", tickerErr). - AnErr("candle", candleErr). - Msg("Error on receive message") -} - -func (p *OkxProvider) setTickerPair(tickerPair OkxTickerPair) { - p.mtx.Lock() - defer p.mtx.Unlock() - p.tickers[tickerPair.InstID] = tickerPair -} - -func (p *OkxProvider) setCandlePair(pairData []string, instID string) { - p.mtx.Lock() - defer p.mtx.Unlock() - - ts, err := strconv.ParseInt(pairData[0], 10, 64) - if err != nil { - return - } - // the candlesticks channel uses an array of strings. - candle := OkxCandlePair{ - Close: pairData[4], - InstID: instID, - Volume: pairData[5], - TimeStamp: ts, - } - staleTime := PastUnixTime(providerCandlePeriod) - candleList := []OkxCandlePair{} - - candleList = append(candleList, candle) - for _, c := range p.candles[instID] { - if staleTime < c.TimeStamp { - candleList = append(candleList, c) - } - } - p.candles[instID] = candleList -} - -// setSubscribedPairs sets N currency pairs to the map of subscribed pairs. -func (p *OkxProvider) setSubscribedPairs(cps ...types.CurrencyPair) { - for _, cp := range cps { - p.subscribedPairs[cp.String()] = cp - } -} - -// GetAvailablePairs return all available pairs symbol to subscribe. -func (p *OkxProvider) GetAvailablePairs() (map[string]struct{}, error) { - resp, err := http.Get(p.endpoints.Rest + okxRestPath) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var pairsSummary struct { - Data []OkxInstID `json:"data"` - } - - if err := json.NewDecoder(resp.Body).Decode(&pairsSummary); err != nil { - return nil, err - } - - availablePairs := make(map[string]struct{}, len(pairsSummary.Data)) - for _, pair := range pairsSummary.Data { - splitInstID := strings.Split(pair.InstID, "-") - if len(splitInstID) != 2 { - continue - } - - cp := types.CurrencyPair{ - Base: strings.ToUpper(splitInstID[0]), - Quote: strings.ToUpper(splitInstID[1]), - } - availablePairs[cp.String()] = struct{}{} - } - - return availablePairs, nil -} - -func (ticker OkxTickerPair) toTickerPrice() (types.TickerPrice, error) { - return types.NewTickerPrice(string(ProviderOkx), ticker.InstID, ticker.Last, ticker.Vol24h) -} - -func (candle OkxCandlePair) toCandlePrice() (types.CandlePrice, error) { - return types.NewCandlePrice(string(ProviderOkx), candle.InstID, candle.Close, candle.Volume, candle.TimeStamp) -} - -// currencyPairToOkxPair returns the expected pair instrument ID for Okx -// ex.: "BTC-USDT". -func currencyPairToOkxPair(pair types.CurrencyPair) string { - return pair.Base + "-" + pair.Quote -} - -// newOkxTickerSubscriptionTopic returns a new subscription topic. -func newOkxTickerSubscriptionTopic(instID string) OkxSubscriptionTopic { - return OkxSubscriptionTopic{ - Channel: "tickers", - InstID: instID, - } -} - -// newOkxSubscriptionTopic returns a new subscription topic. -func newOkxCandleSubscriptionTopic(instID string) OkxSubscriptionTopic { - return OkxSubscriptionTopic{ - Channel: "candle1m", - InstID: instID, - } -} - -// newOkxSubscriptionMsg returns a new subscription Msg for Okx. -func newOkxSubscriptionMsg(args ...OkxSubscriptionTopic) OkxSubscriptionMsg { - return OkxSubscriptionMsg{ - Op: "subscribe", - Args: args, - } -} diff --git a/price-feeder/oracle/provider/okx_test.go b/price-feeder/oracle/provider/okx_test.go deleted file mode 100644 index dc94209195..0000000000 --- a/price-feeder/oracle/provider/okx_test.go +++ /dev/null @@ -1,107 +0,0 @@ -package provider - -import ( - "context" - "encoding/json" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -func TestOkxProvider_GetTickerPrices(t *testing.T) { - p, err := NewOkxProvider( - context.TODO(), - zerolog.Nop(), - Endpoint{}, - types.CurrencyPair{Base: "BTC", Quote: "USDT"}, - ) - require.NoError(t, err) - - t.Run("valid_request_single_ticker", func(t *testing.T) { - lastPrice := "34.69000000" - volume := "2396974.02000000" - - syncMap := map[string]OkxTickerPair{} - syncMap["ATOM-USDT"] = OkxTickerPair{ - OkxInstID: OkxInstID{ - InstID: "ATOM-USDT", - }, - Last: lastPrice, - Vol24h: volume, - } - - p.tickers = syncMap - - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "ATOM", Quote: "USDT"}) - require.NoError(t, err) - require.Len(t, prices, 1) - require.Equal(t, sdk.MustNewDecFromStr(lastPrice), prices["ATOMUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr(volume), prices["ATOMUSDT"].Volume) - }) - - t.Run("valid_request_multi_ticker", func(t *testing.T) { - lastPriceAtom := "34.69000000" - lastPriceLuna := "41.35000000" - volume := "2396974.02000000" - - syncMap := map[string]OkxTickerPair{} - syncMap["ATOM-USDT"] = OkxTickerPair{ - OkxInstID: OkxInstID{ - InstID: "ATOM-USDT", - }, - Last: lastPriceAtom, - Vol24h: volume, - } - - syncMap["LUNA-USDT"] = OkxTickerPair{ - OkxInstID: OkxInstID{ - InstID: "LUNA-USDT", - }, - Last: lastPriceLuna, - Vol24h: volume, - } - - p.tickers = syncMap - prices, err := p.GetTickerPrices( - types.CurrencyPair{Base: "ATOM", Quote: "USDT"}, - types.CurrencyPair{Base: "LUNA", Quote: "USDT"}, - ) - require.NoError(t, err) - require.Len(t, prices, 2) - require.Equal(t, sdk.MustNewDecFromStr(lastPriceAtom), prices["ATOMUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr(volume), prices["ATOMUSDT"].Volume) - require.Equal(t, sdk.MustNewDecFromStr(lastPriceLuna), prices["LUNAUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr(volume), prices["LUNAUSDT"].Volume) - }) - - t.Run("invalid_request_invalid_ticker", func(t *testing.T) { - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "FOO", Quote: "BAR"}) - require.EqualError(t, err, "okx has no ticker data for requested pairs: [FOOBAR]") - require.Nil(t, prices) - }) -} - -func TestOkxCurrencyPairToOkxPair(t *testing.T) { - cp := types.CurrencyPair{Base: "ATOM", Quote: "USDT"} - okxSymbol := currencyPairToOkxPair(cp) - require.Equal(t, okxSymbol, "ATOM-USDT") -} - -func TestOkxProvider_getSubscriptionMsgs(t *testing.T) { - provider := &OkxProvider{ - subscribedPairs: map[string]types.CurrencyPair{}, - } - cps := []types.CurrencyPair{ - {Base: "ATOM", Quote: "USDT"}, - } - subMsgs := provider.getSubscriptionMsgs(cps...) - - msg, _ := json.Marshal(subMsgs[0]) - require.Equal(t, "{\"op\":\"subscribe\",\"args\":[{\"channel\":\"candle1m\",\"instId\":\"ATOM-USDT\"}]}", string(msg)) - - msg, _ = json.Marshal(subMsgs[1]) - require.Equal(t, "{\"op\":\"subscribe\",\"args\":[{\"channel\":\"tickers\",\"instId\":\"ATOM-USDT\"}]}", string(msg)) -} diff --git a/price-feeder/oracle/provider/osmosis.go b/price-feeder/oracle/provider/osmosis.go deleted file mode 100644 index 243edf6b96..0000000000 --- a/price-feeder/oracle/provider/osmosis.go +++ /dev/null @@ -1,222 +0,0 @@ -package provider - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "strings" - - "github.com/umee-network/umee/price-feeder/v2/oracle/types" - "github.com/umee-network/umee/v4/util/coin" -) - -const ( - osmosisRestURL = "https://api-osmosis.imperator.co" - osmosisTokenEndpoint = "/tokens/v2" - osmosisCandleEndpoint = "/tokens/v2/historical" - osmosisPairsEndpoint = "/pairs/v1/summary" -) - -var _ Provider = (*OsmosisProvider)(nil) - -type ( - // OsmosisProvider defines an Oracle provider implemented by the Osmosis public - // API. - // - // REF: https://api-osmosis.imperator.co/swagger/ - OsmosisProvider struct { - baseURL string - client *http.Client - } - - // OsmosisTokenResponse defines the response structure for an Osmosis token - // request. - OsmosisTokenResponse struct { - Price float64 `json:"price"` - Symbol string `json:"symbol"` - Volume float64 `json:"volume_24h"` - } - - // OsmosisCandleResponse defines the response structure for an Osmosis candle - // request. - OsmosisCandleResponse struct { - Time int64 `json:"time"` - Close float64 `json:"close"` - Volume float64 `json:"volume"` - } - - // OsmosisPairsSummary defines the response structure for an Osmosis pairs - // summary. - OsmosisPairsSummary struct { - Data []OsmosisPairData `json:"data"` - } - - // OsmosisPairData defines the data response structure for an Osmosis pair. - OsmosisPairData struct { - Base string `json:"base_symbol"` - Quote string `json:"quote_symbol"` - } -) - -func NewOsmosisProvider(endpoint Endpoint) *OsmosisProvider { - if endpoint.Name == ProviderOsmosis { - return &OsmosisProvider{ - baseURL: endpoint.Rest, - client: newDefaultHTTPClient(), - } - } - return &OsmosisProvider{ - baseURL: osmosisRestURL, - client: newDefaultHTTPClient(), - } -} - -// SubscribeCurrencyPairs performs a no-op since osmosis does not use websockets -func (OsmosisProvider) SubscribeCurrencyPairs(...types.CurrencyPair) {} - -func (p OsmosisProvider) GetTickerPrices(pairs ...types.CurrencyPair) (map[string]types.TickerPrice, error) { - path := fmt.Sprintf("%s%s/all", p.baseURL, osmosisTokenEndpoint) - - resp, err := p.client.Get(path) - if err != nil { - return nil, fmt.Errorf("failed to make Osmosis request: %w", err) - } - err = checkHTTPStatus(resp) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - - bz, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed to read Osmosis response body: %w", err) - } - - var tokensResp []OsmosisTokenResponse - if err := json.Unmarshal(bz, &tokensResp); err != nil { - return nil, fmt.Errorf("failed to unmarshal Osmosis response body: %w", err) - } - - baseDenomIdx := make(map[string]types.CurrencyPair) - for _, cp := range pairs { - baseDenomIdx[strings.ToUpper(cp.Base)] = cp - } - - tickerPrices := make(map[string]types.TickerPrice, len(pairs)) - for _, tr := range tokensResp { - symbol := strings.ToUpper(tr.Symbol) // symbol == base in a currency pair - - cp, ok := baseDenomIdx[symbol] - if !ok { - // skip tokens that are not requested - continue - } - - if _, ok := tickerPrices[symbol]; ok { - return nil, fmt.Errorf("duplicate token found in Osmosis response: %s", symbol) - } - - price, err := coin.NewDecFromFloat(tr.Price) - if err != nil { - return nil, fmt.Errorf("failed to read Osmosis price (%f) for %s", tr.Price, symbol) - } - - volume, err := coin.NewDecFromFloat(tr.Volume) - if err != nil { - return nil, fmt.Errorf("failed to read Osmosis volume (%f) for %s", tr.Volume, symbol) - } - - tickerPrices[cp.String()] = types.TickerPrice{Price: price, Volume: volume} - } - - for _, cp := range pairs { - if _, ok := tickerPrices[cp.String()]; !ok { - return nil, fmt.Errorf(types.ErrMissingExchangeRate.Error(), cp.String()) - } - } - - return tickerPrices, nil -} - -func (p OsmosisProvider) GetCandlePrices(pairs ...types.CurrencyPair) (map[string][]types.CandlePrice, error) { - candles := make(map[string][]types.CandlePrice) - for _, pair := range pairs { - if _, ok := candles[pair.Base]; !ok { - candles[pair.String()] = []types.CandlePrice{} - } - - path := fmt.Sprintf("%s%s/%s/chart?tf=5", p.baseURL, osmosisCandleEndpoint, pair.Base) - - resp, err := p.client.Get(path) - if err != nil { - return nil, fmt.Errorf("failed to make Osmosis request: %w", err) - } - err = checkHTTPStatus(resp) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - - bz, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed to read Osmosis response body: %w", err) - } - - var candlesResp []OsmosisCandleResponse - if err := json.Unmarshal(bz, &candlesResp); err != nil { - return nil, fmt.Errorf("failed to unmarshal Osmosis response body: %w", err) - } - - staleTime := PastUnixTime(providerCandlePeriod) - - candlePrices := []types.CandlePrice{} - for _, responseCandle := range candlesResp { - if staleTime >= responseCandle.Time { - continue - } - candlePrices = append(candlePrices, types.CandlePrice{ - Price: coin.MustNewDecFromFloat(responseCandle.Close), - Volume: coin.MustNewDecFromFloat(responseCandle.Volume), - // convert osmosis timestamp seconds -> milliseconds - TimeStamp: SecondsToMilli(responseCandle.Time), - }) - } - candles[pair.String()] = candlePrices - } - - return candles, nil -} - -// GetAvailablePairs return all available pairs symbol to susbscribe. -func (p OsmosisProvider) GetAvailablePairs() (map[string]struct{}, error) { - path := fmt.Sprintf("%s%s", p.baseURL, osmosisPairsEndpoint) - - resp, err := p.client.Get(path) - if err != nil { - return nil, err - } - err = checkHTTPStatus(resp) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var pairsSummary OsmosisPairsSummary - if err := json.NewDecoder(resp.Body).Decode(&pairsSummary); err != nil { - return nil, err - } - - availablePairs := make(map[string]struct{}, len(pairsSummary.Data)) - for _, pair := range pairsSummary.Data { - cp := types.CurrencyPair{ - Base: strings.ToUpper(pair.Base), - Quote: strings.ToUpper(pair.Quote), - } - availablePairs[cp.String()] = struct{}{} - } - - return availablePairs, nil -} diff --git a/price-feeder/oracle/provider/osmosis_test.go b/price-feeder/oracle/provider/osmosis_test.go deleted file mode 100644 index 09ddd48983..0000000000 --- a/price-feeder/oracle/provider/osmosis_test.go +++ /dev/null @@ -1,193 +0,0 @@ -package provider - -import ( - "net/http" - "net/http/httptest" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -func TestOsmosisProvider_GetTickerPrices(t *testing.T) { - p := NewOsmosisProvider(Endpoint{}) - - t.Run("valid_request_single_ticker", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - require.Equal(t, "/tokens/v2/all", req.URL.String()) - resp := `[ - { - "price": 100.22, - "denom": "ibc/0EF15DF2F02480ADE0BB6E85D9EBB5DAEA2836D3860E9F97F9AADE4F57A31AA0", - "symbol": "LUNA", - "liquidity": 56928301.60178607, - "volume_24h": 7047660.837452592, - "name": "Luna" - }, - { - "price": 28.52, - "denom": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2", - "symbol": "ATOM", - "liquidity": 189672157.83693966, - "volume_24h": 17006018.613512218, - "name": "Cosmos" - } - ] - ` - rw.Write([]byte(resp)) - })) - defer server.Close() - - p.client = server.Client() - p.baseURL = server.URL - - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "ATOM", Quote: "USDT"}) - require.NoError(t, err) - require.Len(t, prices, 1) - require.Equal(t, sdk.MustNewDecFromStr("28.52"), prices["ATOMUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr("17006018.613512218"), prices["ATOMUSDT"].Volume) - }) - - t.Run("valid_request_multi_ticker", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - require.Equal(t, "/tokens/v2/all", req.URL.String()) - resp := `[ - { - "price": 100.22, - "denom": "ibc/0EF15DF2F02480ADE0BB6E85D9EBB5DAEA2836D3860E9F97F9AADE4F57A31AA0", - "symbol": "LUNA", - "liquidity": 56928301.60178607, - "volume_24h": 7047660.837452592, - "name": "Luna" - }, - { - "price": 28.52, - "denom": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2", - "symbol": "ATOM", - "liquidity": 189672157.83693966, - "volume_24h": 17006018.613512218, - "name": "Cosmos" - } - ] - ` - rw.Write([]byte(resp)) - })) - defer server.Close() - - p.client = server.Client() - p.baseURL = server.URL - - prices, err := p.GetTickerPrices( - types.CurrencyPair{Base: "ATOM", Quote: "USDT"}, - types.CurrencyPair{Base: "LUNA", Quote: "USDT"}, - ) - require.NoError(t, err) - require.Len(t, prices, 2) - require.Equal(t, sdk.MustNewDecFromStr("28.52"), prices["ATOMUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr("17006018.613512218"), prices["ATOMUSDT"].Volume) - require.Equal(t, sdk.MustNewDecFromStr("100.22"), prices["LUNAUSDT"].Price) - require.Equal(t, sdk.MustNewDecFromStr("7047660.837452592"), prices["LUNAUSDT"].Volume) - }) - - t.Run("invalid_request_bad_response", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - require.Equal(t, "/tokens/v2/all", req.URL.String()) - rw.Write([]byte(`FOO`)) - })) - defer server.Close() - - p.client = server.Client() - p.baseURL = server.URL - - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "ATOM", Quote: "USDT"}) - require.Error(t, err) - require.Nil(t, prices) - }) - - t.Run("invalid_request_invalid_ticker", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - require.Equal(t, "/tokens/v2/all", req.URL.String()) - resp := `[ - { - "price": 100.22, - "denom": "ibc/0EF15DF2F02480ADE0BB6E85D9EBB5DAEA2836D3860E9F97F9AADE4F57A31AA0", - "symbol": "LUNA", - "liquidity": 56928301.60178607, - "volume_24h": 7047660.837452592, - "name": "Luna" - }, - { - "price": 28.52, - "denom": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2", - "symbol": "ATOM", - "liquidity": 189672157.83693966, - "volume_24h": 17006018.613512218, - "name": "Cosmos" - } - ] - ` - rw.Write([]byte(resp)) - })) - defer server.Close() - - p.client = server.Client() - p.baseURL = server.URL - - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "FOO", Quote: "BAR"}) - require.Error(t, err) - require.Nil(t, prices) - }) - - t.Run("check_redirect", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - http.Redirect(rw, r, p.baseURL, http.StatusTemporaryRedirect) - })) - defer server.Close() - - server.Client().CheckRedirect = preventRedirect - p.client = server.Client() - p.baseURL = server.URL - - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "ATOM", Quote: "USDT"}) - require.Error(t, err) - require.Nil(t, prices) - }) -} - -func TestOsmosisProvider_GetAvailablePairs(t *testing.T) { - p := NewOsmosisProvider(Endpoint{}) - p.GetAvailablePairs() - - t.Run("valid_available_pair", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - require.Equal(t, "/pairs/v1/summary", req.URL.String()) - resp := `{ - "data": [ - { - "base_symbol": "ATOM", - "quote_symbol": "OSMO" - }, - { - "base_symbol": "ION", - "quote_symbol": "OSMO" - } - ] - }` - rw.Write([]byte(resp)) - })) - defer server.Close() - - p.client = server.Client() - p.baseURL = server.URL - - availablePairs, err := p.GetAvailablePairs() - require.Nil(t, err) - - _, exist := availablePairs["ATOMOSMO"] - require.True(t, exist) - - _, exist = availablePairs["IONOSMO"] - require.True(t, exist) - }) -} diff --git a/price-feeder/oracle/provider/osmosisv2.go b/price-feeder/oracle/provider/osmosisv2.go deleted file mode 100644 index 5877e4c4ce..0000000000 --- a/price-feeder/oracle/provider/osmosisv2.go +++ /dev/null @@ -1,376 +0,0 @@ -package provider - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - "strings" - "sync" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/gorilla/websocket" - "github.com/rs/zerolog" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -const ( - osmosisV2WSHost = "api.osmo-api.network.umee.cc" - osmosisV2WSPath = "ws" - osmosisV2RestHost = "https://api.osmo-api.network.umee.cc" - osmosisV2RestPath = "/assetpairs" -) - -var _ Provider = (*OsmosisV2Provider)(nil) - -type ( - // OsmosisV2Provider defines an Oracle provider implemented by UMEE's - // Osmosis API. - // - // REF: https://github.com/umee-network/osmosis-api - OsmosisV2Provider struct { - wsc *WebsocketController - wsURL url.URL - logger zerolog.Logger - mtx sync.RWMutex - endpoints Endpoint - tickers map[string]types.TickerPrice // Symbol => TickerPrice - candles map[string][]types.CandlePrice // Symbol => CandlePrice - subscribedPairs map[string]types.CurrencyPair // Symbol => types.CurrencyPair - } - - OsmosisV2Ticker struct { - Price string `json:"Price"` - Volume string `json:"Volume"` - } - - OsmosisV2Candle struct { - Close string `json:"Close"` - Volume string `json:"Volume"` - EndTime int64 `json:"EndTime"` - } - - // OsmosisV2PairsSummary defines the response structure for an Osmosis pairs - // summary. - OsmosisV2PairsSummary struct { - Data []OsmosisPairData `json:"data"` - } - - // OsmosisV2PairData defines the data response structure for an Osmosis pair. - OsmosisV2PairData struct { - Base string `json:"base_symbol"` - Quote string `json:"quote_symbol"` - } -) - -func NewOsmosisV2Provider( - ctx context.Context, - logger zerolog.Logger, - endpoints Endpoint, - pairs ...types.CurrencyPair, -) (*OsmosisV2Provider, error) { - if endpoints.Name != ProviderOsmosisV2 { - endpoints = Endpoint{ - Name: ProviderOsmosisV2, - Rest: osmosisV2RestHost, - Websocket: osmosisV2WSHost, - } - } - - wsURL := url.URL{ - Scheme: "wss", - Host: endpoints.Websocket, - Path: osmosisV2WSPath, - } - - osmosisV2Logger := logger.With().Str("provider", "osmosisv2").Logger() - - provider := &OsmosisV2Provider{ - wsURL: wsURL, - logger: osmosisV2Logger, - endpoints: endpoints, - tickers: map[string]types.TickerPrice{}, - candles: map[string][]types.CandlePrice{}, - subscribedPairs: map[string]types.CurrencyPair{}, - } - - provider.setSubscribedPairs(pairs...) - - provider.wsc = NewWebsocketController( - ctx, - endpoints.Name, - wsURL, - []interface{}{""}, - provider.messageReceived, - defaultPingDuration, - websocket.PingMessage, - osmosisV2Logger, - ) - provider.wsc.StartConnections() - - return provider, nil -} - -// SubscribeCurrencyPairs sends the new subscription messages to the websocket -// and adds them to the providers subscribedPairs array -func (p *OsmosisV2Provider) SubscribeCurrencyPairs(cps ...types.CurrencyPair) { - p.mtx.Lock() - defer p.mtx.Unlock() - - p.setSubscribedPairs(cps...) -} - -// GetTickerPrices returns the tickerPrices based on the saved map. -func (p *OsmosisV2Provider) GetTickerPrices(pairs ...types.CurrencyPair) (map[string]types.TickerPrice, error) { - tickerPrices := make(map[string]types.TickerPrice, len(pairs)) - - tickerErrs := 0 - for _, cp := range pairs { - key := currencyPairToOsmosisV2Pair(cp) - price, err := p.getTickerPrice(key) - if err != nil { - p.logger.Warn().Err(err) - tickerErrs++ - continue - } - tickerPrices[cp.String()] = price - } - - if tickerErrs == len(pairs) { - return nil, fmt.Errorf( - types.ErrNoTickers.Error(), - p.endpoints.Name, - pairs, - ) - } - return tickerPrices, nil -} - -// GetCandlePrices returns the candlePrices based on the saved map -func (p *OsmosisV2Provider) GetCandlePrices(pairs ...types.CurrencyPair) (map[string][]types.CandlePrice, error) { - candlePrices := make(map[string][]types.CandlePrice, len(pairs)) - - candleErrs := 0 - for _, cp := range pairs { - key := currencyPairToOsmosisV2Pair(cp) - prices, err := p.getCandlePrices(key) - if err != nil { - p.logger.Warn().Err(err) - candleErrs++ - continue - } - candlePrices[cp.String()] = prices - } - - if candleErrs == len(pairs) { - return nil, fmt.Errorf( - types.ErrNoCandles.Error(), - p.endpoints.Name, - pairs, - ) - } - return candlePrices, nil -} - -func (p *OsmosisV2Provider) getTickerPrice(key string) (types.TickerPrice, error) { - p.mtx.RLock() - defer p.mtx.RUnlock() - - ticker, ok := p.tickers[key] - if !ok { - return types.TickerPrice{}, fmt.Errorf( - types.ErrTickerNotFound.Error(), - p.endpoints.Name, - key, - ) - } - - return ticker, nil -} - -func (p *OsmosisV2Provider) getCandlePrices(key string) ([]types.CandlePrice, error) { - p.mtx.RLock() - defer p.mtx.RUnlock() - - candles, ok := p.candles[key] - if !ok { - return []types.CandlePrice{}, fmt.Errorf( - types.ErrCandleNotFound.Error(), - p.endpoints.Name, - key, - ) - } - - candleList := []types.CandlePrice{} - candleList = append(candleList, candles...) - - return candleList, nil -} - -func (p *OsmosisV2Provider) messageReceived(_ int, _ *WebsocketConnection, bz []byte) { - // check if message is an ack first - if string(bz) == "ack" { - return - } - - var ( - messageResp map[string]interface{} - messageErr error - tickerResp OsmosisV2Ticker - tickerErr error - candleResp []OsmosisV2Candle - candleErr error - ) - - messageErr = json.Unmarshal(bz, &messageResp) - if messageErr != nil { - p.logger.Error(). - Int("length", len(bz)). - AnErr("message", messageErr). - Msg("Error on receive message") - } - - // Check the response for currency pairs that the provider is subscribed - // to and determine whether it is a ticker or candle. - for _, pair := range p.subscribedPairs { - osmosisV2Pair := currencyPairToOsmosisV2Pair(pair) - if msg, ok := messageResp[osmosisV2Pair]; ok { - switch v := msg.(type) { - // ticker response - case map[string]interface{}: - tickerString, _ := json.Marshal(v) - tickerErr = json.Unmarshal(tickerString, &tickerResp) - if tickerErr != nil { - p.logger.Error(). - Int("length", len(bz)). - AnErr("ticker", tickerErr). - Msg("Error on receive message") - continue - } - p.setTickerPair( - osmosisV2Pair, - tickerResp, - ) - telemetryWebsocketMessage(ProviderOsmosisV2, MessageTypeTicker) - continue - - // candle response - case []interface{}: - // use latest candlestick in list if there is one - if len(v) == 0 { - continue - } - candleString, _ := json.Marshal(v) - candleErr = json.Unmarshal(candleString, &candleResp) - if candleErr != nil { - p.logger.Error(). - Int("length", len(bz)). - AnErr("candle", candleErr). - Msg("Error on receive message") - continue - } - for _, singleCandle := range candleResp { - p.setCandlePair( - osmosisV2Pair, - singleCandle, - ) - } - telemetryWebsocketMessage(ProviderOsmosisV2, MessageTypeCandle) - continue - } - } - } -} - -func (p *OsmosisV2Provider) setTickerPair(symbol string, tickerPair OsmosisV2Ticker) { - p.mtx.Lock() - defer p.mtx.Unlock() - - price, err := sdk.NewDecFromStr(tickerPair.Price) - if err != nil { - p.logger.Warn().Err(err).Msg("osmosisv2: failed to parse ticker price") - return - } - volume, err := sdk.NewDecFromStr(tickerPair.Volume) - if err != nil { - p.logger.Warn().Err(err).Msg("osmosisv2: failed to parse ticker volume") - return - } - - p.tickers[symbol] = types.TickerPrice{ - Price: price, - Volume: volume, - } -} - -func (p *OsmosisV2Provider) setCandlePair(symbol string, candlePair OsmosisV2Candle) { - p.mtx.Lock() - defer p.mtx.Unlock() - - close, err := sdk.NewDecFromStr(candlePair.Close) - if err != nil { - p.logger.Warn().Err(err).Msg("osmosisv2: failed to parse candle close") - return - } - volume, err := sdk.NewDecFromStr(candlePair.Volume) - if err != nil { - p.logger.Warn().Err(err).Msg("osmosisv2: failed to parse candle volume") - return - } - candle := types.CandlePrice{ - Price: close, - Volume: volume, - TimeStamp: candlePair.EndTime, - } - - staleTime := PastUnixTime(providerCandlePeriod) - candleList := []types.CandlePrice{} - candleList = append(candleList, candle) - for _, c := range p.candles[symbol] { - if staleTime < c.TimeStamp { - candleList = append(candleList, c) - } - } - - p.candles[symbol] = candleList -} - -// setSubscribedPairs sets N currency pairs to the map of subscribed pairs. -func (p *OsmosisV2Provider) setSubscribedPairs(cps ...types.CurrencyPair) { - for _, cp := range cps { - p.subscribedPairs[cp.String()] = cp - } -} - -// GetAvailablePairs returns all pairs to which the provider can subscribe. -// ex.: map["ATOMUSDT" => {}, "UMEEUSDC" => {}]. -func (p *OsmosisV2Provider) GetAvailablePairs() (map[string]struct{}, error) { - resp, err := http.Get(p.endpoints.Rest + osmosisV2RestPath) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var pairsSummary OsmosisV2PairsSummary - if err := json.NewDecoder(resp.Body).Decode(&pairsSummary); err != nil { - return nil, err - } - - availablePairs := make(map[string]struct{}, len(pairsSummary.Data)) - for _, pair := range pairsSummary.Data { - cp := types.CurrencyPair{ - Base: strings.ToUpper(pair.Base), - Quote: strings.ToUpper(pair.Quote), - } - availablePairs[cp.String()] = struct{}{} - } - - return availablePairs, nil -} - -// currencyPairToOsmosisV2Pair receives a currency pair and return osmosisv2 -// ticker symbol atomusdt@ticker. -func currencyPairToOsmosisV2Pair(cp types.CurrencyPair) string { - return cp.Base + "/" + cp.Quote -} diff --git a/price-feeder/oracle/provider/osmosisv2_test.go b/price-feeder/oracle/provider/osmosisv2_test.go deleted file mode 100644 index 4983c3cf8c..0000000000 --- a/price-feeder/oracle/provider/osmosisv2_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package provider - -import ( - "context" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -func TestOsmosisV2Provider_GetTickerPrices(t *testing.T) { - p, err := NewOsmosisV2Provider( - context.TODO(), - zerolog.Nop(), - Endpoint{}, - types.CurrencyPair{Base: "OSMO", Quote: "ATOM"}, - ) - require.NoError(t, err) - - t.Run("valid_request_single_ticker", func(t *testing.T) { - lastPrice := sdk.MustNewDecFromStr("34.69000000") - volume := sdk.MustNewDecFromStr("2396974.02000000") - - tickerMap := map[string]types.TickerPrice{} - tickerMap["OSMO/ATOM"] = types.TickerPrice{ - Price: lastPrice, - Volume: volume, - } - - p.tickers = tickerMap - - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "OSMO", Quote: "ATOM"}) - require.NoError(t, err) - require.Len(t, prices, 1) - require.Equal(t, lastPrice, prices["OSMOATOM"].Price) - require.Equal(t, volume, prices["OSMOATOM"].Volume) - }) - - t.Run("valid_request_multi_ticker", func(t *testing.T) { - lastPriceAtom := sdk.MustNewDecFromStr("34.69000000") - lastPriceLuna := sdk.MustNewDecFromStr("41.35000000") - volume := sdk.MustNewDecFromStr("2396974.02000000") - - tickerMap := map[string]types.TickerPrice{} - tickerMap["ATOM/USDT"] = types.TickerPrice{ - Price: lastPriceAtom, - Volume: volume, - } - - tickerMap["LUNA/USDT"] = types.TickerPrice{ - Price: lastPriceLuna, - Volume: volume, - } - - p.tickers = tickerMap - prices, err := p.GetTickerPrices( - types.CurrencyPair{Base: "ATOM", Quote: "USDT"}, - types.CurrencyPair{Base: "LUNA", Quote: "USDT"}, - ) - require.NoError(t, err) - require.Len(t, prices, 2) - require.Equal(t, lastPriceAtom, prices["ATOMUSDT"].Price) - require.Equal(t, volume, prices["ATOMUSDT"].Volume) - require.Equal(t, lastPriceLuna, prices["LUNAUSDT"].Price) - require.Equal(t, volume, prices["LUNAUSDT"].Volume) - }) - - t.Run("invalid_request_invalid_ticker", func(t *testing.T) { - prices, err := p.GetTickerPrices(types.CurrencyPair{Base: "FOO", Quote: "BAR"}) - require.Error(t, err) - require.EqualError(t, err, "osmosisv2 has no ticker data for requested pairs: [FOOBAR]") - require.Nil(t, prices) - }) -} - -func TestOsmosisV2Provider_GetCandlePrices(t *testing.T) { - p, err := NewOsmosisV2Provider( - context.TODO(), - zerolog.Nop(), - Endpoint{}, - types.CurrencyPair{Base: "OSMO", Quote: "ATOM"}, - ) - require.NoError(t, err) - - t.Run("valid_request_single_candle", func(t *testing.T) { - price := "34.689998626708984000" - volume := "2396974.000000000000000000" - time := int64(1000000) - - candle := OsmosisV2Candle{ - Volume: volume, - Close: price, - EndTime: time, - } - - p.setCandlePair("OSMO/ATOM", candle) - - prices, err := p.GetCandlePrices(types.CurrencyPair{Base: "OSMO", Quote: "ATOM"}) - require.NoError(t, err) - require.Len(t, prices, 1) - require.Equal(t, sdk.MustNewDecFromStr(price), prices["OSMOATOM"][0].Price) - require.Equal(t, sdk.MustNewDecFromStr(volume), prices["OSMOATOM"][0].Volume) - require.Equal(t, time, prices["OSMOATOM"][0].TimeStamp) - }) - - t.Run("invalid_request_invalid_candle", func(t *testing.T) { - prices, err := p.GetCandlePrices(types.CurrencyPair{Base: "FOO", Quote: "BAR"}) - require.EqualError(t, err, "osmosisv2 has no candle data for requested pairs: [FOOBAR]") - require.Nil(t, prices) - }) -} - -func TestOsmosisV2CurrencyPairToOsmosisV2Pair(t *testing.T) { - cp := types.CurrencyPair{Base: "ATOM", Quote: "USDT"} - osmosisv2Symbol := currencyPairToOsmosisV2Pair(cp) - require.Equal(t, osmosisv2Symbol, "ATOM/USDT") -} diff --git a/price-feeder/oracle/provider/provider.go b/price-feeder/oracle/provider/provider.go deleted file mode 100644 index 3e4c4530b6..0000000000 --- a/price-feeder/oracle/provider/provider.go +++ /dev/null @@ -1,114 +0,0 @@ -package provider - -import ( - "fmt" - "net/http" - "time" - - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -const ( - defaultTimeout = 10 * time.Second - providerCandlePeriod = 10 * time.Minute - - ProviderKraken Name = "kraken" - ProviderBinance Name = "binance" - ProviderBinanceUS Name = "binanceus" - ProviderOsmosis Name = "osmosis" - ProviderOsmosisV2 Name = "osmosisv2" - ProviderHuobi Name = "huobi" - ProviderOkx Name = "okx" - ProviderGate Name = "gate" - ProviderCoinbase Name = "coinbase" - ProviderBitget Name = "bitget" - ProviderMexc Name = "mexc" - ProviderCrypto Name = "crypto" - ProviderMock Name = "mock" -) - -var ping = []byte("ping") - -type ( - // Provider defines an interface an exchange price provider must implement. - Provider interface { - // GetTickerPrices returns the tickerPrices based on the provided pairs. - GetTickerPrices(...types.CurrencyPair) (map[string]types.TickerPrice, error) - - // GetCandlePrices returns the candlePrices based on the provided pairs. - GetCandlePrices(...types.CurrencyPair) (map[string][]types.CandlePrice, error) - - // GetAvailablePairs return all available pairs symbol to subscribe. - GetAvailablePairs() (map[string]struct{}, error) - - // SubscribeCurrencyPairs sends subscription messages for the new currency - // pairs and adds them to the providers subscribed pairs - SubscribeCurrencyPairs(...types.CurrencyPair) - } - - // Name name of an oracle provider. Usually it is an exchange - // but this can be any provider name that can give token prices - // examples.: "binance", "osmosis", "kraken". - Name string - - // AggregatedProviderPrices defines a type alias for a map - // of provider -> asset -> TickerPrice - AggregatedProviderPrices map[Name]map[string]types.TickerPrice - - // AggregatedProviderCandles defines a type alias for a map - // of provider -> asset -> []types.CandlePrice - AggregatedProviderCandles map[Name]map[string][]types.CandlePrice - - // Endpoint defines an override setting in our config for the - // hardcoded rest and websocket api endpoints. - Endpoint struct { - // Name of the provider, ex. "binance" - Name Name `toml:"name"` - - // Rest endpoint for the provider, ex. "https://api1.binance.com" - Rest string `toml:"rest"` - - // Websocket endpoint for the provider, ex. "stream.binance.com:9443" - Websocket string `toml:"websocket"` - } -) - -// String cast provider name to string. -func (n Name) String() string { - return string(n) -} - -// preventRedirect avoid any redirect in the http.Client the request call -// will not return an error, but a valid response with redirect response code. -func preventRedirect(*http.Request, []*http.Request) error { - return http.ErrUseLastResponse -} - -func newDefaultHTTPClient() *http.Client { - return newHTTPClientWithTimeout(defaultTimeout) -} - -func newHTTPClientWithTimeout(timeout time.Duration) *http.Client { - return &http.Client{ - Timeout: timeout, - CheckRedirect: preventRedirect, - } -} - -// PastUnixTime returns a millisecond timestamp that represents the unix time -// minus t. -func PastUnixTime(t time.Duration) int64 { - return time.Now().Add(t*-1).Unix() * int64(time.Second/time.Millisecond) -} - -// SecondsToMilli converts seconds to milliseconds for our unix timestamps. -func SecondsToMilli(t int64) int64 { - return t * int64(time.Second/time.Millisecond) -} - -func checkHTTPStatus(resp *http.Response) error { - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("unexpected status: %s", resp.Status) - } - return nil -} diff --git a/price-feeder/oracle/provider/telemetry.go b/price-feeder/oracle/provider/telemetry.go deleted file mode 100644 index b01ea34620..0000000000 --- a/price-feeder/oracle/provider/telemetry.go +++ /dev/null @@ -1,99 +0,0 @@ -package provider - -import ( - "github.com/armon/go-metrics" - "github.com/cosmos/cosmos-sdk/telemetry" -) - -const ( - MessageTypeCandle = MessageType("candle") - MessageTypeTicker = MessageType("ticker") - MessageTypeTrade = MessageType("trade") -) - -type ( - MessageType string -) - -// String cast provider MessageType to string. -func (mt MessageType) String() string { - return string(mt) -} - -// providerLabel returns a label based on the provider name. -func providerLabel(n Name) metrics.Label { - return metrics.Label{ - Name: "provider", - Value: n.String(), - } -} - -// messageTypeLabel returns a label based on the message type. -func messageTypeLabel(mt MessageType) metrics.Label { - return metrics.Label{ - Name: "type", - Value: mt.String(), - } -} - -// telemetryWebsocketReconnect gives an standard way to add -// `price_feeder_websocket_reconnect` metric. -func telemetryWebsocketReconnect(n Name) { - telemetry.IncrCounterWithLabels( - []string{ - "websocket", - "reconnect", - }, - 1, - []metrics.Label{ - providerLabel(n), - }, - ) -} - -// telemetryWebsocketSubscribeCurrencyPairs gives an standard way to add -// `price_feeder_websocket_subscribe_currency_pairs{provider="x"}` metric. -func telemetryWebsocketSubscribeCurrencyPairs(n Name, incr int) { - telemetry.IncrCounterWithLabels( - []string{ - "websocket", - "subscribe", - "currency_pairs", - }, - float32(incr), - []metrics.Label{ - providerLabel(n), - }, - ) -} - -// telemetryWebsocketMessage gives an standard way to add -// `price_feeder_websocket_message{type="x", provider="x"}` metric. -func telemetryWebsocketMessage(n Name, mt MessageType) { - telemetry.IncrCounterWithLabels( - []string{ - "websocket", - "message", - }, - 1, - []metrics.Label{ - providerLabel(n), - messageTypeLabel(mt), - }, - ) -} - -// TelemetryFailure gives an standard way to add -// `price_feeder_failure_provider{type="x", provider="x"}` metric. -func TelemetryFailure(n Name, mt MessageType) { - telemetry.IncrCounterWithLabels( - []string{ - "failure", - }, - 1, - []metrics.Label{ - providerLabel(n), - messageTypeLabel(mt), - }, - ) -} diff --git a/price-feeder/oracle/provider/websocket_controller.go b/price-feeder/oracle/provider/websocket_controller.go deleted file mode 100644 index 1b2f801bbe..0000000000 --- a/price-feeder/oracle/provider/websocket_controller.go +++ /dev/null @@ -1,311 +0,0 @@ -package provider - -import ( - "context" - "fmt" - "math" - "net/url" - "sync" - "time" - - "github.com/gorilla/websocket" - "github.com/rs/zerolog" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -const ( - defaultReadNewWSMessage = 50 * time.Millisecond - defaultMaxConnectionTime = time.Hour * 23 // should be < 24h - defaultPingDuration = 15 * time.Second - disabledPingDuration = time.Duration(0) - startingReconnectDuration = 5 * time.Second - maxRetryMultiplier = 25 // max retry duration: 52m5s -) - -type ( - MessageHandler func(int, *WebsocketConnection, []byte) - - // WebsocketController defines a provider agnostic websocket handler - // that manages reconnecting, subscribing, and receiving messages - WebsocketConnection struct { - parentCtx context.Context - websocketCtx context.Context - websocketCancelFunc context.CancelFunc - providerName Name - websocketURL url.URL - subscriptionMsg interface{} - messageHandler MessageHandler - pingDuration time.Duration - pingMessageType uint - logger zerolog.Logger - - mtx sync.Mutex - client *websocket.Conn - reconnectCounter uint - } - - WebsocketController struct { - parentCtx context.Context - providerName Name - websocketURL url.URL - logger zerolog.Logger - connections []*WebsocketConnection - } -) - -func NewWebsocketController( - ctx context.Context, - providerName Name, - websocketURL url.URL, - subscriptionMsgs []interface{}, - messageHandler MessageHandler, - pingDuration time.Duration, - pingMessageType uint, - logger zerolog.Logger, -) *WebsocketController { - connections := make([]*WebsocketConnection, 0) - - for _, subMsg := range subscriptionMsgs { - connection := &WebsocketConnection{ - parentCtx: ctx, - providerName: providerName, - websocketURL: websocketURL, - subscriptionMsg: subMsg, - messageHandler: messageHandler, - pingDuration: pingDuration, - pingMessageType: pingMessageType, - logger: logger, - } - connections = append(connections, connection) - } - - return &WebsocketController{ - parentCtx: ctx, - providerName: providerName, - websocketURL: websocketURL, - logger: logger, - connections: connections, - } -} - -func (wsc *WebsocketController) StartConnections() { - for _, conn := range wsc.connections { - go conn.start() - } -} - -// AddSubscriptionMsgs immediately sends the new subscription messages and -// adds them to the subscriptionMsgs array if successful -func (wsc *WebsocketController) AddWebsocketConnection( - msgs []interface{}, - messageHandler MessageHandler, - pingDuration time.Duration, - pingMessageType uint, -) { - for _, msg := range msgs { - conn := &WebsocketConnection{ - parentCtx: wsc.parentCtx, - providerName: wsc.providerName, - websocketURL: wsc.websocketURL, - subscriptionMsg: msg, - messageHandler: messageHandler, - pingDuration: pingDuration, - pingMessageType: pingMessageType, - logger: wsc.logger, - } - wsc.connections = append(wsc.connections, conn) - go conn.start() - } -} - -// Start will continuously loop and attempt connecting to the websocket -// until a successful connection is made. It then starts the ping -// service and read listener in new go routines and sends subscription -// messages using the passed in subscription messages -func (conn *WebsocketConnection) start() { - connectTicker := time.NewTicker(time.Millisecond) - defer connectTicker.Stop() - - for { - if err := conn.connect(); err != nil { - conn.logger.Err(err).Send() - select { - case <-conn.parentCtx.Done(): - return - case <-connectTicker.C: - connectTicker.Reset(conn.iterateRetryCounter()) - continue - } - } - - go conn.readWebSocket() - go conn.pingLoop() - - if err := conn.subscribe(conn.subscriptionMsg); err != nil { - conn.logger.Err(err).Send() - conn.close() - continue - } - return - } -} - -// connect dials the websocket and sets the client to the established connection -func (conn *WebsocketConnection) connect() error { - conn.mtx.Lock() - defer conn.mtx.Unlock() - - conn.logger.Debug().Msg("connecting to websocket") - connection, resp, err := websocket.DefaultDialer.Dial(conn.websocketURL.String(), nil) - if err != nil { - return fmt.Errorf(types.ErrWebsocketDial.Error(), conn.providerName, err) - } - defer resp.Body.Close() - conn.client = connection - conn.websocketCtx, conn.websocketCancelFunc = context.WithCancel(conn.parentCtx) - conn.client.SetPingHandler(conn.pingHandler) - conn.reconnectCounter = 0 - return nil -} - -func (conn *WebsocketConnection) iterateRetryCounter() time.Duration { - if conn.reconnectCounter < 25 { - conn.reconnectCounter++ - } - multiplier := math.Pow(float64(conn.reconnectCounter), 2) - return startingReconnectDuration * time.Duration(multiplier) -} - -// subscribe sends the WebsocketControllers subscription messages to the websocket -func (conn *WebsocketConnection) subscribe(msg interface{}) error { - telemetryWebsocketSubscribeCurrencyPairs(conn.providerName, 1) - conn.logger.Debug().Interface("msg", msg).Msg("sending subscription message") - if err := conn.SendJSON(msg); err != nil { - return fmt.Errorf(types.ErrWebsocketSend.Error(), conn.providerName, err) - } - return nil -} - -// SendJSON sends a json message to the websocket connection using the Websocket -// Controller mutex to ensure multiple writes do not happen at once -func (conn *WebsocketConnection) SendJSON(msg interface{}) error { - conn.mtx.Lock() - defer conn.mtx.Unlock() - - if conn.client == nil { - return fmt.Errorf("unable to send JSON on a closed connection") - } - if err := conn.client.WriteJSON(msg); err != nil { - return fmt.Errorf(types.ErrWebsocketSend.Error(), conn.providerName, err) - } - return nil -} - -// ping sends a ping to the server every defaultPingDuration -func (conn *WebsocketConnection) pingLoop() { - if conn.pingDuration == disabledPingDuration { - return // disable ping loop if disabledPingDuration - } - pingTicker := time.NewTicker(conn.pingDuration) - defer pingTicker.Stop() - - for { - err := conn.ping() - if err != nil { - return - } - select { - case <-conn.websocketCtx.Done(): - return - case <-pingTicker.C: - continue - } - } -} - -func (conn *WebsocketConnection) ping() error { - conn.mtx.Lock() - defer conn.mtx.Unlock() - - if conn.client == nil { - return fmt.Errorf("unable to ping closed connection") - } - err := conn.client.WriteMessage(int(conn.pingMessageType), ping) - if err != nil { - conn.logger.Err(fmt.Errorf(types.ErrWebsocketSend.Error(), conn.providerName, err)).Send() - } - return err -} - -// readWebSocket continuously reads from the websocket and relays messages -// to the passed in messageHandler. On websocket error this function -// terminates and starts the reconnect process. -// Some providers (Binance) will only allow a valid connection for 24 hours -// so we manually disconnect and reconnect every 23 hours (defaultMaxConnectionTime) -func (conn *WebsocketConnection) readWebSocket() { - reconnectTicker := time.NewTicker(defaultMaxConnectionTime) - defer reconnectTicker.Stop() - - for { - select { - case <-conn.websocketCtx.Done(): - conn.close() - return - case <-time.After(defaultReadNewWSMessage): - messageType, bz, err := conn.client.ReadMessage() - if err != nil { - conn.logger.Err(fmt.Errorf(types.ErrWebsocketRead.Error(), conn.providerName, err)).Send() - conn.reconnect() - return - } - conn.readSuccess(messageType, bz) - case <-reconnectTicker.C: - conn.reconnect() - return - } - } -} - -func (conn *WebsocketConnection) readSuccess(messageType int, bz []byte) { - if len(bz) == 0 { - return - } - // mexc and bitget do not send a valid pong response code so check for it here - if string(bz) == "pong" { - return - } - - conn.messageHandler(messageType, conn, bz) -} - -// close sends a close message to the websocket and sets the client to nil -func (conn *WebsocketConnection) close() { - conn.mtx.Lock() - defer conn.mtx.Unlock() - - conn.logger.Debug().Msg("closing websocket") - conn.websocketCancelFunc() - if conn.client == nil { - return - } - if err := conn.client.Close(); err != nil { - conn.logger.Err(fmt.Errorf(types.ErrWebsocketClose.Error(), conn.providerName, err)).Send() - } - conn.client = nil -} - -// reconnect closes the current websocket and starts a new connection process -func (conn *WebsocketConnection) reconnect() { - conn.close() - go conn.start() - telemetryWebsocketReconnect(conn.providerName) -} - -// pingHandler is called by the websocket library whenever a ping message is received -// and responds with a pong message to the server -func (conn *WebsocketConnection) pingHandler(string) error { - if err := conn.client.WriteMessage(websocket.PongMessage, []byte("pong")); err != nil { - conn.logger.Error().Err(err).Msg("error sending pong") - } - return nil -} diff --git a/price-feeder/oracle/provider/websocket_controller_test.go b/price-feeder/oracle/provider/websocket_controller_test.go deleted file mode 100644 index 39e933dcba..0000000000 --- a/price-feeder/oracle/provider/websocket_controller_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package provider - -import ( - "testing" - - "github.com/gorilla/websocket" - "github.com/stretchr/testify/require" -) - -type TestProvider struct { - handlerCalled bool -} - -func (mp *TestProvider) messageHandler(int, *WebsocketConnection, []byte) { - mp.handlerCalled = true -} - -func TestWebsocketController_readSuccess(t *testing.T) { - testCases := []struct { - name string - messageType int - bz []byte - shouldCallMessageHandler bool - }{ - { - "message length is zero", - 1, - []byte{}, - false, - }, - { - "message is a pong", - 1, - []byte("pong"), - false, - }, - { - "is a valid message for the messageHandler", - 1, - []byte("asdf"), - true, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - provider := TestProvider{} - mockClient := new(websocket.Conn) - c := &WebsocketController{ - providerName: ProviderMock, - connections: []*WebsocketConnection{ - { - messageHandler: provider.messageHandler, - client: mockClient, - }, - }, - } - c.connections[0].readSuccess(testCase.messageType, testCase.bz) - require.Equal(t, provider.handlerCalled, testCase.shouldCallMessageHandler) - }) - } -} diff --git a/price-feeder/oracle/types/candle_price.go b/price-feeder/oracle/types/candle_price.go deleted file mode 100644 index f00eb95dd3..0000000000 --- a/price-feeder/oracle/types/candle_price.go +++ /dev/null @@ -1,29 +0,0 @@ -package types - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// CandlePrice defines price, volume, and time information for an exchange rate. -type CandlePrice struct { - Price sdk.Dec // last trade price - Volume sdk.Dec // volume - TimeStamp int64 // timestamp -} - -// NewCandlePrice parses the lastPrice and volume to a decimal and returns a CandlePrice -func NewCandlePrice(provider, symbol, lastPrice, volume string, timeStamp int64) (CandlePrice, error) { - price, err := sdk.NewDecFromStr(lastPrice) - if err != nil { - return CandlePrice{}, fmt.Errorf("failed to parse %s price (%s) for %s: %w", provider, lastPrice, symbol, err) - } - - volumeDec, err := sdk.NewDecFromStr(volume) - if err != nil { - return CandlePrice{}, fmt.Errorf("failed to parse %s volume (%s) for %s: %w", provider, volume, symbol, err) - } - - return CandlePrice{Price: price, Volume: volumeDec, TimeStamp: timeStamp}, nil -} diff --git a/price-feeder/oracle/types/candle_price_test.go b/price-feeder/oracle/types/candle_price_test.go deleted file mode 100644 index b7bcb75bab..0000000000 --- a/price-feeder/oracle/types/candle_price_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package types - -import ( - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" -) - -func TestNewCandlePrice(t *testing.T) { - price := "105473.43" - volume := "48394" - timeStamp := int64(1257894000) - - t.Run("when the inputs are valid", func(t *testing.T) { - candlePrice, err := NewCandlePrice("binance", "BTC", price, volume, timeStamp) - require.Nil(t, err, "expected the returned error to be nil") - - parsedPrice, _ := sdk.NewDecFromStr(price) - require.Equal(t, candlePrice.Price, parsedPrice) - - parsedVolume, _ := sdk.NewDecFromStr(volume) - require.Equal(t, candlePrice.Volume, parsedVolume) - - require.Equal(t, candlePrice.TimeStamp, timeStamp) - }) - - t.Run("when the lastPrice input is invalid", func(t *testing.T) { - _, err := NewCandlePrice("binance", "BTC", "bad_price", volume, timeStamp) - require.NotNil(t, err, "expected the returned error to not be nil") - }) - - t.Run("when the volume input is invalid", func(t *testing.T) { - _, err := NewCandlePrice("binance", "BTC", price, "bad_volume", timeStamp) - require.NotNil(t, err, "expected the returned error to not be nil") - }) -} diff --git a/price-feeder/oracle/types/currency.go b/price-feeder/oracle/types/currency.go deleted file mode 100644 index ad332520ea..0000000000 --- a/price-feeder/oracle/types/currency.go +++ /dev/null @@ -1,28 +0,0 @@ -package types - -// CurrencyPair defines a currency exchange pair consisting of a base and a quote. -// We primarily utilize the base for broadcasting exchange rates and use the -// pair for querying for the ticker prices. -type CurrencyPair struct { - Base string - Quote string -} - -// String implements the Stringer interface and defines a ticker symbol for -// querying the exchange rate. -func (cp CurrencyPair) String() string { - return cp.Base + cp.Quote -} - -// MapPairsToSlice returns the map of currency pairs as slice. -func MapPairsToSlice(mapPairs map[string]CurrencyPair) []CurrencyPair { - currencyPairs := make([]CurrencyPair, len(mapPairs)) - - iterator := 0 - for _, cp := range mapPairs { - currencyPairs[iterator] = cp - iterator++ - } - - return currencyPairs -} diff --git a/price-feeder/oracle/types/errors.go b/price-feeder/oracle/types/errors.go deleted file mode 100644 index f817346d5e..0000000000 --- a/price-feeder/oracle/types/errors.go +++ /dev/null @@ -1,22 +0,0 @@ -package types - -import ( - sdkerrors "cosmossdk.io/errors" -) - -const ModuleName = "price-feeder" - -// Price feeder errors -var ( - ErrProviderConnection = sdkerrors.Register(ModuleName, 2, "provider connection") - ErrMissingExchangeRate = sdkerrors.Register(ModuleName, 3, "missing exchange rate for %s") - ErrTickerNotFound = sdkerrors.Register(ModuleName, 4, "%s failed to get ticker price for %s") - ErrCandleNotFound = sdkerrors.Register(ModuleName, 5, "%s failed to get candle price for %s") - ErrNoTickers = sdkerrors.Register(ModuleName, 6, "%s has no ticker data for requested pairs: %v") - ErrNoCandles = sdkerrors.Register(ModuleName, 7, "%s has no candle data for requested pairs: %v") - - ErrWebsocketDial = sdkerrors.Register(ModuleName, 8, "error connecting to %s websocket: %w") - ErrWebsocketClose = sdkerrors.Register(ModuleName, 9, "error closing %s websocket: %w") - ErrWebsocketSend = sdkerrors.Register(ModuleName, 10, "error sending to %s websocket: %w") - ErrWebsocketRead = sdkerrors.Register(ModuleName, 11, "error reading from %s websocket: %w") -) diff --git a/price-feeder/oracle/types/ticker_price.go b/price-feeder/oracle/types/ticker_price.go deleted file mode 100644 index 2ed5e132fe..0000000000 --- a/price-feeder/oracle/types/ticker_price.go +++ /dev/null @@ -1,28 +0,0 @@ -package types - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// TickerPrice defines price and volume information for a symbol or ticker exchange rate. -type TickerPrice struct { - Price sdk.Dec // last trade price - Volume sdk.Dec // 24h volume -} - -// NewTickerPrice parses the lastPrice and volume to a decimal and returns a TickerPrice -func NewTickerPrice(provider, symbol, lastPrice, volume string) (TickerPrice, error) { - price, err := sdk.NewDecFromStr(lastPrice) - if err != nil { - return TickerPrice{}, fmt.Errorf("failed to parse %s price (%s) for %s: %w", provider, lastPrice, symbol, err) - } - - volumeDec, err := sdk.NewDecFromStr(volume) - if err != nil { - return TickerPrice{}, fmt.Errorf("failed to parse %s volume (%s) for %s: %w", provider, volume, symbol, err) - } - - return TickerPrice{Price: price, Volume: volumeDec}, nil -} diff --git a/price-feeder/oracle/types/ticker_price_test.go b/price-feeder/oracle/types/ticker_price_test.go deleted file mode 100644 index a0e6d610c4..0000000000 --- a/price-feeder/oracle/types/ticker_price_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package types - -import ( - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" -) - -func TestNewTicketPrice(t *testing.T) { - price := "105473.43" - volume := "48394" - - t.Run("when the inputs are valid", func(t *testing.T) { - tickerPrice, err := NewTickerPrice("binance", "BTC", price, volume) - require.Nil(t, err, "expected the returned error to be nil") - - parsedPrice, _ := sdk.NewDecFromStr(price) - require.Equal(t, tickerPrice.Price, parsedPrice) - - parsedVolume, _ := sdk.NewDecFromStr(volume) - require.Equal(t, tickerPrice.Volume, parsedVolume) - }) - - t.Run("when the lastPrice input is invalid", func(t *testing.T) { - _, err := NewTickerPrice("binance", "BTC", "bad_price", volume) - require.NotNil(t, err, "expected the returned error to not be nil") - }) - - t.Run("when the volume input is invalid", func(t *testing.T) { - _, err := NewTickerPrice("binance", "BTC", price, "bad_volume") - require.NotNil(t, err, "expected the returned error to not be nil") - }) -} diff --git a/price-feeder/oracle/util.go b/price-feeder/oracle/util.go deleted file mode 100644 index c776697bcf..0000000000 --- a/price-feeder/oracle/util.go +++ /dev/null @@ -1,222 +0,0 @@ -package oracle - -import ( - "fmt" - "sort" - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/umee-network/umee/price-feeder/v2/oracle/provider" -) - -var ( - minimumTimeWeight = sdk.MustNewDecFromStr("0.2000") - minimumCandleVolume = sdk.MustNewDecFromStr("0.0001") -) - -const ( - // tvwapCandlePeriod represents the time period we use for tvwap in minutes - tvwapCandlePeriod = 5 * time.Minute -) - -// compute VWAP for each base by dividing the Σ {P * V} by Σ {V} -func vwap(weightedPrices, volumeSum map[string]sdk.Dec) map[string]sdk.Dec { - vwap := make(map[string]sdk.Dec) - - for base, p := range weightedPrices { - if !volumeSum[base].Equal(sdk.ZeroDec()) { - if _, ok := vwap[base]; !ok { - vwap[base] = sdk.ZeroDec() - } - - vwap[base] = p.Quo(volumeSum[base]) - } - } - - return vwap -} - -// ComputeVWAP computes the volume weighted average price for all price points -// for each ticker/exchange pair. The provided prices argument reflects a mapping -// of provider => { => , ...}. -// -// Ref: https://en.wikipedia.org/wiki/Volume-weighted_average_price -func ComputeVWAP(prices provider.AggregatedProviderPrices) map[string]sdk.Dec { - var ( - weightedPrices = make(map[string]sdk.Dec) - volumeSum = make(map[string]sdk.Dec) - ) - - for _, providerPrices := range prices { - for base, tp := range providerPrices { - if _, ok := weightedPrices[base]; !ok { - weightedPrices[base] = sdk.ZeroDec() - } - if _, ok := volumeSum[base]; !ok { - volumeSum[base] = sdk.ZeroDec() - } - - // weightedPrices[base] = Σ {P * V} for all TickerPrice - weightedPrices[base] = weightedPrices[base].Add(tp.Price.Mul(tp.Volume)) - - // track total volume for each base - volumeSum[base] = volumeSum[base].Add(tp.Volume) - } - } - - return vwap(weightedPrices, volumeSum) -} - -// ComputeTVWAP computes the time volume weighted average price for all points -// for each exchange pair. Filters out any candles that did not occur within -// timePeriod. The provided prices argument reflects a mapping of -// provider => { => , ...}. -// -// Ref : https://en.wikipedia.org/wiki/Time-weighted_average_price -func ComputeTVWAP(prices provider.AggregatedProviderCandles) (map[string]sdk.Dec, error) { - var ( - weightedPrices = make(map[string]sdk.Dec) - volumeSum = make(map[string]sdk.Dec) - now = provider.PastUnixTime(0) - timePeriod = provider.PastUnixTime(tvwapCandlePeriod) - ) - - for _, providerPrices := range prices { - for base := range providerPrices { - cp := providerPrices[base] - if len(cp) == 0 { - continue - } - - if _, ok := weightedPrices[base]; !ok { - weightedPrices[base] = sdk.ZeroDec() - } - if _, ok := volumeSum[base]; !ok { - volumeSum[base] = sdk.ZeroDec() - } - - // Sort by timestamp old -> new - sort.SliceStable(cp, func(i, j int) bool { - return cp[i].TimeStamp < cp[j].TimeStamp - }) - - period := sdk.NewDec(now - cp[0].TimeStamp) - if period.Equal(sdk.ZeroDec()) { - return nil, fmt.Errorf("unable to divide by zero") - } - // weightUnit = (1 - minimumTimeWeight) / period - weightUnit := sdk.OneDec().Sub(minimumTimeWeight).Quo(period) - - // get weighted prices, and sum of volumes - for _, candle := range cp { - // we only want candles within the last timePeriod - if timePeriod < candle.TimeStamp { - // timeDiff = now - candle.TimeStamp - timeDiff := sdk.NewDec(now - candle.TimeStamp) - // set minimum candle volume for low-trading assets - if candle.Volume.Equal(sdk.ZeroDec()) { - candle.Volume = minimumCandleVolume - } - - // volume = candle.Volume * (weightUnit * (period - timeDiff) + minimumTimeWeight) - volume := candle.Volume.Mul( - weightUnit.Mul(period.Sub(timeDiff).Add(minimumTimeWeight)), - ) - volumeSum[base] = volumeSum[base].Add(volume) - weightedPrices[base] = weightedPrices[base].Add(candle.Price.Mul(volume)) - } - } - - } - } - - return vwap(weightedPrices, volumeSum), nil -} - -// StandardDeviation returns maps of the standard deviations and means of assets. -// Will skip calculating for an asset if there are less than 3 prices. -func StandardDeviation( - prices map[provider.Name]map[string]sdk.Dec, -) (map[string]sdk.Dec, map[string]sdk.Dec, error) { - var ( - deviations = make(map[string]sdk.Dec) - means = make(map[string]sdk.Dec) - priceSlice = make(map[string][]sdk.Dec) - priceSums = make(map[string]sdk.Dec) - ) - - for _, providerPrices := range prices { - for base, p := range providerPrices { - if _, ok := priceSums[base]; !ok { - priceSums[base] = sdk.ZeroDec() - } - if _, ok := priceSlice[base]; !ok { - priceSlice[base] = []sdk.Dec{} - } - - priceSums[base] = priceSums[base].Add(p) - priceSlice[base] = append(priceSlice[base], p) - } - } - - for base, sum := range priceSums { - // Skip if standard deviation would not be meaningful - if len(priceSlice[base]) < 3 { - continue - } - if _, ok := deviations[base]; !ok { - deviations[base] = sdk.ZeroDec() - } - if _, ok := means[base]; !ok { - means[base] = sdk.ZeroDec() - } - - numPrices := int64(len(priceSlice[base])) - means[base] = sum.QuoInt64(numPrices) - varianceSum := sdk.ZeroDec() - - for _, price := range priceSlice[base] { - deviation := price.Sub(means[base]) - varianceSum = varianceSum.Add(deviation.Mul(deviation)) - } - - variance := varianceSum.QuoInt64(numPrices) - - standardDeviation, err := variance.ApproxSqrt() - if err != nil { - return make(map[string]sdk.Dec), make(map[string]sdk.Dec), err - } - - deviations[base] = standardDeviation - } - - return deviations, means, nil -} - -// ComputeTvwapsByProvider computes the tvwap prices from candles for each provider separately and returns them -// in a map separated by provider name -func ComputeTvwapsByProvider(prices provider.AggregatedProviderCandles) (map[provider.Name]map[string]sdk.Dec, error) { - tvwaps := make(map[provider.Name]map[string]sdk.Dec) - var err error - - for providerName, candles := range prices { - singleProviderCandles := provider.AggregatedProviderCandles{"providerName": candles} - tvwaps[providerName], err = ComputeTVWAP(singleProviderCandles) - if err != nil { - return nil, err - } - } - return tvwaps, nil -} - -// ComputeVwapsByProvider computes the vwap prices from tickers for each provider separately and returns them -// in a map separated by provider name -func ComputeVwapsByProvider(prices provider.AggregatedProviderPrices) map[provider.Name]map[string]sdk.Dec { - vwaps := make(map[provider.Name]map[string]sdk.Dec) - - for providerName, tickers := range prices { - singleProviderCandles := provider.AggregatedProviderPrices{"providerName": tickers} - vwaps[providerName] = ComputeVWAP(singleProviderCandles) - } - return vwaps -} diff --git a/price-feeder/oracle/util_test.go b/price-feeder/oracle/util_test.go deleted file mode 100644 index ef2ba03f28..0000000000 --- a/price-feeder/oracle/util_test.go +++ /dev/null @@ -1,369 +0,0 @@ -package oracle_test - -import ( - "testing" - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" - "github.com/umee-network/umee/price-feeder/v2/oracle" - "github.com/umee-network/umee/price-feeder/v2/oracle/provider" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -func TestComputeVWAP(t *testing.T) { - testCases := map[string]struct { - prices map[provider.Name]map[string]types.TickerPrice - expected map[string]sdk.Dec - }{ - "empty prices": { - prices: make(map[provider.Name]map[string]types.TickerPrice), - expected: make(map[string]sdk.Dec), - }, - "nil prices": { - prices: nil, - expected: make(map[string]sdk.Dec), - }, - "valid prices": { - prices: map[provider.Name]map[string]types.TickerPrice{ - provider.ProviderBinance: { - "ATOM": types.TickerPrice{ - Price: sdk.MustNewDecFromStr("28.21000000"), - Volume: sdk.MustNewDecFromStr("2749102.78000000"), - }, - "UMEE": types.TickerPrice{ - Price: sdk.MustNewDecFromStr("1.13000000"), - Volume: sdk.MustNewDecFromStr("249102.38000000"), - }, - "LUNA": types.TickerPrice{ - Price: sdk.MustNewDecFromStr("64.87000000"), - Volume: sdk.MustNewDecFromStr("7854934.69000000"), - }, - }, - provider.ProviderKraken: { - "ATOM": types.TickerPrice{ - Price: sdk.MustNewDecFromStr("28.268700"), - Volume: sdk.MustNewDecFromStr("178277.53314385"), - }, - "LUNA": types.TickerPrice{ - Price: sdk.MustNewDecFromStr("64.87853000"), - Volume: sdk.MustNewDecFromStr("458917.46353577"), - }, - }, - "FOO": { - "ATOM": types.TickerPrice{ - Price: sdk.MustNewDecFromStr("28.168700"), - Volume: sdk.MustNewDecFromStr("4749102.53314385"), - }, - }, - }, - expected: map[string]sdk.Dec{ - "ATOM": sdk.MustNewDecFromStr("28.185812745610043621"), - "UMEE": sdk.MustNewDecFromStr("1.13000000"), - "LUNA": sdk.MustNewDecFromStr("64.870470848638112395"), - }, - }, - } - - for name, tc := range testCases { - tc := tc - - t.Run(name, func(t *testing.T) { - vwap := oracle.ComputeVWAP(tc.prices) - require.Len(t, vwap, len(tc.expected)) - - for k, v := range tc.expected { - require.Equalf(t, v, vwap[k], "unexpected VWAP for %s", k) - } - }) - } -} - -func TestComputeTVWAP(t *testing.T) { - testCases := map[string]struct { - candles provider.AggregatedProviderCandles - expected map[string]sdk.Dec - }{ - "empty prices": { - candles: make(provider.AggregatedProviderCandles), - expected: make(map[string]sdk.Dec), - }, - "nil prices": { - candles: nil, - expected: make(map[string]sdk.Dec), - }, - "valid prices": { - candles: map[provider.Name]map[string][]types.CandlePrice{ - provider.ProviderBinance: { - "ATOM": []types.CandlePrice{ - { - Price: sdk.MustNewDecFromStr("25.09183"), - Volume: sdk.MustNewDecFromStr("98444.123455"), - TimeStamp: provider.PastUnixTime(1 * time.Minute), - }, - }, - }, - provider.ProviderKraken: { - "ATOM": []types.CandlePrice{ - { - Price: sdk.MustNewDecFromStr("28.268700"), - Volume: sdk.MustNewDecFromStr("178277.53314385"), - TimeStamp: provider.PastUnixTime(2 * time.Minute), - }, - }, - "UMEE": []types.CandlePrice{ - { - Price: sdk.MustNewDecFromStr("1.13000000"), - Volume: sdk.MustNewDecFromStr("178277.53314385"), - TimeStamp: provider.PastUnixTime(2 * time.Minute), - }, - }, - "LUNA": []types.CandlePrice{ - { - Price: sdk.MustNewDecFromStr("64.87853000"), - Volume: sdk.MustNewDecFromStr("458917.46353577"), - TimeStamp: provider.PastUnixTime(1 * time.Minute), - }, - }, - }, - "FOO": { - "ATOM": []types.CandlePrice{ - { - Price: sdk.MustNewDecFromStr("28.168700"), - Volume: sdk.MustNewDecFromStr("4749102.53314385"), - TimeStamp: provider.PastUnixTime(130 * time.Second), - }, - }, - }, - }, - expected: map[string]sdk.Dec{ - "ATOM": sdk.MustNewDecFromStr("28.045149332478338614"), - "UMEE": sdk.MustNewDecFromStr("1.13000000"), - "LUNA": sdk.MustNewDecFromStr("64.878530000000000000"), - }, - }, - "one expired price": { - candles: map[provider.Name]map[string][]types.CandlePrice{ - provider.ProviderBinance: { - "ATOM": []types.CandlePrice{ - { - Price: sdk.MustNewDecFromStr("25.09183"), - Volume: sdk.MustNewDecFromStr("98444.123455"), - TimeStamp: provider.PastUnixTime(1 * time.Minute), - }, - }, - }, - provider.ProviderKraken: { - "ATOM": []types.CandlePrice{ - { - Price: sdk.MustNewDecFromStr("28.268700"), - Volume: sdk.MustNewDecFromStr("178277.53314385"), - TimeStamp: provider.PastUnixTime(2 * time.Minute), - }, - }, - "UMEE": []types.CandlePrice{ - { - Price: sdk.MustNewDecFromStr("1.13000000"), - Volume: sdk.MustNewDecFromStr("178277.53314385"), - TimeStamp: provider.PastUnixTime(2 * time.Minute), - }, - }, - "LUNA": []types.CandlePrice{ - { - Price: sdk.MustNewDecFromStr("64.87853000"), - Volume: sdk.MustNewDecFromStr("458917.46353577"), - TimeStamp: provider.PastUnixTime(1 * time.Minute), - }, - }, - }, - "FOO": { - "ATOM": []types.CandlePrice{ - { - Price: sdk.MustNewDecFromStr("28.168700"), - Volume: sdk.MustNewDecFromStr("4749102.53314385"), - TimeStamp: provider.PastUnixTime(5 * time.Minute), - }, - }, - }, - }, - expected: map[string]sdk.Dec{ - "ATOM": sdk.MustNewDecFromStr("26.601468076898424151"), - "UMEE": sdk.MustNewDecFromStr("1.13000000"), - "LUNA": sdk.MustNewDecFromStr("64.878530000000000000"), - }, - }, - "all expired prices": { - candles: map[provider.Name]map[string][]types.CandlePrice{ - provider.ProviderBinance: { - "ATOM": []types.CandlePrice{ - { - Price: sdk.MustNewDecFromStr("25.09183"), - Volume: sdk.MustNewDecFromStr("98444.123455"), - TimeStamp: provider.PastUnixTime(5 * time.Minute), - }, - }, - }, - provider.ProviderKraken: { - "ATOM": []types.CandlePrice{ - { - Price: sdk.MustNewDecFromStr("28.268700"), - Volume: sdk.MustNewDecFromStr("178277.53314385"), - TimeStamp: provider.PastUnixTime(5 * time.Minute), - }, - }, - "UMEE": []types.CandlePrice{ - { - Price: sdk.MustNewDecFromStr("1.13000000"), - Volume: sdk.MustNewDecFromStr("178277.53314385"), - TimeStamp: provider.PastUnixTime(5 * time.Minute), - }, - }, - "LUNA": []types.CandlePrice{ - { - Price: sdk.MustNewDecFromStr("64.87853000"), - Volume: sdk.MustNewDecFromStr("458917.46353577"), - TimeStamp: provider.PastUnixTime(5 * time.Minute), - }, - }, - }, - "FOO": { - "ATOM": []types.CandlePrice{ - { - Price: sdk.MustNewDecFromStr("28.168700"), - Volume: sdk.MustNewDecFromStr("4749102.53314385"), - TimeStamp: provider.PastUnixTime(5 * time.Minute), - }, - }, - }, - }, - expected: map[string]sdk.Dec{}, - }, - } - - for name, tc := range testCases { - tc := tc - - t.Run(name, func(t *testing.T) { - vwap, err := oracle.ComputeTVWAP(tc.candles) - require.NoError(t, err) - require.Len(t, vwap, len(tc.expected)) - - for k, v := range tc.expected { - require.Equalf(t, v, vwap[k], "unexpected VWAP for %s", k) - } - }) - } -} - -func TestStandardDeviation(t *testing.T) { - type deviation struct { - mean sdk.Dec - deviation sdk.Dec - } - testCases := map[string]struct { - prices map[provider.Name]map[string]sdk.Dec - expected map[string]deviation - }{ - "empty prices": { - prices: make(map[provider.Name]map[string]sdk.Dec), - expected: map[string]deviation{}, - }, - "nil prices": { - prices: nil, - expected: map[string]deviation{}, - }, - "not enough prices": { - prices: map[provider.Name]map[string]sdk.Dec{ - provider.ProviderBinance: { - "ATOM": sdk.MustNewDecFromStr("28.21000000"), - "UMEE": sdk.MustNewDecFromStr("1.13000000"), - "LUNA": sdk.MustNewDecFromStr("64.87000000"), - }, - provider.ProviderKraken: { - "ATOM": sdk.MustNewDecFromStr("28.23000000"), - "UMEE": sdk.MustNewDecFromStr("1.13050000"), - "LUNA": sdk.MustNewDecFromStr("64.85000000"), - }, - }, - expected: map[string]deviation{}, - }, - "some prices": { - prices: map[provider.Name]map[string]sdk.Dec{ - provider.ProviderBinance: { - "ATOM": sdk.MustNewDecFromStr("28.21000000"), - "UMEE": sdk.MustNewDecFromStr("1.13000000"), - "LUNA": sdk.MustNewDecFromStr("64.87000000"), - }, - provider.ProviderKraken: { - "ATOM": sdk.MustNewDecFromStr("28.23000000"), - "UMEE": sdk.MustNewDecFromStr("1.13050000"), - }, - provider.ProviderOsmosis: { - "ATOM": sdk.MustNewDecFromStr("28.40000000"), - "UMEE": sdk.MustNewDecFromStr("1.14000000"), - "LUNA": sdk.MustNewDecFromStr("64.10000000"), - }, - }, - expected: map[string]deviation{ - "ATOM": { - mean: sdk.MustNewDecFromStr("28.28"), - deviation: sdk.MustNewDecFromStr("0.085244745683629475"), - }, - "UMEE": { - mean: sdk.MustNewDecFromStr("1.1335"), - deviation: sdk.MustNewDecFromStr("0.004600724580614015"), - }, - }, - }, - - "non empty prices": { - prices: map[provider.Name]map[string]sdk.Dec{ - provider.ProviderBinance: { - "ATOM": sdk.MustNewDecFromStr("28.21000000"), - "UMEE": sdk.MustNewDecFromStr("1.13000000"), - "LUNA": sdk.MustNewDecFromStr("64.87000000"), - }, - provider.ProviderKraken: { - "ATOM": sdk.MustNewDecFromStr("28.23000000"), - "UMEE": sdk.MustNewDecFromStr("1.13050000"), - "LUNA": sdk.MustNewDecFromStr("64.85000000"), - }, - provider.ProviderOsmosis: { - "ATOM": sdk.MustNewDecFromStr("28.40000000"), - "UMEE": sdk.MustNewDecFromStr("1.14000000"), - "LUNA": sdk.MustNewDecFromStr("64.10000000"), - }, - }, - expected: map[string]deviation{ - "ATOM": { - mean: sdk.MustNewDecFromStr("28.28"), - deviation: sdk.MustNewDecFromStr("0.085244745683629475"), - }, - "UMEE": { - mean: sdk.MustNewDecFromStr("1.1335"), - deviation: sdk.MustNewDecFromStr("0.004600724580614015"), - }, - "LUNA": { - mean: sdk.MustNewDecFromStr("64.606666666666666666"), - deviation: sdk.MustNewDecFromStr("0.358360464089193609"), - }, - }, - }, - } - - for name, tc := range testCases { - tc := tc - - t.Run(name, func(t *testing.T) { - deviation, mean, err := oracle.StandardDeviation(tc.prices) - require.NoError(t, err) - require.Len(t, deviation, len(tc.expected)) - require.Len(t, mean, len(tc.expected)) - - for k, v := range tc.expected { - require.Equalf(t, v.deviation, deviation[k], "unexpected deviation for %s", k) - require.Equalf(t, v.mean, mean[k], "unexpected mean for %s", k) - } - }) - } -} diff --git a/price-feeder/pkg/httputil/http.go b/price-feeder/pkg/httputil/http.go deleted file mode 100644 index 271817d644..0000000000 --- a/price-feeder/pkg/httputil/http.go +++ /dev/null @@ -1,32 +0,0 @@ -package httputil - -import ( - "encoding/json" - "net/http" -) - -// Common HTTP methods and header values -const ( - MethodGET = "GET" -) - -// ErrResponse defines an HTTP error response. -type ErrResponse struct { - Error string `json:"error"` -} - -// RespondWithError provides an auxiliary function to handle all failed HTTP -// requests. -func RespondWithError(w http.ResponseWriter, code int, err error) { - RespondWithJSON(w, code, ErrResponse{err.Error()}) -} - -// RespondWithJSON provides an auxiliary function to return an HTTP response -// with JSON content and an HTTP status code. -func RespondWithJSON(w http.ResponseWriter, code int, payload interface{}) { - response, _ := json.Marshal(payload) - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(code) - _, _ = w.Write(response) -} diff --git a/price-feeder/pkg/sync/sync.go b/price-feeder/pkg/sync/sync.go deleted file mode 100644 index 8871b3936f..0000000000 --- a/price-feeder/pkg/sync/sync.go +++ /dev/null @@ -1,33 +0,0 @@ -package sync - -import ( - "sync" -) - -// Closer implements a primitive to close a channel that signals process -// termination while allowing a caller to call Close multiple times safely. It -// should be used in cases where guarantees cannot be made about when and how -// many times closure is executed. -type Closer struct { - closeOnce sync.Once - doneCh chan struct{} -} - -// NewCloser returns a reference to a new Closer. -func NewCloser() *Closer { - return &Closer{doneCh: make(chan struct{})} -} - -// Done returns the internal done channel allowing the caller either block or wait -// for the Closer to be terminated/closed. -func (c *Closer) Done() <-chan struct{} { - return c.doneCh -} - -// Close gracefully closes the Closer. A caller should only call Close once, but -// it is safe to call it successive times. -func (c *Closer) Close() { - c.closeOnce.Do(func() { - close(c.doneCh) - }) -} diff --git a/price-feeder/price-feeder.example.toml b/price-feeder/price-feeder.example.toml deleted file mode 100644 index 6d1fa56d83..0000000000 --- a/price-feeder/price-feeder.example.toml +++ /dev/null @@ -1,308 +0,0 @@ -gas_adjustment = 1 - -[server] -listen_addr = "0.0.0.0:7171" -read_timeout = "20s" -verbose_cors = true -write_timeout = "20s" - -[[deviation_thresholds]] -base = "USDT" -threshold = "1.5" - -[[deviation_thresholds]] -base = "UMEE" -threshold = "1.5" - -[[deviation_thresholds]] -base = "ATOM" -threshold = "1.5" - -[[deviation_thresholds]] -base = "USDC" -threshold = "1.5" - -[[deviation_thresholds]] -base = "CRO" -threshold = "1.5" - -[[deviation_thresholds]] -base = "DAI" -threshold = "2" - -[[deviation_thresholds]] -base = "ETH" -threshold = "2" - -[[deviation_thresholds]] -base = "WBTC" -threshold = "1.5" - -[[deviation_thresholds]] -base = "BNB" -threshold = "2" - -[[deviation_thresholds]] -base = "JUNO" -threshold = "2" - -[[deviation_thresholds]] -base = "OSMO" -threshold = "2" - -[[deviation_thresholds]] -base = "stATOM" -threshold = "2" - -[[deviation_thresholds]] -base = "stOSMO" -threshold = "2" - -[[deviation_thresholds]] -base = "IST" -threshold = "2" - -[[deviation_thresholds]] -base = "WAXL" -threshold = "1.5" - -[[deviation_thresholds]] -base = "MATIC" -threshold = "1.5" - -[[deviation_thresholds]] -base = "DOT" -threshold = "1.5" - -[[currency_pairs]] -base = "UMEE" -providers = [ - "okx", - "gate", -] -quote = "USDT" - -[[currency_pairs]] -base = "UMEE" -providers = [ - "osmosisv2", -] -quote = "ATOM" - -[[currency_pairs]] -base = "USDT" -providers = [ - "kraken", - "coinbase", - "binanceus", -] -quote = "USD" - -[[currency_pairs]] -base = "ATOM" -providers = [ - "okx", - "bitget", -] -quote = "USDT" - -[[currency_pairs]] -base = "ATOM" -providers = [ - "kraken", - "binanceus", -] -quote = "USD" - -[[currency_pairs]] -base = "USDC" -providers = [ - "okx", - "bitget", - "kraken", -] -quote = "USDT" - -[[currency_pairs]] -base = "DAI" -providers = [ - "okx", - "bitget", - "huobi", -] -quote = "USDT" - -[[currency_pairs]] -base = "DAI" -providers = [ - "kraken", -] -quote = "USD" - -[[currency_pairs]] -base = "ETH" -providers = [ - "okx", - "bitget", -] -quote = "USDT" - -[[currency_pairs]] -base = "ETH" -providers = [ - "kraken", -] -quote = "USD" - -[[currency_pairs]] -base = "WBTC" -providers = [ - "okx", - "bitget", - "crypto", -] -quote = "USDT" - -[[currency_pairs]] -base = "CRO" -providers = [ - "crypto", - "bitget", - "okx", -] -quote = "USDT" - -[[currency_pairs]] -base = "BNB" -providers = [ - "binanceus", - "bitget", - "okx", -] -quote = "USDT" - -[[currency_pairs]] -base = "OSMO" -providers = [ - "osmosisv2", -] -quote = "ATOM" - -[[currency_pairs]] -base = "OSMO" -providers = [ - "bitget", - "gate", -] -quote = "USDT" - -[[currency_pairs]] -base = "OSMO" -providers = [ - "crypto", -] -quote = "USD" - -[[currency_pairs]] -base = "stATOM" -providers = [ - "osmosisv2", -] -quote = "ATOM" - -[[currency_pairs]] -base = "stOSMO" -providers = [ - "osmosisv2", -] -quote = "OSMO" - -[[currency_pairs]] -base = "IST" -providers = [ - "osmosisv2", -] -quote = "OSMO" - -[[currency_pairs]] -base = "WAXL" -providers = [ - "kraken", -] -quote = "USD" - -[[currency_pairs]] -base = "WAXL" -providers = [ - "huobi", - "bitget", - "gate", -] -quote = "USDT" - -[[currency_pairs]] -base = "DOT" -providers = [ - "kraken", - "coinbase", - "crypto", -] -quote = "USD" - -[[currency_pairs]] -base = "DOT" -providers = [ - "gate", - "bitget", -] -quote = "USDT" - -[[currency_pairs]] -base = "MATIC" -providers = [ - "coinbase", - "kraken", -] -quote = "USD" - -[[currency_pairs]] -base = "MATIC" -providers = [ - "gate", - "mexc", - "bitget", -] -quote = "USDT" - -[account] -address = "umee15nejfgcaanqpw25ru4arvfd0fwy6j8clccvwx4" -chain_id = "umee-local-testnet" -validator = "umeevaloper12tysz6mzrawenca2t3t7ltym4hfjj8a5upsn2k" - -[keyring] -backend = "test" -dir = "/Users/username/.umee" - -[rpc] -grpc_endpoint = "localhost:9090" -rpc_timeout = "100ms" -tmrpc_endpoint = "http://localhost:26657" - -[telemetry] -enable-hostname = true -enable-hostname-label = true -enable-service-label = true -enabled = true -global-labels = [["chain_id", "umee-local-testnet"]] -service-name = "price-feeder" -prometheus-retention-time = 100 - -[[provider_endpoints]] -name = "binance" -rest = "https://api1.binance.com" -websocket = "stream.binance.com:9443" - -[[provider_endpoints]] -name = "osmosisv2" -rest = "https://api.osmo-api.prod.network.umee.cc" -websocket = "api.osmo-api.prod.network.umee.cc" diff --git a/price-feeder/router/middleware/middleware.go b/price-feeder/router/middleware/middleware.go deleted file mode 100644 index a0efb48eef..0000000000 --- a/price-feeder/router/middleware/middleware.go +++ /dev/null @@ -1,72 +0,0 @@ -package middleware - -import ( - "net/http" - "time" - - "github.com/justinas/alice" - "github.com/rs/cors" - "github.com/rs/zerolog" - "github.com/rs/zerolog/hlog" - - "github.com/umee-network/umee/price-feeder/v2/config" -) - -func Build(logger zerolog.Logger, cfg config.Config) alice.Chain { - mChain := alice.New() - mChain = AddRequestLoggingMiddleware(mChain, logger) - mChain = AddCORSMiddleware(mChain, logger, cfg) - - return mChain -} - -// AddRequestLoggingMiddleware appends HTTP logging middleware to a provided -// middleware chain. -func AddRequestLoggingMiddleware(mChain alice.Chain, logger zerolog.Logger) alice.Chain { - mChain = mChain.Append(hlog.NewHandler(logger)) - mChain = mChain.Append(hlog.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) { - hlog.FromRequest(r).Info(). - Str("method", r.Method). - Str("url", r.URL.String()). - Int("status", status). - Int("size", size). - Dur("duration", duration). - Msg("") - })) - mChain = mChain.Append(hlog.RequestHandler("req")) - mChain = mChain.Append(hlog.RemoteAddrHandler("ip")) - mChain = mChain.Append(hlog.UserAgentHandler("ua")) - mChain = mChain.Append(hlog.RefererHandler("ref")) - mChain = mChain.Append(hlog.RequestIDHandler("req_id", "Request-Id")) - - return mChain -} - -// AddCORSMiddleware appends CORS middleware to a provided middleware chain. -func AddCORSMiddleware(mChain alice.Chain, logger zerolog.Logger, cfg config.Config) alice.Chain { - opts := cors.Options{ - AllowedMethods: []string{ - http.MethodGet, - http.MethodOptions, - }, - AllowCredentials: true, - AllowedHeaders: []string{ - "Content-Type", - "Access-Control-Allow-Headers", - "Authorization", - "X-Requested-With", - }, - AllowedOrigins: cfg.Server.AllowedOrigins, - } - - if cfg.Server.VerboseCORS { - opts.Debug = true - } - - c := cors.New(opts) - c.Log = &logger - - mChain = mChain.Append(c.Handler) - - return mChain -} diff --git a/price-feeder/router/v1/metrics.go b/price-feeder/router/v1/metrics.go deleted file mode 100644 index fce432f850..0000000000 --- a/price-feeder/router/v1/metrics.go +++ /dev/null @@ -1,7 +0,0 @@ -package v1 - -import "github.com/cosmos/cosmos-sdk/telemetry" - -type Metrics interface { - Gather(format string) (telemetry.GatherResponse, error) -} diff --git a/price-feeder/router/v1/oracle.go b/price-feeder/router/v1/oracle.go deleted file mode 100644 index 0a30364f2e..0000000000 --- a/price-feeder/router/v1/oracle.go +++ /dev/null @@ -1,16 +0,0 @@ -package v1 - -import ( - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/umee-network/umee/price-feeder/v2/oracle" -) - -// Oracle defines the Oracle interface contract that the v1 router depends on. -type Oracle interface { - GetLastPriceSyncTimestamp() time.Time - GetPrices() map[string]sdk.Dec - GetTvwapPrices() oracle.PricesByProvider - GetVwapPrices() oracle.PricesByProvider -} diff --git a/price-feeder/router/v1/response.go b/price-feeder/router/v1/response.go deleted file mode 100644 index b346534e5b..0000000000 --- a/price-feeder/router/v1/response.go +++ /dev/null @@ -1,58 +0,0 @@ -package v1 - -import ( - "encoding/json" - "fmt" - "net/http" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/umee-network/umee/price-feeder/v2/oracle/provider" -) - -// Response constants -const ( - StatusAvailable = "available" -) - -type ( - // HealthZResponse defines the response type for the healthy API handler. - HealthZResponse struct { - Status string `json:"status" yaml:"status"` - Oracle struct { - LastSync string `json:"last_sync"` - } `json:"oracle"` - } - - // PricesResponse defines the response type for getting the latest exchange - // rates from the oracle. - PricesResponse struct { - Prices map[string]sdk.Dec `json:"prices"` - } - - PricesPerProviderResponse struct { - Prices map[provider.Name]map[string]sdk.Dec `json:"providers"` - } -) - -// errorResponse defines the attributes of a JSON error response. -type errorResponse struct { - Code int `json:"code,omitempty"` - Error string `json:"error"` -} - -// newErrorResponse creates a new errorResponse instance. -func newErrorResponse(code int, err string) errorResponse { - return errorResponse{Code: code, Error: err} -} - -// writeErrorResponse prepares and writes a HTTP error -// given a status code and an error message. -func writeErrorResponse(w http.ResponseWriter, status int, err string) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(status) - if bz, err := json.Marshal(newErrorResponse(0, err)); err == nil { - _, _ = w.Write(bz) - } else { - _, _ = w.Write([]byte(fmt.Sprintf("failed to marshal error response: %s", err))) - } -} diff --git a/price-feeder/router/v1/router.go b/price-feeder/router/v1/router.go deleted file mode 100644 index 8bbaf4ad57..0000000000 --- a/price-feeder/router/v1/router.go +++ /dev/null @@ -1,151 +0,0 @@ -package v1 - -import ( - "fmt" - "net/http" - "strings" - "text/template" - "time" - - "github.com/gorilla/mux" - "github.com/rs/zerolog" - - "github.com/umee-network/umee/price-feeder/v2/config" - "github.com/umee-network/umee/price-feeder/v2/pkg/httputil" - "github.com/umee-network/umee/price-feeder/v2/router/middleware" -) - -const ( - APIPathPrefix = "/api/v1" -) - -// Router defines a router wrapper used for registering v1 API routes. -type Router struct { - logger zerolog.Logger - cfg config.Config - oracle Oracle - metrics Metrics -} - -func New(logger zerolog.Logger, cfg config.Config, oracle Oracle, metrics Metrics) *Router { - return &Router{ - logger: logger.With().Str("module", "router").Logger(), - cfg: cfg, - oracle: oracle, - metrics: metrics, - } -} - -// RegisterRoutes register v1 API routes on the provided sub-router. -func (r *Router) RegisterRoutes(rtr *mux.Router, prefix string) { - v1Router := rtr.PathPrefix(prefix).Subrouter() - - // build middleware chain - mChain := middleware.Build(r.logger, r.cfg) - - // handle all preflight request - v1Router.Methods("OPTIONS").HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - for _, origin := range r.cfg.Server.AllowedOrigins { - if origin == req.Header.Get("Origin") { - w.Header().Set("Access-Control-Allow-Origin", origin) - } - } - - w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") - w.Header().Set( - "Access-Control-Allow-Headers", - "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With", - ) - w.Header().Set("Access-Control-Allow-Credentials", "true") - w.WriteHeader(http.StatusOK) - }) - - v1Router.Handle( - "/healthz", - mChain.ThenFunc(r.healthzHandler()), - ).Methods(httputil.MethodGET) - - v1Router.Handle( - "/prices", - mChain.ThenFunc(r.pricesHandler()), - ).Methods(httputil.MethodGET) - - v1Router.Handle( - "/prices/providers/tvwap", - mChain.ThenFunc(r.candlePricesHandler()), - ).Methods(httputil.MethodGET) - - v1Router.Handle( - "/prices/providers/vwap", - mChain.ThenFunc(r.tickerPricesHandler()), - ).Methods(httputil.MethodGET) - - if r.cfg.Telemetry.Enabled { - v1Router.Handle( - "/metrics", - mChain.ThenFunc(r.metricsHandler()), - ).Methods(httputil.MethodGET) - } -} - -func (r *Router) healthzHandler() http.HandlerFunc { - return func(w http.ResponseWriter, req *http.Request) { - resp := HealthZResponse{ - Status: StatusAvailable, - } - - resp.Oracle.LastSync = r.oracle.GetLastPriceSyncTimestamp().Format(time.RFC3339) - - httputil.RespondWithJSON(w, http.StatusOK, resp) - } -} - -func (r *Router) pricesHandler() http.HandlerFunc { - return func(w http.ResponseWriter, req *http.Request) { - resp := PricesResponse{ - Prices: r.oracle.GetPrices(), - } - - httputil.RespondWithJSON(w, http.StatusOK, resp) - } -} - -func (r *Router) candlePricesHandler() http.HandlerFunc { - return func(w http.ResponseWriter, req *http.Request) { - resp := PricesPerProviderResponse{ - Prices: r.oracle.GetTvwapPrices(), - } - - httputil.RespondWithJSON(w, http.StatusOK, resp) - } -} - -func (r *Router) tickerPricesHandler() http.HandlerFunc { - return func(w http.ResponseWriter, req *http.Request) { - resp := PricesPerProviderResponse{ - Prices: r.oracle.GetVwapPrices(), - } - - httputil.RespondWithJSON(w, http.StatusOK, resp) - } -} - -func (r *Router) metricsHandler() http.HandlerFunc { - return func(w http.ResponseWriter, req *http.Request) { - format := strings.TrimSpace(req.FormValue("format")) - - gr, err := r.metrics.Gather(format) - if err != nil { - writeErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to gather metrics: %s", err)) - return - } - - w.Header().Set("Content-Type", gr.ContentType) - if t, err := template.New("metrics").Parse(string(gr.Metrics)); err == nil { - // unchecked err, too late for bad response - _ = t.ExecuteTemplate(w, "metrics", nil) - } else { - writeErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to parse metrics: %s", err)) - } - } -} diff --git a/price-feeder/router/v1/router_test.go b/price-feeder/router/v1/router_test.go deleted file mode 100644 index 2a74a7a67e..0000000000 --- a/price-feeder/router/v1/router_test.go +++ /dev/null @@ -1,153 +0,0 @@ -package v1_test - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/gorilla/mux" - "github.com/rs/zerolog" - "github.com/stretchr/testify/suite" - - "github.com/cosmos/cosmos-sdk/telemetry" - "github.com/umee-network/umee/price-feeder/v2/config" - "github.com/umee-network/umee/price-feeder/v2/oracle" - "github.com/umee-network/umee/price-feeder/v2/oracle/provider" - v1 "github.com/umee-network/umee/price-feeder/v2/router/v1" -) - -var ( - _ v1.Oracle = (*mockOracle)(nil) - - mockPrices = map[string]sdk.Dec{ - "ATOM": sdk.MustNewDecFromStr("34.84"), - "UMEE": sdk.MustNewDecFromStr("4.21"), - } - - mockComputedPrices = map[provider.Name]map[string]sdk.Dec{ - provider.ProviderBinance: { - "ATOM": sdk.MustNewDecFromStr("28.21000000"), - "UMEE": sdk.MustNewDecFromStr("1.13000000"), - }, - provider.ProviderKraken: { - "ATOM": sdk.MustNewDecFromStr("28.268700"), - "UMEE": sdk.MustNewDecFromStr("1.13000000"), - }, - } -) - -type mockOracle struct{} - -func (m mockOracle) GetLastPriceSyncTimestamp() time.Time { - return time.Now() -} - -func (m mockOracle) GetPrices() map[string]sdk.Dec { - return mockPrices -} - -func (m mockOracle) GetTvwapPrices() oracle.PricesByProvider { - return mockComputedPrices -} - -func (m mockOracle) GetVwapPrices() oracle.PricesByProvider { - return mockComputedPrices -} - -type mockMetrics struct{} - -func (mockMetrics) Gather(format string) (telemetry.GatherResponse, error) { - return telemetry.GatherResponse{}, nil -} - -type RouterTestSuite struct { - suite.Suite - - mux *mux.Router - router *v1.Router -} - -// SetupSuite executes once before the suite's tests are executed. -func (rts *RouterTestSuite) SetupSuite() { - mux := mux.NewRouter() - cfg := config.Config{ - Server: config.Server{ - AllowedOrigins: []string{}, - VerboseCORS: false, - }, - } - - r := v1.New(zerolog.Nop(), cfg, mockOracle{}, mockMetrics{}) - r.RegisterRoutes(mux, v1.APIPathPrefix) - - rts.mux = mux - rts.router = r -} - -func TestServiceTestSuite(t *testing.T) { - suite.Run(t, new(RouterTestSuite)) -} - -func (rts *RouterTestSuite) executeRequest(req *http.Request) *httptest.ResponseRecorder { - rr := httptest.NewRecorder() - rts.mux.ServeHTTP(rr, req) - - return rr -} - -func (rts *RouterTestSuite) TestHealthz() { - req, err := http.NewRequest("GET", "/api/v1/healthz", nil) - rts.Require().NoError(err) - - response := rts.executeRequest(req) - rts.Require().Equal(http.StatusOK, response.Code) - - var respBody map[string]interface{} - rts.Require().NoError(json.Unmarshal(response.Body.Bytes(), &respBody)) - rts.Require().Equal(respBody["status"], v1.StatusAvailable) -} - -func (rts *RouterTestSuite) TestPrices() { - req, err := http.NewRequest("GET", "/api/v1/prices", nil) - rts.Require().NoError(err) - - response := rts.executeRequest(req) - rts.Require().Equal(http.StatusOK, response.Code) - - var respBody v1.PricesResponse - rts.Require().NoError(json.Unmarshal(response.Body.Bytes(), &respBody)) - rts.Require().Equal(respBody.Prices["ATOM"], mockPrices["ATOM"]) - rts.Require().Equal(respBody.Prices["UMEE"], mockPrices["UMEE"]) - rts.Require().Equal(respBody.Prices["FOO"], sdk.Dec{}) -} - -func (rts *RouterTestSuite) TestTvwap() { - req, err := http.NewRequest("GET", "/api/v1/prices/providers/tvwap", nil) - rts.Require().NoError(err) - response := rts.executeRequest(req) - rts.Require().Equal(http.StatusOK, response.Code) - - var respBody v1.PricesPerProviderResponse - rts.Require().NoError(json.Unmarshal(response.Body.Bytes(), &respBody)) - rts.Require().Equal( - respBody.Prices[provider.ProviderBinance]["ATOM"], - mockComputedPrices[provider.ProviderBinance]["ATOM"], - ) -} - -func (rts *RouterTestSuite) TestVwap() { - req, err := http.NewRequest("GET", "/api/v1/prices/providers/vwap", nil) - rts.Require().NoError(err) - response := rts.executeRequest(req) - rts.Require().Equal(http.StatusOK, response.Code) - - var respBody v1.PricesPerProviderResponse - rts.Require().NoError(json.Unmarshal(response.Body.Bytes(), &respBody)) - rts.Require().Equal( - respBody.Prices[provider.ProviderBinance]["ATOM"], - mockComputedPrices[provider.ProviderBinance]["ATOM"], - ) -} diff --git a/price-feeder/tests/integration/provider_test.go b/price-feeder/tests/integration/provider_test.go deleted file mode 100644 index b28bce3877..0000000000 --- a/price-feeder/tests/integration/provider_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package integration - -import ( - "context" - "os" - "testing" - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" - "github.com/umee-network/umee/price-feeder/v2/config" - "github.com/umee-network/umee/price-feeder/v2/oracle" - "github.com/umee-network/umee/price-feeder/v2/oracle/provider" - "github.com/umee-network/umee/price-feeder/v2/oracle/types" -) - -type IntegrationTestSuite struct { - suite.Suite - - logger zerolog.Logger -} - -func (s *IntegrationTestSuite) SetupSuite() { - s.logger = getLogger() -} - -func TestServiceTestSuite(t *testing.T) { - suite.Run(t, new(IntegrationTestSuite)) -} - -func (s *IntegrationTestSuite) TestWebsocketProviders() { - if testing.Short() { - s.T().Skip("skipping integration test in short mode") - } - - cfg, err := config.ParseConfig("../../price-feeder.example.toml") - require.NoError(s.T(), err) - - endpoints := cfg.ProviderEndpointsMap() - - for key, pairs := range cfg.ProviderPairs() { - providerName := key - currencyPairs := pairs - endpoint := endpoints[providerName] - s.T().Run(string(providerName), func(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) - pvd, _ := oracle.NewProvider(ctx, providerName, getLogger(), endpoint, currencyPairs...) - time.Sleep(60 * time.Second) // wait for provider to connect and receive some prices - checkForPrices(t, pvd, currencyPairs, providerName.String()) - cancel() - }) - } -} - -func (s *IntegrationTestSuite) TestSubscribeCurrencyPairs() { - if testing.Short() { - s.T().Skip("skipping integration test in short mode") - } - - ctx, cancel := context.WithCancel(context.Background()) - currencyPairs := []types.CurrencyPair{{Base: "ATOM", Quote: "USDT"}} - pvd, _ := provider.NewOkxProvider(ctx, getLogger(), provider.Endpoint{}, currencyPairs...) - - time.Sleep(5 * time.Second) - - newPairs := []types.CurrencyPair{{Base: "ETH", Quote: "USDT"}} - pvd.SubscribeCurrencyPairs(newPairs...) - currencyPairs = append(currencyPairs, newPairs...) - - time.Sleep(25 * time.Second) // wait for provider to connect and receive some prices - - checkForPrices(s.T(), pvd, currencyPairs, "OKX") - - cancel() -} - -func checkForPrices(t *testing.T, pvd provider.Provider, currencyPairs []types.CurrencyPair, providerName string) { - tickerPrices, err := pvd.GetTickerPrices(currencyPairs...) - require.NoError(t, err) - - candlePrices, err := pvd.GetCandlePrices(currencyPairs...) - require.NoError(t, err) - - for _, cp := range currencyPairs { - currencyPairKey := cp.String() - - require.False(t, - tickerPrices[currencyPairKey].Price.IsNil(), - "no ticker price for %s pair %s", - providerName, - currencyPairKey, - ) - - require.True(t, - tickerPrices[currencyPairKey].Price.GT(sdk.NewDec(0)), - "ticker price is zero for %s pair %s", - providerName, - currencyPairKey, - ) - - require.NotEmpty(t, - candlePrices[currencyPairKey], - "no candle prices for %s pair %s", - providerName, - currencyPairKey, - ) - - require.True(t, - candlePrices[currencyPairKey][0].Price.GT(sdk.NewDec(0)), - "candle price iss zero for %s pair %s", - providerName, - currencyPairKey, - ) - } -} - -func getLogger() zerolog.Logger { - logWriter := zerolog.ConsoleWriter{Out: os.Stderr} - logLvl := zerolog.DebugLevel - zerolog.SetGlobalLevel(logLvl) - return zerolog.New(logWriter).Level(logLvl).With().Timestamp().Logger() -} diff --git a/price-feeder/tools/tools.go b/price-feeder/tools/tools.go deleted file mode 100644 index ae8599252c..0000000000 --- a/price-feeder/tools/tools.go +++ /dev/null @@ -1,13 +0,0 @@ -//go:build tools -// +build tools - -// This file uses the recommended method for tracking developer tools in a Go -// module. -// -// REF: https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module -package tools - -import ( - _ "github.com/golangci/golangci-lint/cmd/golangci-lint" - _ "github.com/mgechev/revive" -) diff --git a/tests/e2e/e2e_setup_test.go b/tests/e2e/e2e_setup_test.go index 7c84749915..43db40fb5f 100644 --- a/tests/e2e/e2e_setup_test.go +++ b/tests/e2e/e2e_setup_test.go @@ -50,6 +50,10 @@ const ( ethChainID uint = 15 ethMinerPK = "0xb1bab011e03a9862664706fc3bbaa1b16651528e5f0e7fbfcbfdd8be302a13e7" + + priceFeederContainerRepo = "ghcr.io/umee-network/price-feeder-umee" + priceFeederServerPort = "7171/tcp" + priceFeederMaxStartupTime = 20 // seconds ) var ( @@ -975,88 +979,98 @@ func (s *IntegrationTestSuite) runOrchestrators() { func (s *IntegrationTestSuite) runPriceFeeder() { s.T().Log("starting price-feeder container...") - tmpDir, err := os.MkdirTemp("", "umee-e2e-testnet-price-feeder-") - s.Require().NoError(err) - s.tmpDirs = append(s.tmpDirs, tmpDir) - - priceFeederCfgPath := path.Join(tmpDir, "price-feeder") - - s.Require().NoError(os.MkdirAll(priceFeederCfgPath, 0o755)) - _, err = copyFile( - filepath.Join("./scripts/", "price_feeder_bootstrap.sh"), - filepath.Join(priceFeederCfgPath, "price_feeder_bootstrap.sh"), - ) - s.Require().NoError(err) - umeeVal := s.chain.validators[2] umeeValAddr, err := umeeVal.keyInfo.GetAddress() s.Require().NoError(err) + grpcEndpoint := fmt.Sprintf("tcp://%s:%s", umeeVal.instanceName(), "9090") + tmrpcEndpoint := fmt.Sprintf("http://%s:%s", umeeVal.instanceName(), "26657") + s.priceFeederResource, err = s.dkrPool.RunWithOptions( &dockertest.RunOptions{ Name: "umee-price-feeder", NetworkID: s.dkrNet.Network.ID, - Repository: "umee-network/umeed-e2e", + Repository: priceFeederContainerRepo, Mounts: []string{ - fmt.Sprintf("%s/:/root/price-feeder", priceFeederCfgPath), fmt.Sprintf("%s/:/root/.umee", umeeVal.configDir()), }, PortBindings: map[docker.Port][]docker.PortBinding{ - "7171/tcp": {{HostIP: "", HostPort: "7171"}}, + priceFeederServerPort: {{HostIP: "", HostPort: "7171"}}, }, Env: []string{ - "UMEE_E2E_UMEE_VAL_KEY_DIR=/root/.umee", fmt.Sprintf("PRICE_FEEDER_PASS=%s", keyringPassphrase), - fmt.Sprintf("UMEE_E2E_PRICE_FEEDER_ADDRESS=%s", umeeValAddr), - fmt.Sprintf("UMEE_E2E_PRICE_FEEDER_VALIDATOR=%s", sdk.ValAddress(umeeValAddr)), - fmt.Sprintf("UMEE_E2E_UMEE_VAL_HOST=%s", s.valResources[0].Container.Name[1:]), - fmt.Sprintf("UMEE_E2E_CHAIN_ID=%s", s.chain.id), + fmt.Sprintf("ACCOUNT_ADDRESS=%s", umeeValAddr), + fmt.Sprintf("ACCOUNT_VALIDATOR=%s", sdk.ValAddress(umeeValAddr)), + fmt.Sprintf("KEYRING_DIR=%s", "/root/.umee"), + fmt.Sprintf("ACCOUNT_CHAIN_ID=%s", s.chain.id), + fmt.Sprintf("RPC_GRPC_ENDPOINT=%s", grpcEndpoint), + fmt.Sprintf("RPC_TMRPC_ENDPOINT=%s", tmrpcEndpoint), }, - Entrypoint: []string{ - "sh", - "-c", - "chmod +x /root/price-feeder/price_feeder_bootstrap.sh && sh /root/price-feeder/price_feeder_bootstrap.sh", + Cmd: []string{ + "--skip-provider-check", + "--log-level=debug", }, }, noRestart, ) s.Require().NoError(err) - endpoint := fmt.Sprintf("http://%s/api/v1/prices", s.priceFeederResource.GetHostPort("7171/tcp")) - s.Require().Eventually( - func() bool { - resp, err := http.Get(endpoint) - if err != nil { - s.T().Log("Price feeder endpoint not available", err) - return false - } + endpoint := fmt.Sprintf("http://%s/api/v1/prices", s.priceFeederResource.GetHostPort(priceFeederServerPort)) - defer resp.Body.Close() + checkHealth := func() bool { + resp, err := http.Get(endpoint) + if err != nil { + s.T().Log("Price feeder endpoint not available", err) + return false + } - bz, err := io.ReadAll(resp.Body) - if err != nil { - s.T().Log("Can't get price feeder response", err) - return false - } + defer resp.Body.Close() - var respBody map[string]interface{} - if err := json.Unmarshal(bz, &respBody); err != nil { - s.T().Log("Can't unmarshal price feed", err) - return false - } + bz, err := io.ReadAll(resp.Body) + if err != nil { + s.T().Log("Can't get price feeder response", err) + return false + } - prices, ok := respBody["prices"].(map[string]interface{}) - if !ok { - s.T().Log("price feeder: no prices") - return false - } + var respBody map[string]interface{} + if err := json.Unmarshal(bz, &respBody); err != nil { + s.T().Log("Can't unmarshal price feed", err) + return false + } - return len(prices) > 0 - }, - time.Minute, - time.Second, - "price-feeder not healthy", - ) + prices, ok := respBody["prices"].(map[string]interface{}) + if !ok { + s.T().Log("price feeder: no prices") + return false + } + + return len(prices) > 0 + } + + isHealthy := false + for i := 0; i < priceFeederMaxStartupTime; i++ { + isHealthy = checkHealth() + if isHealthy { + break + } + time.Sleep(time.Second) + } + + if !isHealthy { + err := s.dkrPool.Client.Logs(docker.LogsOptions{ + Container: s.priceFeederResource.Container.ID, + OutputStream: os.Stdout, + ErrorStream: os.Stderr, + Stdout: true, + Stderr: true, + Tail: "false", + }) + if err != nil { + s.T().Log("Error retrieving price feeder logs", err) + } + + s.T().Fatal("price-feeder not healthy") + } s.T().Logf("started price-feeder container: %s", s.priceFeederResource.Container.ID) } diff --git a/tests/e2e/scripts/price_feeder_bootstrap.sh b/tests/e2e/scripts/price_feeder_bootstrap.sh deleted file mode 100755 index 6aca2fdc50..0000000000 --- a/tests/e2e/scripts/price_feeder_bootstrap.sh +++ /dev/null @@ -1,159 +0,0 @@ -#!/bin/sh - -set -ex - -# initialize price-feeder configuration -mkdir -p /root/.price-feeder/ -touch /root/.price-feeder/config.toml - -# setup price-feeder configuration -tee /root/.price-feeder/config.toml <