diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 0000000..e44b407 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -0,0 +1,30 @@ +--- +name: Report a bug +about: Something with blob-syncer is not working as expected +title: '' +labels: 'type:bug' +assignees: '' +--- + +#### System information + +Blob syncer version: (if getting from release page) +OS & Version: Windows/Linux/OSX +Commit hash : (if `develop`) + +#### Expected behaviour + + +#### Actual behaviour + + +#### Steps to reproduce the behaviour + + +#### Backtrace + +```` +[backtrace] +```` + +When submitting logs: please submit them as text and not screenshots. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md new file mode 100644 index 0000000..aacd885 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -0,0 +1,17 @@ +--- +name: Request a feature +about: Report a missing feature - e.g. as a step before submitting a PR +title: '' +labels: 'type:feature' +assignees: '' +--- + +# Rationale + +Why should this feature exist? +What are the use-cases? + +# Implementation + +Do you have ideas regarding the implementation of this feature? +Are you willing to implement this feature? \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000..f2f3022 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,9 @@ +--- +name: Ask a question +about: Something is unclear +title: '' +labels: 'type:docs' +assignees: '' +--- + +This should only be used in very rare cases e.g. if you are not 100% sure if something is a bug or asking a question that leads to improving the documentation. For general questions please use [discord](https://discord.gg/bnbchain). diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..d9bdc91 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,17 @@ +### Description + +add a description of your changes here... + +### Rationale + +tell us why we need these changes... + +### Example + +add an example CLI or API response... + +### Changes + +Notable changes: +* add each change in a bullet point here +* ... \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..6c8bea1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,54 @@ +name: Build Test + +on: + push: + branches: + - master + - develop + + pull_request: + branches: + - master + - develop + +jobs: + build-test: + strategy: + matrix: + go-version: [1.20.x] + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + env: + GOPRIVATE: github.com/bnb-chain + GH_ACCESS_TOKEN: ${{ secrets.GH_TOKEN }} + steps: + - name: Install Go + uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go-version }} + + - name: Checkout code + uses: actions/checkout@v3 + + - uses: actions/cache@v3 + with: + # In order: + # * Module download cache + # * Build cache (Linux) + # * Build cache (Mac) + # * Build cache (Windows) + path: | + ~/go/pkg/mod + ~/.cache/go-build + ~/Library/Caches/go-build + %LocalAppData%\go-build + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Setup GitHub Token + run: git config --global url.https://$GH_ACCESS_TOKEN@github.com/.insteadOf https://github.com/ + + - name: Test Build + run: | + make build diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml new file mode 100644 index 0000000..662387a --- /dev/null +++ b/.github/workflows/docker-release.yml @@ -0,0 +1,56 @@ +name: Docker + +on: + push: + # Publish `v1.2.3` tags as releases. + tags: + - v* + +env: + IMAGE_NAME: ghcr.io/${{ github.repository }} + IMAGE_SOURCE: https://github.com/${{ github.repository }} + +jobs: + # Push image to GitHub Packages. + push: + runs-on: ubuntu-latest + if: github.event_name == 'push' + + steps: + - uses: actions/checkout@v3 + + - name: Build image + run: | + docker build . \ + --label "org.opencontainers.image.source=${IMAGE_SOURCE}" \ + --label "org.opencontainers.image.revision=$(git rev-parse HEAD)" \ + --label "org.opencontainers.image.version=$(git describe --tags --abbrev=0)" \ + -f ./server-distroless.dockerfile -t "${IMAGE_NAME}:server-distroless" + + docker build . \ + --label "org.opencontainers.image.source=${IMAGE_SOURCE}" \ + --label "org.opencontainers.image.revision=$(git rev-parse HEAD)" \ + --label "org.opencontainers.image.version=$(git describe --tags --abbrev=0)" \ + -f ./syncer-distroless.dockerfile -t "${IMAGE_NAME}:syncer-distroless" + + - name: Login to GHCR + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push image + run: | + # Strip git ref prefix from version + VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') + # Strip "v" prefix from tag name + [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') + # Use Docker `latest` tag convention + [ "$VERSION" == "master" ] && VERSION=latest + echo IMAGE_NAME=$IMAGE_NAME + echo VERSION=$VERSION + docker tag ${IMAGE_NAME}:server-distroless $IMAGE_NAME:$VERSION-server-distroless + docker tag ${IMAGE_NAME}:syncer-distroless $IMAGE_NAME:$VERSION-syncer-distroless + docker push $IMAGE_NAME:$VERSION-server-distroless + docker push $IMAGE_NAME:$VERSION-syncer-distroless \ No newline at end of file diff --git a/.github/workflows/gosec.yml b/.github/workflows/gosec.yml new file mode 100644 index 0000000..8b82c0b --- /dev/null +++ b/.github/workflows/gosec.yml @@ -0,0 +1,51 @@ +name: gosec + +on: + push: + branches: + - master + - develop + pull_request: + branches: + - master + - develop +jobs: + gosec: + name: gosec + strategy: + matrix: + go-version: [1.20.x] + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + env: + GOPRIVATE: github.com/bnb-chain + GH_ACCESS_TOKEN: ${{ secrets.GH_TOKEN }} + steps: + - uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go-version }} + - uses: actions/checkout@v3 + - name: Setup GitHub Token + run: git config --global url.https://$GH_ACCESS_TOKEN@github.com/.insteadOf https://github.com/ + - uses: actions/cache@v3 + with: + # In order: + # * Module download cache + # * Build cache (Linux) + # * Build cache (Mac) + # * Build cache (Windows) + path: | + ~/go/pkg/mod + ~/.cache/go-build + ~/Library/Caches/go-build + %LocalAppData%\go-build + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - run: | + go mod tidy + go mod download + - name: Run Gosec Security Scanner + uses: securego/gosec@master + with: + args: -quiet -confidence high -severity high ./... \ No newline at end of file diff --git a/.github/workflows/lint-pr.yml b/.github/workflows/lint-pr.yml new file mode 100644 index 0000000..b2dea88 --- /dev/null +++ b/.github/workflows/lint-pr.yml @@ -0,0 +1,30 @@ +name: "Lint PR" + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +jobs: + main: + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + with: + types: | + feat + fix + docs + style + refactor + perf + test + build + ci + chore + revert + release diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..f3a64f8 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,55 @@ +name: Lint +on: + push: + branches: + - master + - develop + pull_request: +permissions: + contents: read + # Optional: allow read access to pull request. Use with `only-new-issues` option. + # pull-requests: read +jobs: + golangci: + name: golangci-lint + strategy: + matrix: + go-version: [1.20.x] + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + env: + GOPRIVATE: github.com/bnb-chain + steps: + - uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go-version }} + - uses: actions/checkout@v3 + + - run: go env -w GOPRIVATE="github.com/bnb-chain/*" + - run: git config --global url."https://${{ secrets.GH_TOKEN }}@github.com".insteadOf "https://github.com" + + - uses: actions/cache@v3 + with: + # In order: + # * Module download cache + # * Build cache (Linux) + # * Build cache (Mac) + # * Build cache (Windows) + path: | + ~/go/pkg/mod + ~/.cache/go-build + ~/Library/Caches/go-build + %LocalAppData%\go-build + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - run: | + go mod tidy + go mod download + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version + version: latest + skip-pkg-cache: true + args: --timeout=99m diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2d2d804 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +.idea/* +build/* +.local +/temp/ +/config/local/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..40625e7 --- /dev/null +++ b/Makefile @@ -0,0 +1,64 @@ +VERSION=$(shell git describe --tags) +GIT_COMMIT=$(shell git rev-parse HEAD) +GIT_COMMIT_DATE=$(shell git log -n1 --pretty='format:%cd' --date=format:'%Y%m%d') +REPO=github.com/bnb-chain/blob-syncer +IMAGE_NAME=ghcr.io/bnb-chain/blob-syncer + +ldflags = -X $(REPO)/version.AppVersion=$(VERSION) \ + -X $(REPO)/version.GitCommit=$(GIT_COMMIT) \ + -X $(REPO)/version.GitCommitDate=$(GIT_COMMIT_DATE) + +build_syncer: +ifeq ($(OS),Windows_NT) + go build -o build/blob-syncer.exe -ldflags="$(ldflags)" cmd/blob-syncer/main.go +else + go build -o build/blob-syncer -ldflags="$(ldflags)" cmd/blob-syncer/main.go +endif + +build_server: +ifeq ($(OS),Windows_NT) + go build -o build/blob-syncer-server.exe -ldflags="$(ldflags)" cmd/blob-syncer-server/main.go +else + go build -o build/blob-syncer-server -ldflags="$(ldflags)" cmd/blob-syncer-server/main.go +endif + +build: + make build_syncer + make build_server + +install: + go install cmd/blob-syncer/main.go + go install cmd/blob-syncer-server/main.go + +build_docker: + docker build . -t ${IMAGE_NAME} + +.PHONY: build install build_docker + + +############################################################################### +### Linting ### +############################################################################### + +golangci_lint_cmd=golangci-lint +golangci_version=v1.51.2 + +lint: + @echo "--> Running linter" + @go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version) + @$(golangci_lint_cmd) run --timeout=10m + +lint-fix: + @echo "--> Running linter" + @go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version) + @$(golangci_lint_cmd) run --fix --out-format=tab --issues-exit-code=0 + +format: + bash scripts/format.sh + +.PHONY: lint lint-fix format + +swagger-gen: + swagger generate server -f ./swagger.yaml -A blob-syncer --default-scheme=http + + diff --git a/README.md b/README.md index 415297c..8d1ad04 100644 --- a/README.md +++ b/README.md @@ -1 +1,286 @@ -# blob-syncer \ No newline at end of file +# Blob Syncer + +## Overview + +The Blob-Syncer service acts as an intermediary between Greenfield and Ethereum, continuously retrieving blobs from Ethereum. +These blobs, collected from a specific Beacon slot range, are consolidated into a bundle and transmitted to the [Bundle Service](https://docs.bnbchain.org/greenfield-docs/docs/api/bundle-service/) +for further processing. Subsequently, the bundle is stored in Greenfield for long-term archival purposes. Additionally, +the Blob-Syncer service offers APIs that enable users to access and retrieve historical blobs as needed. + +## Disclaimer +**The software and related documentation are under active development, all subject to potential future change without +notification and not ready for production use. The code and security audit have not been fully completed and not ready +for any bug bounty. We advise you to be careful and experiment on the network at your own risk. Stay safe out there.** + +## Components +- blob-syncer: This component is specifically engineered to synchronize blobs and securely store them in Greenfield. It includes a post-verification process to ensure the integrity of blob storage. +- blob-syncer-server: This component functions as the API server, catering to users' requests for querying blobs. + +## Requirement + +Go version above 1.20 + +## Prerequisite + +### Create a bucket on Greenfield + +if you don't have a bucket yet, set up one for blob storage. There are a few ways to create one, below shows examples via [greenfield-go-sdk](https://github.com/bnb-chain/greenfield-go-sdk) +and using provided script. + +#### use go-sdk +```go + account, err := types.NewAccountFromPrivateKey("test", privateKey) + if err != nil { + log.Fatalf("New account from private key error, %v", err) + } + cli, err := client.New("greenfield_5600-1", "https://gnfd-testnet-fullnode-tendermint-us.bnbchain.org:443", client.Option{DefaultAccount: account}) + if err != nil { + log.Fatalf("unable to new greenfield client, %v", err) + } + ctx := context.Background() + + // get storage providers list + spLists, err := cli.ListStorageProviders(ctx, true) + if err != nil { + log.Fatalf("fail to list in service sps") + } + // choose the first sp to be the primary SP + primarySP := spLists[0].GetOperatorAddress() + + // create bucket + _, err = cli.CreateBucket(ctx, bucketName, primarySP, types.CreateBucketOptions{}) + handleErr(err, "CreateBucket") + log.Printf("create bucket %s on SP: %s successfully \n", bucketName, spLists[0].Endpoint) + + // head bucket + bucketInfo, err := cli.HeadBucket(ctx, bucketName) + handleErr(err, "HeadBucket") + log.Println("bucket info:", bucketInfo.String()) +``` + +#### use provided script +you can use the script, before runinng it, modify the the scripts/.env file(the GRANTEE_BUNDLE_ACCOUNT does not need to modified at this moment): + +```shell +bash scripts/set_up.sh --create_bucket +``` + +### Get a Bundler Account + +Request a bundle account from the Bundle Service, you need to grant the bundle account permission in next step, so that bundle service +can create object behave of your account. + +```shell +curl -X POST https://gnfd-testnet-bundle.nodereal.io/v1/bundlerAccount/0xf74d8897D8BeafDF4b766E19A62078DE84570656 + +{"address":"0x4605BFc98E0a5EA63D9D5a4a1Df549732a6963f3"} +``` + +### Grant fee and permission to the bundle address for creating bundled objects under the bucket + + +#### use go-sdk + +```go + bucketActions := []permTypes.ActionType{permTypes.ACTION_CREATE_OBJECT} + statements := utils.NewStatement(bucketActions, permTypes.EFFECT_ALLOW, nil, sdktypes.NewStatementOptions{}) + bundleAgentPrincipal, err := utils.NewPrincipalWithAccount(sdk.MustAccAddressFromHex(bundleAddrToGrant)) + if err != nil { + util.Logger.Fatalf("NewPrincipalWithAccount: %v", err) + return +} + + _, err = cli.PutBucketPolicy(ctx, BlobBucketName, BundleAgentPrincipal, []*permTypes.Statement{&statements}, sdktypes.PutPolicyOption{}) + if err != nil { + util.Logger.Fatalf("put policy failed: %v", err) + return +} +``` + +grant allowance, + +the example shows allowance amount of 1 BNB, considered each object creation gas consumed is 0.000006 BNB, it is approximately for 1666667 objects. + +```go + allowanceAmount := math.NewIntWithDecimal(1, 19) + _, err = cli.GrantBasicAllowance(ctx, bundleAcct.String(), allowanceAmount, nil, gnfdsdktypes.TxOption{}) + if err != nil { + util.Logger.Fatalf("grant fee allowance failed: %v", err) + } + +``` + +You can find similar example [permission](https://github.com/bnb-chain/greenfield-go-sdk/blob/master/examples/permission.go): + +#### use provided sript +or you can use the script, replace `GRANTEE_BUNDLE_ACCOUNT` with the addr got from Bundle Service in step 2, and modify the `ALLOWANCE` to your expect amount, so that transacion +gas will be paid your account: + +```shell +bash scripts/set_up.sh --grant +``` + +After above steps are done, you can start running the Blob Syncer Service. + + +## Build + +### Build all + +```shell +make build +``` + +### Build blob syncer + +```shell +make build_syncer +``` + +### Build blob syncer api server + +```shell +make build_server +``` + +## Run + +### Run the Blob Syncer instance + +```shell +./build/blob-syncer --config-path config/local/config-syncer.json +``` + +```json +{ + "bucket_name": "your-bucket", + "start_slot": 8783000, + "create_bundle_slot_interval": 10, + "beacon_rpc_addrs": [ + "https://eth2-beacon-mainnet.nodereal.io" + ], + "bundle_service_endpoints": [ + "https://gnfd-testnet-bundle.nodereal.io" + ], + "eth_rpc_addrs": [ + "https://eth-mainnet.nodereal.io" + ], + "temp_dir": "temp", + "private_key": "0x....", + "db_config": { + "dialect": "mysql", + "username": "root", + "password": "pass", + "url": "/blob-syncer?charset=utf8&parseTime=True&loc=Local", + "max_idle_conns": 10, + "max_open_conns": 100 + }, + "metrics_config": { + "enable": true, + "http_address": "" + }, + "log_config": { + "level": "DEBUG", + "filename": "", + "max_file_size_in_mb": 0, + "max_backups_of_log_files": 0, + "max_age_to_retain_log_files_in_days": 0, + "use_console_logger": true, + "use_file_logger": false, + "compress": false + } +} +``` + +### Run the api server + +```shell +./build/blob-syncer-server --config-path config/local/config-server.json --port 8080 +``` + +## Blob syncer API + +The Blob syncer server provides eth compatible API to query historical blob + + +### Get blob sidecars. + +* GET /eth/v1/beacon/blob_sidecars/{block_id} + +| ParameterName | Type | Description | +| ------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| block_id | string | Block identifier. Can be one of: slot(beacon chain), . note: "head" (canonical head in node's view), "genesis", "finalized" are not support | +| indices | array of string | Array of indices for blob sidecars to request for in the specified block. Returns all blob sidecars in the block if not specified | + + +200: Ok response + +```json +{ + "data": [ + { + "index": "0", + "blob": "0x00b900026b636f6e74656e745479706569696d6167652f706e6767636f6e7465006e745a0001c8aa1f8b0800000000000003c497e55714dc17ef67e8ee6e8691009666405a1c4a40a4bb4b4a40ba41a41ba44b910649e998a11ba4bbbb4b...", + "kzg_commitment": "0x8f5b5ac395257c71080721a72dfbc2a4260184a9fe6442d53ab17cd3c7246cfc263fbad5f063456bcfefea2c2795378a", + "kzg_proof": "0x9952be38421793ca564e3cb779e14345912184bd883b8532629c23e948ba5c29103ddd072d1fbbb5e521a9bee3ee7925", + "kzg_commitment_inclusion_proof": [ + "0x82ba896ae27ae4d01108146fa4a8313522b966697b088ec0e8f1e53f56c83626", + "0x256135c2cf896b0790ab66a3a9b5cbbe5971968cbc72fc151063e92f500440a2", + "0x93de7d5c33984c7e6c91d486aa097662517e933655c2155c0857a05e43074da5", + "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", + "0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c", + "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30", + "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1", + "0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c", + "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193", + "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1", + "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b", + "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220", + "0x0600000000000000000000000000000000000000000000000000000000000000", + "0x792930bbd5baac43bcc798ee49aa8185ef76bb3b44ba62b91d86ae569e4bb535", + "0x16c5286816e0c2fe95421dc404efb8919aa762db0a15e852933a2ad965aa9ed5", + "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", + "0xd5be0ed2682550ce71db46a6d05873d67bbb855b43a9e017eb76afe317cf7e7d" + ], + "signed_block_header": { + "message": { + "body_root": "0xfeffb7e2e57b5dac8849ce45723c701033053788dd8615fd8e2ad68689ea2cbf", + "parent_root": "0xd39e1b7b8c5c2226d80a071cf919744679b22d95ce241210e6dee5dd76317dce", + "proposer_index": "452467", + "slot": "8783262", + "state_root": "0xf014944ead7b1524d3b3d3e76c0285e20ffb277f3778c5f6be63c487904204cf" + } + } + } + ] +} +``` + + +## Access to blob data directly in greenfield + +In scenarios where the Bundle Service is inaccessible, direct access to blob data stored in Greenfield may be necessary, as blobs are consolidated into a bundle object. +You can retrieve the bundle object and extract specific blobs from it. + +### Namimg pattern + +The bundle objects uploaded to Greenfield adhere to the naming pattern `blobs_s{startSlot}_e{endSlot}`. For instance, if a bundle encompasses blobs within 30 slots, the bundle object could be named `blobs_s8864048_e8864077`, where 8864048 represents the start slot and 8864077 denotes the end slot. The individual blobs contained within this bundle follow the naming convention `blob_h{slot}_i{index}`, such as `blob_h8864074_i3`. + +### Retrieving Blobs via Bundle SDK + +The [bundle SDK](https://github.com/bnb-chain/greenfield-bundle-sdk) offers commands to fetch the bundle object from Greenfield. Follow the steps below: + + +```bash +git clone https://github.com/bnb-chain/greenfield-bundle-sdk.git + +cd greenfield-bundle-sdk + +make build + +cd build + +./bundler download -bucket eth-blob2 -object blobs_s8864048_e8864077 -chain-id greenfield_5600-1 -rpc-url https://gnfd-testnet-fullnode-tendermint-us.bnbchain.org:443 -output ./tmp +``` + +Once the bundle is downloaded and extracted, all original blob files can be found within the `tmp` directory. \ No newline at end of file diff --git a/cache/cache.go b/cache/cache.go new file mode 100644 index 0000000..3630acb --- /dev/null +++ b/cache/cache.go @@ -0,0 +1,34 @@ +package cache + +import ( + lru "github.com/hashicorp/golang-lru" +) + +type Cache interface { + Get(key string) (interface{}, bool) + Set(key string, value interface{}) +} + +const DefaultCacheSize = 1024 + +type LocalCache struct { + *lru.Cache +} + +func NewLocalCache(size uint64) (Cache, error) { + cache, err := lru.New(int(size)) + if err != nil { + return nil, err + } + return &LocalCache{ + cache, + }, nil +} + +func (c *LocalCache) Get(key string) (interface{}, bool) { + return c.Cache.Get(key) +} + +func (c *LocalCache) Set(key string, value interface{}) { + c.Cache.Add(key, value) +} diff --git a/cmd/blob-syncer-server/main.go b/cmd/blob-syncer-server/main.go new file mode 100644 index 0000000..965d507 --- /dev/null +++ b/cmd/blob-syncer-server/main.go @@ -0,0 +1,57 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package main + +import ( + "log" + "os" + + "github.com/go-openapi/loads" + flags "github.com/jessevdk/go-flags" + + "github.com/bnb-chain/blob-syncer/restapi" + "github.com/bnb-chain/blob-syncer/restapi/operations" +) + +// This file was generated by the swagger tool. +// Make sure not to overwrite this file after you generated it because all your edits would be lost! + +func main() { + + swaggerSpec, err := loads.Embedded(restapi.SwaggerJSON, restapi.FlatSwaggerJSON) + if err != nil { + log.Fatalln(err) + } + + api := operations.NewBlobSyncerAPI(swaggerSpec) + server := restapi.NewServer(api) + defer server.Shutdown() + + parser := flags.NewParser(server, flags.Default) + parser.ShortDescription = "Blob Syncer Service API" + parser.LongDescription = "API for handling blob query in the Blob Syncer." + server.ConfigureFlags() + for _, optsGroup := range api.CommandLineOptionsGroups { + _, err := parser.AddGroup(optsGroup.ShortDescription, optsGroup.LongDescription, optsGroup.Options) + if err != nil { + log.Fatalln(err) + } + } + + if _, err := parser.Parse(); err != nil { + code := 1 + if fe, ok := err.(*flags.Error); ok { + if fe.Type == flags.ErrHelp { + code = 0 + } + } + os.Exit(code) + } + + server.ConfigureAPI() + + if err := server.Serve(); err != nil { + log.Fatalln(err) + } + +} diff --git a/cmd/blob-syncer/main.go b/cmd/blob-syncer/main.go new file mode 100644 index 0000000..21ebeb8 --- /dev/null +++ b/cmd/blob-syncer/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "flag" + "os" + + "github.com/spf13/pflag" + "github.com/spf13/viper" + + "github.com/bnb-chain/blob-syncer/config" + syncerdb "github.com/bnb-chain/blob-syncer/db" + "github.com/bnb-chain/blob-syncer/logging" + "github.com/bnb-chain/blob-syncer/metrics" + "github.com/bnb-chain/blob-syncer/syncer" +) + +func initFlags() { + flag.String(config.FlagConfigPath, "", "config file path") + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.Parse() + err := viper.BindPFlags(pflag.CommandLine) + if err != nil { + panic(err) + } +} + +func main() { + var ( + cfg *config.SyncerConfig + configFilePath string + ) + initFlags() + configFilePath = viper.GetString(config.FlagConfigPath) + if configFilePath == "" { + configFilePath = os.Getenv(config.EnvVarConfigFilePath) + } + cfg = config.ParseSyncerConfigFromFile(configFilePath) + if cfg == nil { + panic("failed to get configuration") + } + cfg.Validate() + logging.InitLogger(&cfg.LogConfig) + db := config.InitDBWithConfig(&cfg.DBConfig, true) + blobDB := syncerdb.NewBlobSvcDB(db) + bs := syncer.NewBlobSyncer(blobDB, cfg) + go bs.StartLoop() + + if cfg.MetricsConfig.Enable { + if cfg.MetricsConfig.HttpAddress == "" { + cfg.MetricsConfig.HttpAddress = metrics.DefaultMetricsAddress + } + metric := metrics.NewMetrics(cfg.MetricsConfig.HttpAddress) + go metric.Start() + } + + select {} +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..8800174 --- /dev/null +++ b/config/config.go @@ -0,0 +1,225 @@ +package config + +import ( + "encoding/json" + "fmt" + "log" + "os" + "time" + + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/logger" + + "github.com/bnb-chain/blob-syncer/cache" + syncerdb "github.com/bnb-chain/blob-syncer/db" +) + +type SyncerConfig struct { + BucketName string `json:"bucket_name"` // BucketName is the identifier of bucket on Greenfield that store blob + StartSlot uint64 `json:"start_slot"` // StartSlot is used to init the syncer which slot of beacon chain to synced from, only need to provide once. + CreateBundleSlotInterval uint64 `json:"create_bundle_slot_interval"` // CreateBundleSlotInterval defines the number of slot that syncer would assemble blobs and upload to bundle service + BundleServiceEndpoints []string `json:"bundle_service_endpoints"` // BundleServiceEndpoints is a list of bundle service address + BeaconRPCAddrs []string `json:"beacon_rpc_addrs"` // BeaconRPCAddrs is a list of beacon chain RPC address + ETHRPCAddrs []string `json:"eth_rpc_addrs"` + TempDir string `json:"temp_dir"` // TempDir is used to store blobs and created bundle + PrivateKey string `json:"private_key"` // PrivateKey is the key of bucket owner, request to bundle service will be signed by it as well. + DBConfig DBConfig `json:"db_config"` + MetricsConfig MetricsConfig `json:"metrics_config"` + LogConfig LogConfig `json:"log_config"` +} + +func (s *SyncerConfig) Validate() { + if len(s.BucketName) == 0 { + panic("the Greenfield bucket name is not is not provided") + } + if s.StartSlot == 0 { + panic("the start slot to sync slot is not provided") + } + if len(s.BundleServiceEndpoints) == 0 { + panic("BundleService endpoints should not be empty") + } + if len(s.BeaconRPCAddrs) == 0 { + panic("beacon rpc address should not be empty") + } + if len(s.ETHRPCAddrs) == 0 { + panic("eth rpc address should not be empty") + } + if len(s.TempDir) == 0 { + panic("temp directory is not specified") + } + if len(s.PrivateKey) == 0 { + panic("private key is not provided") + } + if s.CreateBundleSlotInterval > 30 { + panic("create_bundle_slot_interval is supposed less than 20") + } + s.DBConfig.Validate() +} + +func (s *SyncerConfig) GetCreateBundleSlotInterval() uint64 { + if s.CreateBundleSlotInterval == 0 { + return DefaultCreateBundleSlotInterval + } + return s.CreateBundleSlotInterval +} + +type ServerConfig struct { + BucketName string `json:"bucket_name"` + BundleServiceEndpoints []string `json:"bundle_service_endpoints"` // BundleServiceEndpoints is a list of bundle service address + CacheConfig CacheConfig `json:"cache_config"` + DBConfig DBConfig `json:"db_config"` +} + +func (s *ServerConfig) Validate() { + if len(s.BucketName) == 0 { + panic("the Greenfield bucket name is not is not provided") + } + if len(s.BundleServiceEndpoints) == 0 { + panic("BundleService endpoints should not be empty") + } + s.DBConfig.Validate() +} + +type CacheConfig struct { + CacheType string `json:"cache_type"` + URL string `json:"url"` + CacheSize uint64 `json:"cache_size"` +} + +func (c *CacheConfig) GetCacheSize() uint64 { + if c.CacheSize != 0 { + return c.CacheSize + } + return cache.DefaultCacheSize +} + +type DBConfig struct { + Dialect string `json:"dialect"` + Username string `json:"username"` + Password string `json:"password"` + Url string `json:"url"` + MaxIdleConns int `json:"max_idle_conns"` + MaxOpenConns int `json:"max_open_conns"` +} + +func (cfg *DBConfig) Validate() { + if cfg.Dialect != DBDialectMysql { + panic(fmt.Sprintf("only %s supported", DBDialectMysql)) + } + if cfg.Dialect == DBDialectMysql && (cfg.Username == "" || cfg.Url == "") { + panic("db config is not correct, missing username and/or url") + } + if cfg.MaxIdleConns == 0 || cfg.MaxOpenConns == 0 { + panic("db connections is not correct") + } +} + +type MetricsConfig struct { + Enable bool `json:"enable"` + HttpAddress string `json:"http_address"` +} + +type LogConfig struct { + Level string `json:"level"` + Filename string `json:"filename"` + MaxFileSizeInMB int `json:"max_file_size_in_mb"` + MaxBackupsOfLogFiles int `json:"max_backups_of_log_files"` + MaxAgeToRetainLogFilesInDays int `json:"max_age_to_retain_log_files_in_days"` + UseConsoleLogger bool `json:"use_console_logger"` + UseFileLogger bool `json:"use_file_logger"` + Compress bool `json:"compress"` +} + +func (cfg *LogConfig) Validate() { + if cfg.UseFileLogger { + if cfg.Filename == "" { + panic("filename should not be empty if use file logger") + } + if cfg.MaxFileSizeInMB <= 0 { + panic("max_file_size_in_mb should be larger than 0 if use file logger") + } + if cfg.MaxBackupsOfLogFiles <= 0 { + panic("max_backups_off_log_files should be larger than 0 if use file logger") + } + } +} + +func ParseSyncerConfigFromFile(filePath string) *SyncerConfig { + bz, err := os.ReadFile(filePath) + if err != nil { + panic(err) + } + + var config SyncerConfig + if err = json.Unmarshal(bz, &config); err != nil { + panic(err) + } + if config.DBConfig.Username == "" || config.DBConfig.Password == "" { // read password from ENV + config.DBConfig.Username, config.DBConfig.Password = GetDBUsernamePasswordFromEnv() + } + if config.PrivateKey == "" { // read private key from ENV + config.PrivateKey = os.Getenv(EnvVarPrivateKey) + } + return &config +} + +func ParseServerConfigFromFile(filePath string) *ServerConfig { + bz, err := os.ReadFile(filePath) + if err != nil { + panic(err) + } + var config ServerConfig + if err = json.Unmarshal(bz, &config); err != nil { + panic(err) + } + if config.DBConfig.Username == "" || config.DBConfig.Password == "" { // read password from ENV + config.DBConfig.Username, config.DBConfig.Password = GetDBUsernamePasswordFromEnv() + } + return &config +} + +func GetDBUsernamePasswordFromEnv() (string, string) { + username := os.Getenv(EnvVarDBUserName) + password := os.Getenv(EnvVarDBUserPass) + return username, password +} + +func InitDBWithConfig(cfg *DBConfig, writeAccess bool) *gorm.DB { + var db *gorm.DB + var err error + var dialector gorm.Dialector + + if cfg.Dialect == DBDialectMysql { + url := cfg.Url + dbPath := fmt.Sprintf("%s:%s@%s", cfg.Username, cfg.Password, url) + dialector = mysql.Open(dbPath) + } else { + panic(fmt.Sprintf("unexpected DB dialect %s", cfg.Dialect)) + } + newLogger := logger.New( + log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer + logger.Config{ + SlowThreshold: 10 * time.Millisecond, // Slow SQL threshold + LogLevel: logger.Silent, + IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger + Colorful: true, // Disable color + }, + ) + db, err = gorm.Open(dialector, &gorm.Config{ + Logger: newLogger, + }) + if err != nil { + panic(fmt.Sprintf("open db error, err=%s", err.Error())) + } + dbConfig, err := db.DB() + if err != nil { + panic(err) + } + dbConfig.SetMaxIdleConns(cfg.MaxIdleConns) + dbConfig.SetMaxOpenConns(cfg.MaxOpenConns) + if writeAccess { + syncerdb.AutoMigrateDB(db) + } + return db +} diff --git a/config/const.go b/config/const.go new file mode 100644 index 0000000..f79db5a --- /dev/null +++ b/config/const.go @@ -0,0 +1,14 @@ +package config + +const ( + FlagConfigPath = "config-path" + + DBDialectMysql = "mysql" + + EnvVarConfigFilePath = "CONFIG_FILE_PATH" + EnvVarDBUserName = "DB_USERNAME" + EnvVarDBUserPass = "DB_PASSWORD" + EnvVarPrivateKey = "PRIVATE_KEY" + + DefaultCreateBundleSlotInterval = 20 +) diff --git a/config/example/config-server.json b/config/example/config-server.json new file mode 100644 index 0000000..2590045 --- /dev/null +++ b/config/example/config-server.json @@ -0,0 +1,29 @@ +{ + "bucket_name": "yourbucketname", + "bundle_service_endpoints": [ + "https://gnfd-testnet-bundle.nodereal.io" + ], + "db_config": { + "dialect": "mysql", + "username": "root", + "password": "pass", + "url": "/local-blob-syncer?charset=utf8&parseTime=True&loc=Local", + "max_idle_conns": 10, + "max_open_conns": 100 + }, + "cache_config": { + "cache_type": "local", + "url":"", + "cache_size": 1024 + }, + "log_config": { + "level": "DEBUG", + "filename": "", + "max_file_size_in_mb": 0, + "max_backups_of_log_files": 0, + "max_age_to_retain_log_files_in_days": 0, + "use_console_logger": true, + "use_file_logger": false, + "compress": false + } +} \ No newline at end of file diff --git a/config/example/config-syncer.json b/config/example/config-syncer.json new file mode 100644 index 0000000..0f2dc50 --- /dev/null +++ b/config/example/config-syncer.json @@ -0,0 +1,38 @@ +{ + "bucket_name": "yourbucketname", + "start_slot": 8860704, + "create_bundle_slot_interval": 10, + "beacon_rpc_addrs": [ + "https://eth2-beacon-mainnet.nodereal.io" + ], + "bundle_service_endpoints": [ + "https://gnfd-testnet-bundle.nodereal.io" + ], + "eth_rpc_addrs": [ + "https://eth-mainnet.nodereal.io" + ], + "temp_dir": "temp", + "private_key": "0x...", + "db_config": { + "dialect": "mysql", + "username": "root", + "password": "pass", + "url": "/local-blob-syncer?charset=utf8&parseTime=True&loc=Local", + "max_idle_conns": 10, + "max_open_conns": 100 + }, + "metrics_config": { + "enable": true, + "http_address": "" + }, + "log_config": { + "level": "DEBUG", + "filename": "", + "max_file_size_in_mb": 0, + "max_backups_of_log_files": 0, + "max_age_to_retain_log_files_in_days": 0, + "use_console_logger": true, + "use_file_logger": false, + "compress": false + } +} \ No newline at end of file diff --git a/db/blob.go b/db/blob.go new file mode 100644 index 0000000..4a81586 --- /dev/null +++ b/db/blob.go @@ -0,0 +1,18 @@ +package db + +type Blob struct { + Id int64 + Name string `gorm:"NOT NULL;uniqueIndex:idx_blob_name;size:96"` // the identifier of blob object in bundle service + TxHash string `gorm:"NOT NULL;index:idx_blob_tx_hash"` + ToAddr string `gorm:"NOT NULL;index:idx_blob_to_address"` + VersionedHash string `gorm:"NOT NULL"` + Slot uint64 `gorm:"NOT NULL;index:idx_blob_slot_index"` + Idx int `gorm:"NOT NULL;index:idx_blob_slot_idx"` + KzgCommitment string `gorm:"NOT NULL"` + KzgProof string `gorm:"NOT NULL"` + CommitmentInclusionProof string `gorm:"NOT NULL"` +} + +func (*Blob) TableName() string { + return "blob" +} diff --git a/db/block.go b/db/block.go new file mode 100644 index 0000000..7f7f5b7 --- /dev/null +++ b/db/block.go @@ -0,0 +1,29 @@ +package db + +type Status int + +const ( + Processed Status = 0 + Verified Status = 1 // each block's blobs will be verified by the post-verification process + Skipped Status = 2 +) + +type Block struct { + Id int64 + Root string `gorm:"NOT NULL;index:idx_block_root;size:64"` + ParentRoot string + StateRoot string + BodyRoot string + ProposerIndex uint64 + Signature string + Slot uint64 `gorm:"NOT NULL;uniqueIndex:idx_block_slot"` + ELBlockHeight uint64 // the eth1 block height + BlobCount int + + BundleName string `gorm:"NOT NULL"` + Status Status +} + +func (*Block) TableName() string { + return "block" +} diff --git a/db/bundle.go b/db/bundle.go new file mode 100644 index 0000000..5b42b6d --- /dev/null +++ b/db/bundle.go @@ -0,0 +1,21 @@ +package db + +type InnerBundleStatus int + +const ( + Finalizing InnerBundleStatus = 0 + Finalized InnerBundleStatus = 1 // when a bundle is uploaded to bundle service, its status will be Finalized + Sealed InnerBundleStatus = 2 // todo The post verification process should check if a bundle is indeed sealed onchain + Deprecated InnerBundleStatus = 3 +) + +type Bundle struct { + Id int64 + Name string `gorm:"NOT NULL;uniqueIndex:idx_bundle_name;size:64"` + Status InnerBundleStatus `gorm:"NOT NULL"` + Calibrated bool +} + +func (*Bundle) TableName() string { + return "bundle" +} diff --git a/db/dao.go b/db/dao.go new file mode 100644 index 0000000..bc1c525 --- /dev/null +++ b/db/dao.go @@ -0,0 +1,173 @@ +package db + +import ( + "gorm.io/gorm" +) + +type BlobDao interface { + BlockDB + BlobDB + BundleDB + SaveBlockAndBlob(block *Block, blobs []*Blob) error +} + +type BlobSvcDB struct { + db *gorm.DB +} + +func NewBlobSvcDB(db *gorm.DB) BlobDao { + return &BlobSvcDB{ + db, + } +} + +type BlockDB interface { + GetBlock(slot uint64) (*Block, error) + GetBlockByRoot(root string) (*Block, error) + GetLatestProcessedBlock() (*Block, error) + GetEarliestUnverifiedBlock() (*Block, error) + UpdateBlockStatus(slot uint64, status Status) error + UpdateBlocksStatus(startSlot, endSlot uint64, status Status) error +} + +func (d *BlobSvcDB) GetBlock(slot uint64) (*Block, error) { + block := Block{} + err := d.db.Model(Block{}).Where("slot = ?", slot).Take(&block).Error + if err != nil { + return nil, err + } + return &block, nil +} + +func (d *BlobSvcDB) GetBlockByRoot(root string) (*Block, error) { + block := Block{} + err := d.db.Model(Block{}).Where("root = ?", root).Take(&block).Error + if err != nil && err != gorm.ErrRecordNotFound { + return nil, err + } + return &block, nil +} + +func (d *BlobSvcDB) GetLatestProcessedBlock() (*Block, error) { + block := Block{} + err := d.db.Model(Block{}).Order("slot desc").Take(&block).Error + if err != nil && err != gorm.ErrRecordNotFound { + return nil, err + } + return &block, nil +} + +func (d *BlobSvcDB) GetEarliestUnverifiedBlock() (*Block, error) { + block := Block{} + err := d.db.Model(Block{}).Where("status = ?", Processed).Order("slot asc").Take(&block).Error + if err != nil { + return nil, err + } + return &block, nil +} + +func (d *BlobSvcDB) UpdateBlockStatus(slot uint64, status Status) error { + return d.db.Transaction(func(dbTx *gorm.DB) error { + return dbTx.Model(Block{}).Where("slot = ?", slot).Updates( + Block{Status: status}).Error + }) +} + +func (d *BlobSvcDB) UpdateBlocksStatus(startSlot, endSlot uint64, status Status) error { + return d.db.Transaction(func(dbTx *gorm.DB) error { + return dbTx.Model(Block{}).Where("slot >= ? and slot <= ?", startSlot, endSlot).Updates( + Block{Status: status}).Error + }) +} + +type BlobDB interface { + GetBlobBySlot(slot uint64) ([]*Blob, error) + GetBlobBySlotAndIndices(slot uint64, indices []int64) ([]*Blob, error) +} + +func (d *BlobSvcDB) GetBlobBySlot(slot uint64) ([]*Blob, error) { + blobs := make([]*Blob, 0) + if err := d.db.Where("slot = ?", slot).Order("idx asc").Find(&blobs).Error; err != nil { + return blobs, err + } + return blobs, nil +} + +func (d *BlobSvcDB) GetBlobBySlotAndIndices(slot uint64, indices []int64) ([]*Blob, error) { + blobs := make([]*Blob, 0) + if err := d.db.Where("slot = ? and idx in (?)", slot, indices).Order("idx asc").Find(&blobs).Error; err != nil { + return blobs, err + } + return blobs, nil +} + +type BundleDB interface { + GetBundle(name string) (*Bundle, error) + GetLatestFinalizingBundle() (*Bundle, error) + CreateBundle(*Bundle) error + UpdateBundleStatus(bundleName string, status InnerBundleStatus) error +} + +func (d *BlobSvcDB) GetBundle(name string) (*Bundle, error) { + bundle := Bundle{} + err := d.db.Model(Bundle{}).Where("name = ?", name).Take(&bundle).Error + if err != nil { + return nil, err + } + return &bundle, nil +} + +func (d *BlobSvcDB) GetLatestFinalizingBundle() (*Bundle, error) { + bundle := Bundle{} + err := d.db.Model(Bundle{}).Where("status = ? and calibrated = false", Finalizing).Order("id desc").Take(&bundle).Error + if err != nil { + return nil, err + } + return &bundle, nil +} + +func (d *BlobSvcDB) CreateBundle(b *Bundle) error { + return d.db.Transaction(func(dbTx *gorm.DB) error { + err := dbTx.Create(b).Error + if err != nil && MysqlErrCode(err) == ErrDuplicateEntryCode { + return nil + } + return err + }) +} + +func (d *BlobSvcDB) UpdateBundleStatus(bundleName string, status InnerBundleStatus) error { + return d.db.Transaction(func(dbTx *gorm.DB) error { + return dbTx.Model(Bundle{}).Where("name = ?", bundleName).Updates( + Bundle{Status: status}).Error + }) +} + +func (d *BlobSvcDB) SaveBlockAndBlob(block *Block, blobs []*Blob) error { + return d.db.Transaction(func(dbTx *gorm.DB) error { + err := dbTx.Save(block).Error + if err != nil && MysqlErrCode(err) != ErrDuplicateEntryCode { + return err + } + if len(blobs) != 0 { + err = dbTx.Save(blobs).Error + if err != nil && MysqlErrCode(err) != ErrDuplicateEntryCode { + return err + } + } + return nil + }) +} + +func AutoMigrateDB(db *gorm.DB) { + var err error + if err = db.AutoMigrate(&Bundle{}); err != nil { + panic(err) + } + if err = db.AutoMigrate(&Block{}); err != nil { + panic(err) + } + if err = db.AutoMigrate(&Blob{}); err != nil { + panic(err) + } +} diff --git a/db/type.go b/db/type.go new file mode 100644 index 0000000..bff15c1 --- /dev/null +++ b/db/type.go @@ -0,0 +1,17 @@ +package db + +import ( + "github.com/go-sql-driver/mysql" +) + +var ( + ErrDuplicateEntryCode = 1062 +) + +func MysqlErrCode(err error) int { + mysqlErr, ok := err.(*mysql.MySQLError) + if !ok { + return 0 + } + return int(mysqlErr.Number) +} diff --git a/external/beacon_client.go b/external/beacon_client.go new file mode 100644 index 0000000..6376565 --- /dev/null +++ b/external/beacon_client.go @@ -0,0 +1,147 @@ +package external + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strconv" + "time" + + "github.com/prysmaticlabs/prysm/v5/api/server/structs" +) + +var ( + ErrBlockNotFound = errors.New("the block is not found in beacon chain") // note: get a forked block also return 404 +) + +const ( + pathGetSidecars = "/eth/v1/beacon/blob_sidecars/%s" + pathGetBlock = "/eth/v2/beacon/blocks/%s" + pathGetHeader = "/eth/v1/beacon/headers/%s" +) + +type BeaconClient struct { + hc *http.Client + timeout time.Duration + host string +} + +// NewBeaconClient returns a new beacon client. +func NewBeaconClient(host string, timeout time.Duration) (*BeaconClient, error) { + transport := &http.Transport{ + DisableCompression: true, + MaxIdleConnsPerHost: 1000, + MaxConnsPerHost: 1000, + IdleConnTimeout: 90 * time.Second, + } + client := &http.Client{ + Timeout: 10 * time.Minute, + Transport: transport, + } + return &BeaconClient{hc: client, + timeout: timeout, host: host}, nil +} + +func (c *BeaconClient) GetBlob(ctx context.Context, slotNumber uint64) ([]*structs.Sidecar, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.host+fmt.Sprintf(pathGetSidecars, strconv.FormatUint(slotNumber, 10)), nil) + if err != nil { + return nil, err + } + r, err := c.hc.Do(req) + if err != nil { + return nil, err + } + defer func() { + err = r.Body.Close() + }() + respBz, err := io.ReadAll(r.Body) + if err != nil { + return nil, fmt.Errorf("error reading http response body %s", err) + } + var sidecars structs.SidecarsResponse + err = json.Unmarshal(respBz, &sidecars) + if err != nil { + return nil, err + } + return sidecars.Data, nil +} + +func (c *BeaconClient) GetBlock(ctx context.Context, slotNumber uint64) (*structs.GetBlockV2Response, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.host+fmt.Sprintf(pathGetBlock, strconv.FormatUint(slotNumber, 10)), nil) + if err != nil { + return nil, err + } + r, err := c.hc.Do(req) + if err != nil { + return nil, err + } + defer func() { + err = r.Body.Close() + }() + b, err := io.ReadAll(r.Body) + if err != nil { + return nil, fmt.Errorf("error reading http response body %s", err) + } + + if r.StatusCode != http.StatusOK { + if r.StatusCode == http.StatusNotFound { + return nil, ErrBlockNotFound + } + return nil, fmt.Errorf("received non-OK response status: %s", r.Status) + } + resp := &structs.GetBlockV2Response{} + return resp, json.Unmarshal(b, resp) + +} + +func (c *BeaconClient) GetLatestBlock(ctx context.Context) (*structs.GetBlockV2Response, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.host+fmt.Sprintf(pathGetBlock, "head"), nil) + if err != nil { + return nil, err + } + r, err := c.hc.Do(req) + if err != nil { + return nil, err + } + defer func() { + err = r.Body.Close() + }() + b, err := io.ReadAll(r.Body) + if err != nil { + return nil, fmt.Errorf("error reading http response body %s", err) + } + resp := &structs.GetBlockV2Response{} + return resp, json.Unmarshal(b, resp) + +} + +func (c *BeaconClient) GetHeader(ctx context.Context, slotNumber uint64) (*structs.GetBlockHeaderResponse, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.host+fmt.Sprintf(pathGetHeader, strconv.FormatUint(slotNumber, 10)), nil) + if err != nil { + return nil, err + } + r, err := c.hc.Do(req) + if err != nil { + return nil, err + } + defer func() { + err = r.Body.Close() + }() + b, err := io.ReadAll(r.Body) + if err != nil { + return nil, fmt.Errorf("error reading http response body %s", err) + } + + if r.StatusCode != http.StatusOK { + if r.StatusCode == http.StatusNotFound { + return nil, ErrBlockNotFound + } + return nil, fmt.Errorf("received non-OK response status: %s", r.Status) + } + resp := &structs.GetBlockHeaderResponse{} + return resp, json.Unmarshal(b, resp) + +} diff --git a/external/bundle_client.go b/external/bundle_client.go new file mode 100644 index 0000000..4c34157 --- /dev/null +++ b/external/bundle_client.go @@ -0,0 +1,441 @@ +package external + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "mime" + "mime/multipart" + "net/http" + "os" + "path/filepath" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + modle "github.com/node-real/greenfield-bundle-service/models" + "github.com/node-real/greenfield-bundle-service/types" + + bundlesdk "github.com/bnb-chain/greenfield-bundle-sdk/bundle" + bundlesdktypes "github.com/bnb-chain/greenfield-bundle-sdk/types" +) + +const ( + pathCreateBundle = "/v1/createBundle" + pathFinalizeBundle = "/v1/finalizeBundle" + pathDeleteBundle = "/v1/deleteBundle" + pathUploadBundle = "/v1/uploadBundle" + + pathUploadObject = "/v1/uploadObject" + pathGetBundleInfo = "/v1/queryBundle/%s/%s" + pathGetBundleObject = "/v1/view/%s/%s/%s" // {bucketName}/{bundleName}/{objectName} + + bundleExpiredTime = 24 * time.Hour +) + +var ( + ErrorBundleNotExist = errors.New("the bundle not exist in bundle service") + ErrorBundleObjectNotExist = errors.New("the bundle object not exist in bundle service") +) + +type BundleClientOption interface { + Apply(*BundleClient) +} + +type BundleClientOptionFunc func(*BundleClient) + +// Apply set up the option field to the client instance. +func (f BundleClientOptionFunc) Apply(client *BundleClient) { + f(client) +} + +func WithPrivateKey(privateKey []byte) BundleClientOption { + return BundleClientOptionFunc(func(client *BundleClient) { + client.privKey = privateKey + }) +} + +type BundleClient struct { + hc *http.Client + host string + privKey []byte + addr common.Address +} + +func NewBundleClient(host string, opts ...BundleClientOption) (*BundleClient, error) { + transport := &http.Transport{ + DisableCompression: true, + MaxIdleConnsPerHost: 1000, + MaxConnsPerHost: 1000, + IdleConnTimeout: 90 * time.Second, + } + client := &http.Client{ + Timeout: 10 * time.Minute, + Transport: transport, + } + bundleClient := &BundleClient{hc: client, + host: host, + } + for _, opt := range opts { + opt.Apply(bundleClient) + } + if len(bundleClient.privKey) != 0 { + privateKey, err := crypto.ToECDSA(bundleClient.privKey) + if err != nil { + return nil, err + } + bundleClient.addr = crypto.PubkeyToAddress(privateKey.PublicKey) + } + return bundleClient, nil +} + +func (c *BundleClient) CreateBundle(bundleName, bucketName string) error { + headers := map[string]string{ + "Content-Type": "application/json", + "X-Bundle-Bucket-Name": bucketName, + "X-Bundle-Name": bundleName, + "X-Bundle-Expiry-Timestamp": fmt.Sprintf("%d", time.Now().Add(1*time.Hour).Unix()), + } + resp, err := c.sendRequest(c.host+pathCreateBundle, "POST", headers, nil) + if err != nil { + return err + } + defer resp.Body.Close() + bodyStr, err := ReadResponseBody(resp) + if err != nil { + return err + } + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("received non-OK response status: %s, err %s", resp.Status, bodyStr) + } + return nil +} + +func (c *BundleClient) FinalizeBundle(bundleName, bucketName string) error { + headers := map[string]string{ + "Content-Type": "application/json", + "X-Bundle-Bucket-Name": bucketName, + "X-Bundle-Name": bundleName, + "X-Bundle-Expiry-Timestamp": fmt.Sprintf("%d", time.Now().Add(bundleExpiredTime).Unix()), + } + // finalize bundle + resp, err := c.sendRequest(c.host+pathFinalizeBundle, "POST", headers, nil) + if err != nil { + return err + } + defer resp.Body.Close() + bodyStr, err := ReadResponseBody(resp) + if err != nil { + return err + } + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("received non-OK response status: %s, err %s", resp.Status, bodyStr) + } + return nil +} + +func (c *BundleClient) DeleteBundle(bundleName, bucketName string) error { + headers := map[string]string{ + "Content-Type": "application/json", + "X-Bundle-Bucket-Name": bucketName, + "X-Bundle-Name": bundleName, + "X-Bundle-Expiry-Timestamp": fmt.Sprintf("%d", time.Now().Add(bundleExpiredTime).Unix()), + } + resp, err := c.sendRequest(c.host+pathDeleteBundle, "POST", headers, nil) + if err != nil { + return err + } + defer resp.Body.Close() + bodyStr, err := ReadResponseBody(resp) + if err != nil { + return err + } + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("received non-OK response status: %s, err %s", resp.Status, bodyStr) + } + return nil +} + +func (c *BundleClient) UploadAndFinalizeBundle(bundleName, bucketName, bundleDir, bundlePath string) error { + + bundleObject, _, err := bundleDirectory(bundleDir) + if err != nil { + return err + } + bundleFilePath := bundlePath + err = saveBundleToFile(bundleObject, bundleFilePath) + if err != nil { + return err + } + + bundleFile, err := os.Open(bundleFilePath) + if err != nil { + return err + } + defer bundleFile.Close() + bundleContent, err := io.ReadAll(bundleFile) + if err != nil { + return err + } + + // Calculate the SHA256 hash of the bundle file content + hash := sha256.Sum256(bundleContent) + hashInHex := hex.EncodeToString(hash[:]) + + // Create a new multipart form and add the bundle file to it + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + filePart, err := writer.CreateFormFile("file", bundleFile.Name()) + if err != nil { + return err + } + _, err = io.Copy(filePart, bytes.NewReader(bundleContent)) + if err != nil { + return err + } + err = writer.Close() + if err != nil { + return err + } + headers := map[string]string{ + "Content-Type": writer.FormDataContentType(), + "X-Bundle-Bucket-Name": bucketName, + "X-Bundle-Name": bundleName, + "X-Bundle-File-Sha256": hashInHex, + "X-Bundle-Expiry-Timestamp": fmt.Sprintf("%d", time.Now().Add(bundleExpiredTime).Unix()), + } + resp, err := c.sendRequest(c.host+pathUploadBundle, "POST", headers, body.Bytes()) + if err != nil { + return err + } + defer resp.Body.Close() + bodyStr, err := ReadResponseBody(resp) + if err != nil { + return err + } + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("received non-OK response status: %s, err %s", resp.Status, bodyStr) + } + return nil +} + +func (c *BundleClient) UploadObject(fileName, bucketName, bundleName, contentType string, file *os.File) error { + // CreateBlock a new SHA256 hash + hash := sha256.New() + + // Write the file's content to the hash + if _, err := io.Copy(hash, file); err != nil { + return err + } + + // Get the hash sum in bytes + hashInBytes := hash.Sum(nil)[:] + + // Hex encode the hash sum + hashInHex := hex.EncodeToString(hashInBytes) + + // Reset the file read pointer to the beginning + _, _ = file.Seek(0, 0) + + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + filePart, err := writer.CreateFormFile("file", fileName) + if err != nil { + return err + } + _, err = io.Copy(filePart, file) + if err != nil { + return err + } + err = writer.Close() + if err != nil { + return err + } + headers := map[string]string{ + "Content-Type": writer.FormDataContentType(), + "X-Bundle-Bucket-Name": bucketName, + "X-Bundle-Name": bundleName, + "X-Bundle-File-Name": fileName, + "X-Bundle-Content-Type": contentType, + "X-Bundle-Expiry-Timestamp": fmt.Sprintf("%d", time.Now().Add(bundleExpiredTime).Unix()), + "X-Bundle-File-Sha256": hashInHex, + } + resp, err := c.sendRequest(c.host+pathUploadObject, "POST", headers, body.Bytes()) + if err != nil { + return err + } + defer resp.Body.Close() + bodyStr, err := ReadResponseBody(resp) + if err != nil { + return err + } + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("received non-OK response status: %s, err %s", resp.Status, bodyStr) + } + return nil +} + +func (c *BundleClient) GetBundleInfo(bucketName, bundleName string) (*modle.QueryBundleResponse, error) { + req, err := http.NewRequest("GET", c.host+fmt.Sprintf(pathGetBundleInfo, bucketName, bundleName), nil) + if err != nil { + return nil, err + } + resp, err := c.hc.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + if resp.StatusCode == http.StatusNotFound { + return nil, ErrorBundleNotExist + } + return nil, fmt.Errorf("received non-OK response status: %s", resp.Status) + } + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + bundle := &modle.QueryBundleResponse{} + return bundle, json.Unmarshal(body, bundle) +} + +func (c *BundleClient) GetObject(bucketName, bundleName, objectName string) (string, error) { + path := fmt.Sprintf(pathGetBundleObject, bucketName, bundleName, objectName) + req, err := http.NewRequest("GET", c.host+path, nil) + if err != nil { + return "", err + } + resp, err := c.hc.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + if resp.StatusCode == http.StatusNotFound { + return "", ErrorBundleObjectNotExist + } + return "", fmt.Errorf("received non-OK response status: %s", resp.Status) + } + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + return string(body), nil +} + +func (c *BundleClient) sendRequest(url, method string, headers map[string]string, body []byte) (*http.Response, error) { + req, err := http.NewRequest(method, url, bytes.NewBuffer(body)) + if err != nil { + return nil, err + } + for key, value := range headers { + req.Header.Set(key, value) + } + signature, err := c.signMessage(types.TextHash(crypto.Keccak256([]byte(types.GetCanonicalRequest(req))))) + if err != nil { + return nil, err + } + req.Header.Set(types.HTTPHeaderAuthorization, hex.EncodeToString(signature)) + return c.hc.Do(req) +} + +func ReadResponseBody(resp *http.Response) (string, error) { + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + return string(body), nil +} + +func (c *BundleClient) signMessage(message []byte) ([]byte, error) { + privateKey, err := crypto.ToECDSA(c.privKey) + if err != nil { + return nil, err + } + signature, err := crypto.Sign(message, privateKey) + if err != nil { + return nil, err + } + return signature, err +} + +func bundleDirectory(dir string) (io.ReadSeekCloser, int64, error) { + b, err := bundlesdk.NewBundle() + if err != nil { + return nil, 0, err + } + + err = filepath.Walk(dir, visit(dir, b)) + if err != nil { + return nil, 0, err + } + return b.FinalizeBundle() +} +func visit(root string, b *bundlesdk.Bundle) filepath.WalkFunc { + return func(path string, f os.FileInfo, err error) error { + if err != nil { + return err + } + if !f.IsDir() { + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + + relativePath, err := filepath.Rel(root, path) + if err != nil { + return err + } + + ext := filepath.Ext(path) + contentType := mime.TypeByExtension(ext) + + content, err := io.ReadAll(file) + if err != nil { + return err + } + + hash := sha256.Sum256(content) + + options := &bundlesdktypes.AppendObjectOptions{ + ContentType: contentType, + HashAlgo: bundlesdktypes.HashAlgo_SHA256, // Set the hash algorithm to SHA256 + Hash: hash[:], // Set the hash + } + + _, err = file.Seek(0, io.SeekStart) + if err != nil { + return err + } + + _, err = b.AppendObject(relativePath, file, options) + if err != nil { + return err + } + } + return nil + } +} + +func saveBundleToFile(bundle io.ReadSeekCloser, filePath string) error { + // Create a new file + file, err := os.Create(filePath) + if err != nil { + return err + } + defer file.Close() + + // Copy the contents of the bundle to the file + _, err = io.Copy(file, bundle) + if err != nil { + return err + } + + return nil +} diff --git a/external/eth_client.go b/external/eth_client.go new file mode 100644 index 0000000..3b3f4e0 --- /dev/null +++ b/external/eth_client.go @@ -0,0 +1,31 @@ +package external + +import ( + "time" + + "github.com/ethereum/go-ethereum/ethclient" +) + +type ETHClient struct { + Eth1Client *ethclient.Client + BeaconClient *BeaconClient + endpoint string + updatedAt time.Time +} + +func NewETHClient(rpcAddrs, beaconRPCAddrs string) *ETHClient { + ethClient, err := ethclient.Dial(rpcAddrs) + if err != nil { + panic("new eth client error") + } + beaconClient, err := NewBeaconClient(beaconRPCAddrs, time.Second*3) + if err != nil { + panic("new eth client error") + } + return ÐClient{ + Eth1Client: ethClient, + BeaconClient: beaconClient, + endpoint: rpcAddrs, + updatedAt: time.Now(), + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d06f7cc --- /dev/null +++ b/go.mod @@ -0,0 +1,110 @@ +module github.com/bnb-chain/blob-syncer + +go 1.20 + +require ( + github.com/bnb-chain/greenfield-bundle-sdk v1.1.0 + github.com/ethereum/go-ethereum v1.13.10 + github.com/go-openapi/errors v0.20.4 + github.com/go-openapi/loads v0.21.2 + github.com/go-openapi/runtime v0.26.0 + github.com/go-openapi/spec v0.20.9 + github.com/go-openapi/strfmt v0.21.7 + github.com/go-openapi/swag v0.22.4 + github.com/go-openapi/validate v0.22.1 + github.com/go-sql-driver/mysql v1.7.0 + github.com/gorilla/mux v1.8.0 + github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d + github.com/jessevdk/go-flags v1.5.0 + github.com/node-real/greenfield-bundle-service v0.0.1-beta + github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 + github.com/prometheus/client_golang v1.17.0 + github.com/prysmaticlabs/prysm/v5 v5.0.2 + github.com/spf13/pflag v1.0.5 + github.com/spf13/viper v1.15.0 + golang.org/x/net v0.21.0 + gopkg.in/natefinch/lumberjack.v2 v2.2.1 + gorm.io/driver/mysql v1.5.1 + gorm.io/gorm v1.25.5 + +) + +require ( + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/aws/aws-sdk-go v1.48.6 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bits-and-blooms/bitset v1.11.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.12.1 // indirect + github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect + github.com/deckarep/golang-set/v2 v2.5.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/ethereum/c-kzg-4844 v0.4.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-logr/logr v1.3.0 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-openapi/analysis v0.21.4 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/uuid v1.5.0 // indirect + github.com/gorilla/websocket v1.5.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/holiman/uint256 v1.2.4 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/natefinch/lumberjack v2.0.0+incompatible // indirect + github.com/oklog/ulid v1.3.1 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/prysmaticlabs/fastssz v0.0.0-20221107182844-78142813af44 // indirect + github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 // indirect + github.com/prysmaticlabs/gohashtree v0.0.4-beta // indirect + github.com/rs/cors v1.8.3 // indirect + github.com/shirou/gopsutil v3.21.11+incompatible // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/afero v1.10.0 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/subosito/gotenv v1.4.2 // indirect + github.com/supranational/blst v0.3.11 // indirect + github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e // indirect + github.com/tklauser/go-sysconf v0.3.13 // indirect + github.com/tklauser/numcpus v0.7.0 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + go.mongodb.org/mongo-driver v1.11.3 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/sync v0.5.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.16.0 // indirect + google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/grpc v1.59.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + rsc.io/tmplfunc v0.0.3 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..948d666 --- /dev/null +++ b/go.sum @@ -0,0 +1,823 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/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/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/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= +github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aws/aws-sdk-go v1.48.6 h1:hnL/TE3eRigirDLrdRE9AWE1ALZSVLAsC4wK8TGsMqk= +github.com/aws/aws-sdk-go v1.48.6/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +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/bits-and-blooms/bitset v1.11.0 h1:RMyy2mBBShArUAhfVRZJ2xyBO58KCBCtZFShw3umo6k= +github.com/bits-and-blooms/bitset v1.11.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bnb-chain/greenfield-bundle-sdk v1.1.0 h1:0BWQsV+c32wHxEEpJY9igBSBg5N1Fm3KoSLC+Yef2n0= +github.com/bnb-chain/greenfield-bundle-sdk v1.1.0/go.mod h1:NCjQp0sniAbBR5yR5pYiXpYwYd1okSIBLj+31sTpmXA= +github.com/btcsuite/btcd v0.23.3 h1:4KH/JKy9WiCd+iUS9Mu0Zp7Dnj17TGdKrg9xc/FGj24= +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/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 h1:aPEJyR4rPBvDmeyi+l/FS/VtA00IWvjeFvjen1m1l1A= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= +github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= +github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= +github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U= +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/v2 v2.5.0 h1:hn6cEZtQ0h3J8kFrHR/NrzyOoTnjgW1+FmNJzQ7y/sA= +github.com/deckarep/golang-set/v2 v2.5.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= +github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/ethereum/go-ethereum v1.13.10 h1:Ppdil79nN+Vc+mXfge0AuUgmKWuVv4eMqzoIVSdqZek= +github.com/ethereum/go-ethereum v1.13.10/go.mod h1:sc48XYQxCzH3fG9BcrXCOOgQk2JfZzNAmIKnceogzsA= +github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= +github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= +github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI= +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-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY= +github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc= +github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo= +github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.20.4 h1:unTcVm6PispJsMECE3zWgvG4xTiKda1LIR5rCRWLG6M= +github.com/go-openapi/errors v0.20.4/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g= +github.com/go-openapi/loads v0.21.2 h1:r2a/xFIYeZ4Qd2TnGpWDIQNcP80dIaZgf704za8enro= +github.com/go-openapi/loads v0.21.2/go.mod h1:Jq58Os6SSGz0rzh62ptiu8Z31I+OTHqmULx5e/gJbNw= +github.com/go-openapi/runtime v0.26.0 h1:HYOFtG00FM1UvqrcxbEJg/SwvDRvYLQKGhw2zaQjTcc= +github.com/go-openapi/runtime v0.26.0/go.mod h1:QgRGeZwrUcSHdeh4Ka9Glvo0ug1LC5WyE+EV88plZrQ= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= +github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg= +github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= +github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= +github.com/go-openapi/strfmt v0.21.7 h1:rspiXgNWgeUzhjo1YU01do6qsahtJNByjLVbPLNHb8k= +github.com/go-openapi/strfmt v0.21.7/go.mod h1:adeGTkxE44sPyLk0JV235VQAO/ZXUr8KAzYjclFs3ew= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/validate v0.22.1 h1:G+c2ub6q47kfX1sOBLwIQwzBVt8qmOAARyo/9Fqs9NU= +github.com/go-openapi/validate v0.22.1/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= +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/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.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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +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.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 h1:gDLXvp5S9izjldquuoAhDzccbskOL6tDC5jMSyx3zxE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2/go.mod h1:7pdNwVWBBHGiCxa9lAszqCJMbfTISJ7oMftp8+UGV08= +github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= +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/holiman/billy v0.0.0-20230718173358-1c7e68d277a7 h1:3JQNjnMRil1yD0IfZKHF9GxxWKDJGj8I0IqOUol//sw= +github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= +github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= +github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +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/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= +github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/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/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +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/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= +github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= +github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/node-real/greenfield-bundle-service v0.0.1-beta h1:TVwYCp10ez5dybdwtnWAhlGcXoC4kiOxYoVmKjYvJqE= +github.com/node-real/greenfield-bundle-service v0.0.1-beta/go.mod h1:ZK2pn7jvJ1ySG3uoZ+PKWdZzfwOL44mI4720oQmpd10= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +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/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prysmaticlabs/fastssz v0.0.0-20221107182844-78142813af44 h1:c3p3UzV4vFA7xaCDphnDWOjpxcadrQ26l5b+ypsvyxo= +github.com/prysmaticlabs/fastssz v0.0.0-20221107182844-78142813af44/go.mod h1:MA5zShstUwCQaE9faGHgCGvEWUbG87p4SAXINhmCkvg= +github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 h1:0tVE4tdWQK9ZpYygoV7+vS6QkDvQVySboMVEIxBJmXw= +github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7/go.mod h1:wmuf/mdK4VMD+jA9ThwcUKjg3a2XWM9cVfFYjDyY4j4= +github.com/prysmaticlabs/gohashtree v0.0.4-beta h1:H/EbCuXPeTV3lpKeXGPpEV9gsUpkqOOVnWapUyeWro4= +github.com/prysmaticlabs/gohashtree v0.0.4-beta/go.mod h1:BFdtALS+Ffhg3lGQIHv9HDWuHS8cTvHZzrHWxwOtGOs= +github.com/prysmaticlabs/prysm/v5 v5.0.2 h1:xcSUvrCVfOGslKYUb5Hpyz98N9I8fC2p7DMAZfiqEIA= +github.com/prysmaticlabs/prysm/v5 v5.0.2/go.mod h1:XG4nOU925zemOimoexcrFP4oA57f+RTQbp7V/TH9UOM= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +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/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= +github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +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/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +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.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= +github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= +github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= +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/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +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/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= +github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= +github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e h1:cR8/SYRgyQCt5cNCMniB/ZScMkhI9nk8U5C7SbISXjo= +github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e/go.mod h1:Tu4lItkATkonrYuvtVjG0/rhy15qrNGNTjPdaphtZ/8= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= +github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= +github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= +github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= +github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= +github.com/urfave/cli/v2 v2.26.0 h1:3f3AMg3HpThFNT4I++TKOejZO8yU55t3JnnSr4S4QEI= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= +go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= +go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= +go.mongodb.org/mongo-driver v1.11.3 h1:Ql6K6qYHEzB6xvu4+AU0BoRoqf9vFPcc4o7MUIdPW8Y= +go.mongodb.org/mongo-driver v1.11.3/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= +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= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +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-20231214170342-aacd6d4b4611 h1:qCEDpW1G+vcj3Y7Fy52pEM1AWm3abj8WimGYejI3SC4= +golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +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/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.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-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-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +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-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-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-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-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-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= +golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/xerrors v0.0.0-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= +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/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/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-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-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-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= +google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= +google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a h1:myvhA4is3vrit1a6NZCWBIwN0kNEnX21DJOJX/NvIfI= +google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:SUBoKXbI1Efip18FClrQVGjWcyd0QZd8KkvdP34t7ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.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.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +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.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +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-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +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/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw= +gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o= +gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +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= +k8s.io/apimachinery v0.20.0 h1:jjzbTJRXk0unNS71L7h3lxGDH/2HPxMPaQY+MjECKL8= +k8s.io/klog/v2 v2.80.0 h1:lyJt0TWMPaGoODa8B8bUuxgHS3W/m/bNr2cca3brA/g= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +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 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= diff --git a/logging/log.go b/logging/log.go new file mode 100644 index 0000000..9bbac2b --- /dev/null +++ b/logging/log.go @@ -0,0 +1,55 @@ +package logging + +import ( + "os" + + "github.com/op/go-logging" + "gopkg.in/natefinch/lumberjack.v2" + + "github.com/bnb-chain/blob-syncer/config" +) + +var ( + // Logger instance for quick declarative logging levels + Logger = logging.MustGetLogger("blob-syncer") + // log levels that are available + levels = map[string]logging.Level{ + "CRITICAL": logging.CRITICAL, + "ERROR": logging.ERROR, + "WARNING": logging.WARNING, + "NOTICE": logging.NOTICE, + "INFO": logging.INFO, + "DEBUG": logging.DEBUG, + } +) + +// InitLogger initialises the logger. +func InitLogger(config *config.LogConfig) { + backends := make([]logging.Backend, 0) + + if config.UseConsoleLogger { + consoleFormat := logging.MustStringFormatter(`%{time:2006-01-02 15:04:05} %{level} %{shortfunc} %{message}`) + consoleLogger := logging.NewLogBackend(os.Stdout, "", 0) + consoleFormatter := logging.NewBackendFormatter(consoleLogger, consoleFormat) + consoleLoggerLeveled := logging.AddModuleLevel(consoleFormatter) + consoleLoggerLeveled.SetLevel(levels[config.Level], "") + backends = append(backends, consoleLoggerLeveled) + } + + if config.UseFileLogger { + fileLogger := logging.NewLogBackend(&lumberjack.Logger{ + Filename: config.Filename, + MaxSize: config.MaxFileSizeInMB, // MaxSize is the maximum size in megabytes of the log file + MaxBackups: config.MaxBackupsOfLogFiles, // MaxBackups is the maximum number of old log files to retain + MaxAge: config.MaxAgeToRetainLogFilesInDays, // MaxAge is the maximum number of days to retain old log files + Compress: config.Compress, + }, "", 0) + fileFormat := logging.MustStringFormatter(`%{time:2006-01-02 15:04:05} %{level} %{shortfunc} %{message}`) + fileFormatter := logging.NewBackendFormatter(fileLogger, fileFormat) + fileLoggerLeveled := logging.AddModuleLevel(fileFormatter) + fileLoggerLeveled.SetLevel(levels[config.Level], "") + backends = append(backends, fileLoggerLeveled) + } + + logging.SetBackend(backends...) +} diff --git a/metrics/metric.go b/metrics/metric.go new file mode 100644 index 0000000..b935460 --- /dev/null +++ b/metrics/metric.go @@ -0,0 +1,61 @@ +package metrics + +import ( + "net/http" + + "github.com/gorilla/mux" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + + "github.com/bnb-chain/blob-syncer/logging" +) + +var ( + SyncedSlotGauge = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "synced_beacon_slot", + Help: "Synced slot number, all blobs have been uploaded to bundle service.", + }) + + VerifiedSlotGauge = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "verified_beacon_slot", + Help: "Verified slot number, all blobs have been verified against the bundle service.", + }) + + MetricsItems = []prometheus.Collector{ + SyncedSlotGauge, + VerifiedSlotGauge, + } +) + +const DefaultMetricsAddress = "localhost:9090" + +type Metrics struct { + httpAddress string + registry *prometheus.Registry + httpServer *http.Server +} + +func NewMetrics(address string) *Metrics { + return &Metrics{ + httpAddress: address, + registry: prometheus.NewRegistry(), + } +} + +func (m *Metrics) Start() { + m.registry.MustRegister(MetricsItems...) + go m.serve() +} + +func (m *Metrics) serve() { + router := mux.NewRouter() + router.Path("/metrics").Handler(promhttp.HandlerFor(m.registry, promhttp.HandlerOpts{})) + m.httpServer = &http.Server{ + Addr: m.httpAddress, + Handler: router, + } + if err := m.httpServer.ListenAndServe(); err != nil { + logging.Logger.Errorf("failed to listen and serve", "error", err) + panic(err) + } +} diff --git a/models/error.go b/models/error.go new file mode 100644 index 0000000..f4c6fee --- /dev/null +++ b/models/error.go @@ -0,0 +1,55 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// Error error +// +// swagger:model Error +type Error struct { + + // HTTP error code + // Example: 400/500 + Code int64 `json:"code"` + + // Error message + // Example: Bad request/Internal server error + Message string `json:"message"` +} + +// Validate validates this error +func (m *Error) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this error based on context it is used +func (m *Error) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *Error) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *Error) UnmarshalBinary(b []byte) error { + var res Error + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/models/get_blob_side_cars_response.go b/models/get_blob_side_cars_response.go new file mode 100644 index 0000000..0581929 --- /dev/null +++ b/models/get_blob_side_cars_response.go @@ -0,0 +1,124 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// GetBlobSideCarsResponse get blob side cars response +// +// swagger:model GetBlobSideCarsResponse +type GetBlobSideCarsResponse struct { + + // status code + // Example: 200 + Code int64 `json:"code,omitempty"` + + // actual data for request + Data []*Sidecar `json:"data"` + + // error message if there is error + // Example: signature invalid + Message string `json:"message,omitempty"` +} + +// Validate validates this get blob side cars response +func (m *GetBlobSideCarsResponse) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateData(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *GetBlobSideCarsResponse) validateData(formats strfmt.Registry) error { + if swag.IsZero(m.Data) { // not required + return nil + } + + for i := 0; i < len(m.Data); i++ { + if swag.IsZero(m.Data[i]) { // not required + continue + } + + if m.Data[i] != nil { + if err := m.Data[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("data" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("data" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// ContextValidate validate this get blob side cars response based on the context it is used +func (m *GetBlobSideCarsResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateData(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *GetBlobSideCarsResponse) contextValidateData(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Data); i++ { + + if m.Data[i] != nil { + if err := m.Data[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("data" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("data" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// MarshalBinary interface implementation +func (m *GetBlobSideCarsResponse) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *GetBlobSideCarsResponse) UnmarshalBinary(b []byte) error { + var res GetBlobSideCarsResponse + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/models/sidecar.go b/models/sidecar.go new file mode 100644 index 0000000..788d69e --- /dev/null +++ b/models/sidecar.go @@ -0,0 +1,262 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// Sidecar sidecar +// +// swagger:model Sidecar +type Sidecar struct { + + // blob + Blob string `json:"blob,omitempty"` + + // index + // Example: 1 + Index string `json:"index,omitempty"` + + // kzg commitment + KzgCommitment string `json:"kzg_commitment,omitempty"` + + // kzg commitment inclusion proof + KzgCommitmentInclusionProof []string `json:"kzg_commitment_inclusion_proof"` + + // kzg proof + KzgProof string `json:"kzg_proof,omitempty"` + + // signed block header + SignedBlockHeader *SidecarSignedBlockHeader `json:"signed_block_header,omitempty"` +} + +// Validate validates this sidecar +func (m *Sidecar) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateSignedBlockHeader(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *Sidecar) validateSignedBlockHeader(formats strfmt.Registry) error { + if swag.IsZero(m.SignedBlockHeader) { // not required + return nil + } + + if m.SignedBlockHeader != nil { + if err := m.SignedBlockHeader.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("signed_block_header") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("signed_block_header") + } + return err + } + } + + return nil +} + +// ContextValidate validate this sidecar based on the context it is used +func (m *Sidecar) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateSignedBlockHeader(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *Sidecar) contextValidateSignedBlockHeader(ctx context.Context, formats strfmt.Registry) error { + + if m.SignedBlockHeader != nil { + if err := m.SignedBlockHeader.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("signed_block_header") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("signed_block_header") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *Sidecar) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *Sidecar) UnmarshalBinary(b []byte) error { + var res Sidecar + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// SidecarSignedBlockHeader sidecar signed block header +// +// swagger:model SidecarSignedBlockHeader +type SidecarSignedBlockHeader struct { + + // message + Message *SidecarSignedBlockHeaderMessage `json:"message,omitempty"` + + // signature + Signature string `json:"signature,omitempty"` +} + +// Validate validates this sidecar signed block header +func (m *SidecarSignedBlockHeader) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateMessage(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *SidecarSignedBlockHeader) validateMessage(formats strfmt.Registry) error { + if swag.IsZero(m.Message) { // not required + return nil + } + + if m.Message != nil { + if err := m.Message.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("signed_block_header" + "." + "message") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("signed_block_header" + "." + "message") + } + return err + } + } + + return nil +} + +// ContextValidate validate this sidecar signed block header based on the context it is used +func (m *SidecarSignedBlockHeader) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateMessage(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *SidecarSignedBlockHeader) contextValidateMessage(ctx context.Context, formats strfmt.Registry) error { + + if m.Message != nil { + if err := m.Message.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("signed_block_header" + "." + "message") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("signed_block_header" + "." + "message") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *SidecarSignedBlockHeader) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *SidecarSignedBlockHeader) UnmarshalBinary(b []byte) error { + var res SidecarSignedBlockHeader + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// SidecarSignedBlockHeaderMessage sidecar signed block header message +// +// swagger:model SidecarSignedBlockHeaderMessage +type SidecarSignedBlockHeaderMessage struct { + + // body root + BodyRoot string `json:"body_root,omitempty"` + + // parent root + ParentRoot string `json:"parent_root,omitempty"` + + // proposer index + ProposerIndex string `json:"proposer_index,omitempty"` + + // slot + Slot string `json:"slot,omitempty"` + + // state root + StateRoot string `json:"state_root,omitempty"` +} + +// Validate validates this sidecar signed block header message +func (m *SidecarSignedBlockHeaderMessage) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this sidecar signed block header message based on context it is used +func (m *SidecarSignedBlockHeaderMessage) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *SidecarSignedBlockHeaderMessage) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *SidecarSignedBlockHeaderMessage) UnmarshalBinary(b []byte) error { + var res SidecarSignedBlockHeaderMessage + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/restapi/configure_blob_syncer.go b/restapi/configure_blob_syncer.go new file mode 100644 index 0000000..4c5bd76 --- /dev/null +++ b/restapi/configure_blob_syncer.go @@ -0,0 +1,117 @@ +// This file is safe to edit. Once it exists it will not be overwritten + +package restapi + +import ( + "crypto/tls" + "net/http" + "os" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/swag" + + "github.com/bnb-chain/blob-syncer/cache" + "github.com/bnb-chain/blob-syncer/config" + syncerdb "github.com/bnb-chain/blob-syncer/db" + "github.com/bnb-chain/blob-syncer/external" + "github.com/bnb-chain/blob-syncer/restapi/handlers" + "github.com/bnb-chain/blob-syncer/restapi/operations" + "github.com/bnb-chain/blob-syncer/restapi/operations/blob" + "github.com/bnb-chain/blob-syncer/service" +) + +//go:generate swagger generate server --target ../../blob-syncer --name BlobSyncer --spec ../swagger.yaml --principal interface{} + +var cliOpts = struct { + ConfigFilePath string `short:"c" long:"config-path" description:"Config path" default:""` +}{} + +func configureFlags(api *operations.BlobSyncerAPI) { + param := swag.CommandLineOptionsGroup{ + ShortDescription: "config", + Options: &cliOpts, + } + api.CommandLineOptionsGroups = []swag.CommandLineOptionsGroup{param} +} + +func configureAPI(api *operations.BlobSyncerAPI) http.Handler { + // configure the api here + api.ServeError = errors.ServeError + + // Set your custom logger if needed. Default one is log.Printf + // Expected interface func(string, ...interface{}) + // + // Example: + // api.Logger = log.Printf + + api.UseSwaggerUI() + // To continue using redoc as your UI, uncomment the following line + // api.UseRedoc() + + api.JSONConsumer = runtime.JSONConsumer() + + api.JSONProducer = runtime.JSONProducer() + + api.BlobGetBlobSidecarsByBlockNumHandler = blob.GetBlobSidecarsByBlockNumHandlerFunc(handlers.HandleGetBlobSidecars()) + api.PreServerShutdown = func() {} + + api.ServerShutdown = func() {} + + return setupGlobalMiddleware(api.Serve(setupMiddlewares)) +} + +// The TLS configuration before HTTPS server starts. +func configureTLS(tlsConfig *tls.Config) { + // Make all necessary changes to the TLS configuration here. +} + +// As soon as server is initialized but not run yet, this function will be called. +// If you need to modify a config, store server instance to stop it individually later, this is the place. +// This function can be called multiple times, depending on the number of serving schemes. +// scheme value will be set accordingly: "http", "https" or "unix". +func configureServer(s *http.Server, scheme, addr string) { + var ( + cfg *config.ServerConfig + cacheSvc cache.Cache + err error + ) + configFilePath := cliOpts.ConfigFilePath + if configFilePath == "" { + configFilePath = os.Getenv(config.EnvVarConfigFilePath) + } + cfg = config.ParseServerConfigFromFile(configFilePath) + if cfg == nil { + panic("failed to get configuration") + } + cfg.Validate() + db := config.InitDBWithConfig(&cfg.DBConfig, false) + blobDB := syncerdb.NewBlobSvcDB(db) + bundleClient, err := external.NewBundleClient(cfg.BundleServiceEndpoints[0]) + if err != nil { + panic(err) + } + + switch cfg.CacheConfig.CacheType { + case "local": + cacheSvc, err = cache.NewLocalCache(cfg.CacheConfig.GetCacheSize()) + if err != nil { + panic(err) + } + default: + panic("currently only local cache is support.") + } + service.BlobSvc = service.NewBlobService(blobDB, bundleClient, cacheSvc, cfg) +} + +// The middleware configuration is for the handler executors. These do not apply to the swagger.json document. +// The middleware executes after routing but before authentication, binding and validation. +func setupMiddlewares(handler http.Handler) http.Handler { + return handler +} + +// The middleware configuration happens before anything, this middleware also applies to serving the swagger.json document. +// So this is a good place to plug in a panic handling middleware, logging and metrics. +func setupGlobalMiddleware(handler http.Handler) http.Handler { + return handler +} diff --git a/restapi/doc.go b/restapi/doc.go new file mode 100644 index 0000000..1b86217 --- /dev/null +++ b/restapi/doc.go @@ -0,0 +1,19 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// Package restapi Blob Syncer Service API +// +// API for handling blob query in the Blob Syncer. +// Schemes: +// http +// Host: blob-syncer +// BasePath: /eth/v1 +// Version: 1.0.0 +// +// Consumes: +// - application/json +// +// Produces: +// - application/json +// +// swagger:meta +package restapi diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go new file mode 100644 index 0000000..1771760 --- /dev/null +++ b/restapi/embedded_spec.go @@ -0,0 +1,400 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package restapi + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "encoding/json" +) + +var ( + // SwaggerJSON embedded version of the swagger document used at generation time + SwaggerJSON json.RawMessage + // FlatSwaggerJSON embedded flattened version of the swagger document used at generation time + FlatSwaggerJSON json.RawMessage +) + +func init() { + SwaggerJSON = json.RawMessage([]byte(`{ + "schemes": [ + "http" + ], + "swagger": "2.0", + "info": { + "description": "API for handling blob query in the Blob Syncer.", + "title": "Blob Syncer Service API", + "version": "1.0.0" + }, + "host": "blob-syncer", + "basePath": "/eth/v1", + "paths": { + "/beacon/blob_sidecars/{block_id}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "blob" + ], + "summary": "Get blob sidecars by block num", + "operationId": "getBlobSidecarsByBlockNum", + "parameters": [ + { + "minLength": 1, + "type": "string", + "description": "Block identifier. Can be one of: 'head' (canonical head in node's view), 'genesis', 'finalized', \u003cslot\u003e, \u003chex encoded blockRoot with 0x prefix\u003e", + "name": "block_id", + "in": "path", + "required": true + }, + { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of indices for blob sidecars to request for in the specified block. Returns all blob sidecars in the block if not specified", + "name": "indices", + "in": "query" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/GetBlobSideCarsResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "404": { + "description": "blob not found", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "500": { + "description": "internal server error", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } + } + }, + "definitions": { + "Error": { + "type": "object", + "properties": { + "code": { + "description": "HTTP error code", + "type": "integer", + "format": "int64", + "x-omitempty": false, + "example": "400/500" + }, + "message": { + "description": "Error message", + "type": "string", + "x-omitempty": false, + "example": "Bad request/Internal server error" + } + } + }, + "GetBlobSideCarsResponse": { + "type": "object", + "properties": { + "code": { + "description": "status code", + "type": "integer", + "example": 200 + }, + "data": { + "description": "actual data for request", + "type": "array", + "items": { + "$ref": "#/definitions/Sidecar" + } + }, + "message": { + "description": "error message if there is error", + "type": "string", + "example": "signature invalid" + } + } + }, + "Sidecar": { + "type": "object", + "properties": { + "blob": { + "type": "string" + }, + "index": { + "type": "string", + "example": 1 + }, + "kzg_commitment": { + "type": "string" + }, + "kzg_commitment_inclusion_proof": { + "type": "array", + "items": { + "type": "string" + } + }, + "kzg_proof": { + "type": "string" + }, + "signed_block_header": { + "type": "object", + "properties": { + "message": { + "type": "object", + "properties": { + "body_root": { + "type": "string" + }, + "parent_root": { + "type": "string" + }, + "proposer_index": { + "type": "string" + }, + "slot": { + "type": "string" + }, + "state_root": { + "type": "string" + } + } + }, + "signature": { + "type": "string" + } + } + } + } + } + } +}`)) + FlatSwaggerJSON = json.RawMessage([]byte(`{ + "schemes": [ + "http" + ], + "swagger": "2.0", + "info": { + "description": "API for handling blob query in the Blob Syncer.", + "title": "Blob Syncer Service API", + "version": "1.0.0" + }, + "host": "blob-syncer", + "basePath": "/eth/v1", + "paths": { + "/beacon/blob_sidecars/{block_id}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "blob" + ], + "summary": "Get blob sidecars by block num", + "operationId": "getBlobSidecarsByBlockNum", + "parameters": [ + { + "minLength": 1, + "type": "string", + "description": "Block identifier. Can be one of: 'head' (canonical head in node's view), 'genesis', 'finalized', \u003cslot\u003e, \u003chex encoded blockRoot with 0x prefix\u003e", + "name": "block_id", + "in": "path", + "required": true + }, + { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of indices for blob sidecars to request for in the specified block. Returns all blob sidecars in the block if not specified", + "name": "indices", + "in": "query" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/GetBlobSideCarsResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "404": { + "description": "blob not found", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "500": { + "description": "internal server error", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } + } + }, + "definitions": { + "Error": { + "type": "object", + "properties": { + "code": { + "description": "HTTP error code", + "type": "integer", + "format": "int64", + "x-omitempty": false, + "example": "400/500" + }, + "message": { + "description": "Error message", + "type": "string", + "x-omitempty": false, + "example": "Bad request/Internal server error" + } + } + }, + "GetBlobSideCarsResponse": { + "type": "object", + "properties": { + "code": { + "description": "status code", + "type": "integer", + "example": 200 + }, + "data": { + "description": "actual data for request", + "type": "array", + "items": { + "$ref": "#/definitions/Sidecar" + } + }, + "message": { + "description": "error message if there is error", + "type": "string", + "example": "signature invalid" + } + } + }, + "Sidecar": { + "type": "object", + "properties": { + "blob": { + "type": "string" + }, + "index": { + "type": "string", + "example": 1 + }, + "kzg_commitment": { + "type": "string" + }, + "kzg_commitment_inclusion_proof": { + "type": "array", + "items": { + "type": "string" + } + }, + "kzg_proof": { + "type": "string" + }, + "signed_block_header": { + "type": "object", + "properties": { + "message": { + "type": "object", + "properties": { + "body_root": { + "type": "string" + }, + "parent_root": { + "type": "string" + }, + "proposer_index": { + "type": "string" + }, + "slot": { + "type": "string" + }, + "state_root": { + "type": "string" + } + } + }, + "signature": { + "type": "string" + } + } + } + } + }, + "SidecarSignedBlockHeader": { + "type": "object", + "properties": { + "message": { + "type": "object", + "properties": { + "body_root": { + "type": "string" + }, + "parent_root": { + "type": "string" + }, + "proposer_index": { + "type": "string" + }, + "slot": { + "type": "string" + }, + "state_root": { + "type": "string" + } + } + }, + "signature": { + "type": "string" + } + } + }, + "SidecarSignedBlockHeaderMessage": { + "type": "object", + "properties": { + "body_root": { + "type": "string" + }, + "parent_root": { + "type": "string" + }, + "proposer_index": { + "type": "string" + }, + "slot": { + "type": "string" + }, + "state_root": { + "type": "string" + } + } + } + } +}`)) +} diff --git a/restapi/handlers/blob.go b/restapi/handlers/blob.go new file mode 100644 index 0000000..b8d1e6a --- /dev/null +++ b/restapi/handlers/blob.go @@ -0,0 +1,67 @@ +package handlers + +import ( + "encoding/hex" + "fmt" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/go-openapi/runtime/middleware" + + "github.com/bnb-chain/blob-syncer/models" + "github.com/bnb-chain/blob-syncer/restapi/operations/blob" + "github.com/bnb-chain/blob-syncer/service" + "github.com/bnb-chain/blob-syncer/util" +) + +const rootLength = 32 + +func HandleGetBlobSidecars() func(params blob.GetBlobSidecarsByBlockNumParams) middleware.Responder { + return func(params blob.GetBlobSidecarsByBlockNumParams) middleware.Responder { + + blockID := params.BlockID + indices := params.Indices + var root []byte + switch blockID { + case "genesis", "finalized": + return blob.NewGetBlobSidecarsByBlockNumBadRequest().WithPayload(service.BadRequestWithError(fmt.Errorf("block identifier not supported, only and "))) + default: + var ( + err error + sidecars []*models.Sidecar + ) + + indicesInx := make([]int64, 0) + for _, idx := range indices { + i, err := util.StringToInt64(idx) + if err != nil { + return blob.NewGetBlobSidecarsByBlockNumBadRequest().WithPayload(service.BadRequestWithError(err)) + } + indicesInx = append(indicesInx, i) + } + + root, err = hexutil.Decode(blockID) + if err == nil { + if len(root) != rootLength { + return blob.NewGetBlobSidecarsByBlockNumBadRequest().WithPayload(service.BadRequestWithError(fmt.Errorf("invalid block root of length %d", len(root)))) + } + sidecars, err = service.BlobSvc.GetBlobSidecarsByRoot(hex.EncodeToString(root), indicesInx) + if err != nil { + return blob.NewGetBlobSidecarsByBlockNumInternalServerError().WithPayload(service.InternalErrorWithError(err)) + } + } else { + slot, err := util.StringToUint64(blockID) + if err != nil { + return blob.NewGetBlobSidecarsByBlockNumBadRequest().WithPayload(service.BadRequestWithError(err)) + } + sidecars, err = service.BlobSvc.GetBlobSidecarsBySlot(slot, indicesInx) + if err != nil { + return blob.NewGetBlobSidecarsByBlockNumInternalServerError().WithPayload(service.InternalErrorWithError(err)) + } + } + payload := models.GetBlobSideCarsResponse{ + Data: sidecars, + } + return blob.NewGetBlobSidecarsByBlockNumOK().WithPayload(&payload) + } + } +} diff --git a/restapi/operations/blob/get_blob_sidecars_by_block_num.go b/restapi/operations/blob/get_blob_sidecars_by_block_num.go new file mode 100644 index 0000000..40ca57d --- /dev/null +++ b/restapi/operations/blob/get_blob_sidecars_by_block_num.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package blob + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetBlobSidecarsByBlockNumHandlerFunc turns a function with the right signature into a get blob sidecars by block num handler +type GetBlobSidecarsByBlockNumHandlerFunc func(GetBlobSidecarsByBlockNumParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetBlobSidecarsByBlockNumHandlerFunc) Handle(params GetBlobSidecarsByBlockNumParams) middleware.Responder { + return fn(params) +} + +// GetBlobSidecarsByBlockNumHandler interface for that can handle valid get blob sidecars by block num params +type GetBlobSidecarsByBlockNumHandler interface { + Handle(GetBlobSidecarsByBlockNumParams) middleware.Responder +} + +// NewGetBlobSidecarsByBlockNum creates a new http.Handler for the get blob sidecars by block num operation +func NewGetBlobSidecarsByBlockNum(ctx *middleware.Context, handler GetBlobSidecarsByBlockNumHandler) *GetBlobSidecarsByBlockNum { + return &GetBlobSidecarsByBlockNum{Context: ctx, Handler: handler} +} + +/* + GetBlobSidecarsByBlockNum swagger:route GET /beacon/blob_sidecars/{block_id} blob getBlobSidecarsByBlockNum + +Get blob sidecars by block num +*/ +type GetBlobSidecarsByBlockNum struct { + Context *middleware.Context + Handler GetBlobSidecarsByBlockNumHandler +} + +func (o *GetBlobSidecarsByBlockNum) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetBlobSidecarsByBlockNumParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/restapi/operations/blob/get_blob_sidecars_by_block_num_parameters.go b/restapi/operations/blob/get_blob_sidecars_by_block_num_parameters.go new file mode 100644 index 0000000..1d1d4f1 --- /dev/null +++ b/restapi/operations/blob/get_blob_sidecars_by_block_num_parameters.go @@ -0,0 +1,127 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package blob + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// NewGetBlobSidecarsByBlockNumParams creates a new GetBlobSidecarsByBlockNumParams object +// +// There are no default values defined in the spec. +func NewGetBlobSidecarsByBlockNumParams() GetBlobSidecarsByBlockNumParams { + + return GetBlobSidecarsByBlockNumParams{} +} + +// GetBlobSidecarsByBlockNumParams contains all the bound params for the get blob sidecars by block num operation +// typically these are obtained from a http.Request +// +// swagger:parameters getBlobSidecarsByBlockNum +type GetBlobSidecarsByBlockNumParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*Block identifier. Can be one of: 'head' (canonical head in node's view), 'genesis', 'finalized', , + Required: true + Min Length: 1 + In: path + */ + BlockID string + /*Array of indices for blob sidecars to request for in the specified block. Returns all blob sidecars in the block if not specified + In: query + */ + Indices []string +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetBlobSidecarsByBlockNumParams() beforehand. +func (o *GetBlobSidecarsByBlockNumParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + rBlockID, rhkBlockID, _ := route.Params.GetOK("block_id") + if err := o.bindBlockID(rBlockID, rhkBlockID, route.Formats); err != nil { + res = append(res, err) + } + + qIndices, qhkIndices, _ := qs.GetOK("indices") + if err := o.bindIndices(qIndices, qhkIndices, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindBlockID binds and validates parameter BlockID from path. +func (o *GetBlobSidecarsByBlockNumParams) bindBlockID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + o.BlockID = raw + + if err := o.validateBlockID(formats); err != nil { + return err + } + + return nil +} + +// validateBlockID carries on validations for parameter BlockID +func (o *GetBlobSidecarsByBlockNumParams) validateBlockID(formats strfmt.Registry) error { + + if err := validate.MinLength("block_id", "path", o.BlockID, 1); err != nil { + return err + } + + return nil +} + +// bindIndices binds and validates array parameter Indices from query. +// +// Arrays are parsed according to CollectionFormat: "" (defaults to "csv" when empty). +func (o *GetBlobSidecarsByBlockNumParams) bindIndices(rawData []string, hasKey bool, formats strfmt.Registry) error { + var qvIndices string + if len(rawData) > 0 { + qvIndices = rawData[len(rawData)-1] + } + + // CollectionFormat: + indicesIC := swag.SplitByFormat(qvIndices, "") + if len(indicesIC) == 0 { + return nil + } + + var indicesIR []string + for _, indicesIV := range indicesIC { + indicesI := indicesIV + + indicesIR = append(indicesIR, indicesI) + } + + o.Indices = indicesIR + + return nil +} diff --git a/restapi/operations/blob/get_blob_sidecars_by_block_num_responses.go b/restapi/operations/blob/get_blob_sidecars_by_block_num_responses.go new file mode 100644 index 0000000..45de119 --- /dev/null +++ b/restapi/operations/blob/get_blob_sidecars_by_block_num_responses.go @@ -0,0 +1,194 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package blob + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/bnb-chain/blob-syncer/models" +) + +// GetBlobSidecarsByBlockNumOKCode is the HTTP code returned for type GetBlobSidecarsByBlockNumOK +const GetBlobSidecarsByBlockNumOKCode int = 200 + +/* +GetBlobSidecarsByBlockNumOK successful operation + +swagger:response getBlobSidecarsByBlockNumOK +*/ +type GetBlobSidecarsByBlockNumOK struct { + + /* + In: Body + */ + Payload *models.GetBlobSideCarsResponse `json:"body,omitempty"` +} + +// NewGetBlobSidecarsByBlockNumOK creates GetBlobSidecarsByBlockNumOK with default headers values +func NewGetBlobSidecarsByBlockNumOK() *GetBlobSidecarsByBlockNumOK { + + return &GetBlobSidecarsByBlockNumOK{} +} + +// WithPayload adds the payload to the get blob sidecars by block num o k response +func (o *GetBlobSidecarsByBlockNumOK) WithPayload(payload *models.GetBlobSideCarsResponse) *GetBlobSidecarsByBlockNumOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get blob sidecars by block num o k response +func (o *GetBlobSidecarsByBlockNumOK) SetPayload(payload *models.GetBlobSideCarsResponse) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetBlobSidecarsByBlockNumOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetBlobSidecarsByBlockNumBadRequestCode is the HTTP code returned for type GetBlobSidecarsByBlockNumBadRequest +const GetBlobSidecarsByBlockNumBadRequestCode int = 400 + +/* +GetBlobSidecarsByBlockNumBadRequest Bad Request + +swagger:response getBlobSidecarsByBlockNumBadRequest +*/ +type GetBlobSidecarsByBlockNumBadRequest struct { + + /* + In: Body + */ + Payload *models.Error `json:"body,omitempty"` +} + +// NewGetBlobSidecarsByBlockNumBadRequest creates GetBlobSidecarsByBlockNumBadRequest with default headers values +func NewGetBlobSidecarsByBlockNumBadRequest() *GetBlobSidecarsByBlockNumBadRequest { + + return &GetBlobSidecarsByBlockNumBadRequest{} +} + +// WithPayload adds the payload to the get blob sidecars by block num bad request response +func (o *GetBlobSidecarsByBlockNumBadRequest) WithPayload(payload *models.Error) *GetBlobSidecarsByBlockNumBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get blob sidecars by block num bad request response +func (o *GetBlobSidecarsByBlockNumBadRequest) SetPayload(payload *models.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetBlobSidecarsByBlockNumBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetBlobSidecarsByBlockNumNotFoundCode is the HTTP code returned for type GetBlobSidecarsByBlockNumNotFound +const GetBlobSidecarsByBlockNumNotFoundCode int = 404 + +/* +GetBlobSidecarsByBlockNumNotFound blob not found + +swagger:response getBlobSidecarsByBlockNumNotFound +*/ +type GetBlobSidecarsByBlockNumNotFound struct { + + /* + In: Body + */ + Payload *models.Error `json:"body,omitempty"` +} + +// NewGetBlobSidecarsByBlockNumNotFound creates GetBlobSidecarsByBlockNumNotFound with default headers values +func NewGetBlobSidecarsByBlockNumNotFound() *GetBlobSidecarsByBlockNumNotFound { + + return &GetBlobSidecarsByBlockNumNotFound{} +} + +// WithPayload adds the payload to the get blob sidecars by block num not found response +func (o *GetBlobSidecarsByBlockNumNotFound) WithPayload(payload *models.Error) *GetBlobSidecarsByBlockNumNotFound { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get blob sidecars by block num not found response +func (o *GetBlobSidecarsByBlockNumNotFound) SetPayload(payload *models.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetBlobSidecarsByBlockNumNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(404) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetBlobSidecarsByBlockNumInternalServerErrorCode is the HTTP code returned for type GetBlobSidecarsByBlockNumInternalServerError +const GetBlobSidecarsByBlockNumInternalServerErrorCode int = 500 + +/* +GetBlobSidecarsByBlockNumInternalServerError internal server error + +swagger:response getBlobSidecarsByBlockNumInternalServerError +*/ +type GetBlobSidecarsByBlockNumInternalServerError struct { + + /* + In: Body + */ + Payload *models.Error `json:"body,omitempty"` +} + +// NewGetBlobSidecarsByBlockNumInternalServerError creates GetBlobSidecarsByBlockNumInternalServerError with default headers values +func NewGetBlobSidecarsByBlockNumInternalServerError() *GetBlobSidecarsByBlockNumInternalServerError { + + return &GetBlobSidecarsByBlockNumInternalServerError{} +} + +// WithPayload adds the payload to the get blob sidecars by block num internal server error response +func (o *GetBlobSidecarsByBlockNumInternalServerError) WithPayload(payload *models.Error) *GetBlobSidecarsByBlockNumInternalServerError { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get blob sidecars by block num internal server error response +func (o *GetBlobSidecarsByBlockNumInternalServerError) SetPayload(payload *models.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetBlobSidecarsByBlockNumInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(500) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/restapi/operations/blob/get_blob_sidecars_by_block_num_urlbuilder.go b/restapi/operations/blob/get_blob_sidecars_by_block_num_urlbuilder.go new file mode 100644 index 0000000..037eabe --- /dev/null +++ b/restapi/operations/blob/get_blob_sidecars_by_block_num_urlbuilder.go @@ -0,0 +1,124 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package blob + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/swag" +) + +// GetBlobSidecarsByBlockNumURL generates an URL for the get blob sidecars by block num operation +type GetBlobSidecarsByBlockNumURL struct { + BlockID string + + Indices []string + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetBlobSidecarsByBlockNumURL) WithBasePath(bp string) *GetBlobSidecarsByBlockNumURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetBlobSidecarsByBlockNumURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetBlobSidecarsByBlockNumURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/beacon/blob_sidecars/{block_id}" + + blockID := o.BlockID + if blockID != "" { + _path = strings.Replace(_path, "{block_id}", blockID, -1) + } else { + return nil, errors.New("blockId is required on GetBlobSidecarsByBlockNumURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/eth/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + var indicesIR []string + for _, indicesI := range o.Indices { + indicesIS := indicesI + if indicesIS != "" { + indicesIR = append(indicesIR, indicesIS) + } + } + + indices := swag.JoinByFormat(indicesIR, "") + + if len(indices) > 0 { + qsv := indices[0] + if qsv != "" { + qs.Set("indices", qsv) + } + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetBlobSidecarsByBlockNumURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetBlobSidecarsByBlockNumURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetBlobSidecarsByBlockNumURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetBlobSidecarsByBlockNumURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetBlobSidecarsByBlockNumURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetBlobSidecarsByBlockNumURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/restapi/operations/blob_syncer_api.go b/restapi/operations/blob_syncer_api.go new file mode 100644 index 0000000..04468b4 --- /dev/null +++ b/restapi/operations/blob_syncer_api.go @@ -0,0 +1,303 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "fmt" + "net/http" + "strings" + + "github.com/go-openapi/errors" + "github.com/go-openapi/loads" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/runtime/security" + "github.com/go-openapi/spec" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + + "github.com/bnb-chain/blob-syncer/restapi/operations/blob" +) + +// NewBlobSyncerAPI creates a new BlobSyncer instance +func NewBlobSyncerAPI(spec *loads.Document) *BlobSyncerAPI { + return &BlobSyncerAPI{ + handlers: make(map[string]map[string]http.Handler), + formats: strfmt.Default, + defaultConsumes: "application/json", + defaultProduces: "application/json", + customConsumers: make(map[string]runtime.Consumer), + customProducers: make(map[string]runtime.Producer), + PreServerShutdown: func() {}, + ServerShutdown: func() {}, + spec: spec, + useSwaggerUI: false, + ServeError: errors.ServeError, + BasicAuthenticator: security.BasicAuth, + APIKeyAuthenticator: security.APIKeyAuth, + BearerAuthenticator: security.BearerAuth, + + JSONConsumer: runtime.JSONConsumer(), + + JSONProducer: runtime.JSONProducer(), + + BlobGetBlobSidecarsByBlockNumHandler: blob.GetBlobSidecarsByBlockNumHandlerFunc(func(params blob.GetBlobSidecarsByBlockNumParams) middleware.Responder { + return middleware.NotImplemented("operation blob.GetBlobSidecarsByBlockNum has not yet been implemented") + }), + } +} + +/*BlobSyncerAPI API for handling blob query in the Blob Syncer. */ +type BlobSyncerAPI struct { + spec *loads.Document + context *middleware.Context + handlers map[string]map[string]http.Handler + formats strfmt.Registry + customConsumers map[string]runtime.Consumer + customProducers map[string]runtime.Producer + defaultConsumes string + defaultProduces string + Middleware func(middleware.Builder) http.Handler + useSwaggerUI bool + + // BasicAuthenticator generates a runtime.Authenticator from the supplied basic auth function. + // It has a default implementation in the security package, however you can replace it for your particular usage. + BasicAuthenticator func(security.UserPassAuthentication) runtime.Authenticator + + // APIKeyAuthenticator generates a runtime.Authenticator from the supplied token auth function. + // It has a default implementation in the security package, however you can replace it for your particular usage. + APIKeyAuthenticator func(string, string, security.TokenAuthentication) runtime.Authenticator + + // BearerAuthenticator generates a runtime.Authenticator from the supplied bearer token auth function. + // It has a default implementation in the security package, however you can replace it for your particular usage. + BearerAuthenticator func(string, security.ScopedTokenAuthentication) runtime.Authenticator + + // JSONConsumer registers a consumer for the following mime types: + // - application/json + JSONConsumer runtime.Consumer + + // JSONProducer registers a producer for the following mime types: + // - application/json + JSONProducer runtime.Producer + + // BlobGetBlobSidecarsByBlockNumHandler sets the operation handler for the get blob sidecars by block num operation + BlobGetBlobSidecarsByBlockNumHandler blob.GetBlobSidecarsByBlockNumHandler + + // ServeError is called when an error is received, there is a default handler + // but you can set your own with this + ServeError func(http.ResponseWriter, *http.Request, error) + + // PreServerShutdown is called before the HTTP(S) server is shutdown + // This allows for custom functions to get executed before the HTTP(S) server stops accepting traffic + PreServerShutdown func() + + // ServerShutdown is called when the HTTP(S) server is shut down and done + // handling all active connections and does not accept connections any more + ServerShutdown func() + + // Custom command line argument groups with their descriptions + CommandLineOptionsGroups []swag.CommandLineOptionsGroup + + // User defined logger function. + Logger func(string, ...interface{}) +} + +// UseRedoc for documentation at /docs +func (o *BlobSyncerAPI) UseRedoc() { + o.useSwaggerUI = false +} + +// UseSwaggerUI for documentation at /docs +func (o *BlobSyncerAPI) UseSwaggerUI() { + o.useSwaggerUI = true +} + +// SetDefaultProduces sets the default produces media type +func (o *BlobSyncerAPI) SetDefaultProduces(mediaType string) { + o.defaultProduces = mediaType +} + +// SetDefaultConsumes returns the default consumes media type +func (o *BlobSyncerAPI) SetDefaultConsumes(mediaType string) { + o.defaultConsumes = mediaType +} + +// SetSpec sets a spec that will be served for the clients. +func (o *BlobSyncerAPI) SetSpec(spec *loads.Document) { + o.spec = spec +} + +// DefaultProduces returns the default produces media type +func (o *BlobSyncerAPI) DefaultProduces() string { + return o.defaultProduces +} + +// DefaultConsumes returns the default consumes media type +func (o *BlobSyncerAPI) DefaultConsumes() string { + return o.defaultConsumes +} + +// Formats returns the registered string formats +func (o *BlobSyncerAPI) Formats() strfmt.Registry { + return o.formats +} + +// RegisterFormat registers a custom format validator +func (o *BlobSyncerAPI) RegisterFormat(name string, format strfmt.Format, validator strfmt.Validator) { + o.formats.Add(name, format, validator) +} + +// Validate validates the registrations in the BlobSyncerAPI +func (o *BlobSyncerAPI) Validate() error { + var unregistered []string + + if o.JSONConsumer == nil { + unregistered = append(unregistered, "JSONConsumer") + } + + if o.JSONProducer == nil { + unregistered = append(unregistered, "JSONProducer") + } + + if o.BlobGetBlobSidecarsByBlockNumHandler == nil { + unregistered = append(unregistered, "blob.GetBlobSidecarsByBlockNumHandler") + } + + if len(unregistered) > 0 { + return fmt.Errorf("missing registration: %s", strings.Join(unregistered, ", ")) + } + + return nil +} + +// ServeErrorFor gets a error handler for a given operation id +func (o *BlobSyncerAPI) ServeErrorFor(operationID string) func(http.ResponseWriter, *http.Request, error) { + return o.ServeError +} + +// AuthenticatorsFor gets the authenticators for the specified security schemes +func (o *BlobSyncerAPI) AuthenticatorsFor(schemes map[string]spec.SecurityScheme) map[string]runtime.Authenticator { + return nil +} + +// Authorizer returns the registered authorizer +func (o *BlobSyncerAPI) Authorizer() runtime.Authorizer { + return nil +} + +// ConsumersFor gets the consumers for the specified media types. +// MIME type parameters are ignored here. +func (o *BlobSyncerAPI) ConsumersFor(mediaTypes []string) map[string]runtime.Consumer { + result := make(map[string]runtime.Consumer, len(mediaTypes)) + for _, mt := range mediaTypes { + switch mt { + case "application/json": + result["application/json"] = o.JSONConsumer + } + + if c, ok := o.customConsumers[mt]; ok { + result[mt] = c + } + } + return result +} + +// ProducersFor gets the producers for the specified media types. +// MIME type parameters are ignored here. +func (o *BlobSyncerAPI) ProducersFor(mediaTypes []string) map[string]runtime.Producer { + result := make(map[string]runtime.Producer, len(mediaTypes)) + for _, mt := range mediaTypes { + switch mt { + case "application/json": + result["application/json"] = o.JSONProducer + } + + if p, ok := o.customProducers[mt]; ok { + result[mt] = p + } + } + return result +} + +// HandlerFor gets a http.Handler for the provided operation method and path +func (o *BlobSyncerAPI) HandlerFor(method, path string) (http.Handler, bool) { + if o.handlers == nil { + return nil, false + } + um := strings.ToUpper(method) + if _, ok := o.handlers[um]; !ok { + return nil, false + } + if path == "/" { + path = "" + } + h, ok := o.handlers[um][path] + return h, ok +} + +// Context returns the middleware context for the blob syncer API +func (o *BlobSyncerAPI) Context() *middleware.Context { + if o.context == nil { + o.context = middleware.NewRoutableContext(o.spec, o, nil) + } + + return o.context +} + +func (o *BlobSyncerAPI) initHandlerCache() { + o.Context() // don't care about the result, just that the initialization happened + if o.handlers == nil { + o.handlers = make(map[string]map[string]http.Handler) + } + + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } + o.handlers["GET"]["/beacon/blob_sidecars/{block_id}"] = blob.NewGetBlobSidecarsByBlockNum(o.context, o.BlobGetBlobSidecarsByBlockNumHandler) +} + +// Serve creates a http handler to serve the API over HTTP +// can be used directly in http.ListenAndServe(":8000", api.Serve(nil)) +func (o *BlobSyncerAPI) Serve(builder middleware.Builder) http.Handler { + o.Init() + + if o.Middleware != nil { + return o.Middleware(builder) + } + if o.useSwaggerUI { + return o.context.APIHandlerSwaggerUI(builder) + } + return o.context.APIHandler(builder) +} + +// Init allows you to just initialize the handler cache, you can then recompose the middleware as you see fit +func (o *BlobSyncerAPI) Init() { + if len(o.handlers) == 0 { + o.initHandlerCache() + } +} + +// RegisterConsumer allows you to add (or override) a consumer for a media type. +func (o *BlobSyncerAPI) RegisterConsumer(mediaType string, consumer runtime.Consumer) { + o.customConsumers[mediaType] = consumer +} + +// RegisterProducer allows you to add (or override) a producer for a media type. +func (o *BlobSyncerAPI) RegisterProducer(mediaType string, producer runtime.Producer) { + o.customProducers[mediaType] = producer +} + +// AddMiddlewareFor adds a http middleware to existing handler +func (o *BlobSyncerAPI) AddMiddlewareFor(method, path string, builder middleware.Builder) { + um := strings.ToUpper(method) + if path == "/" { + path = "" + } + o.Init() + if h, ok := o.handlers[um][path]; ok { + o.handlers[method][path] = builder(h) + } +} diff --git a/restapi/server.go b/restapi/server.go new file mode 100644 index 0000000..bc93767 --- /dev/null +++ b/restapi/server.go @@ -0,0 +1,507 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package restapi + +import ( + "context" + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "log" + "net" + "net/http" + "os" + "os/signal" + "strconv" + "sync" + "sync/atomic" + "syscall" + "time" + + "github.com/go-openapi/runtime/flagext" + "github.com/go-openapi/swag" + flags "github.com/jessevdk/go-flags" + "golang.org/x/net/netutil" + + "github.com/bnb-chain/blob-syncer/restapi/operations" +) + +const ( + schemeHTTP = "http" + schemeHTTPS = "https" + schemeUnix = "unix" +) + +var defaultSchemes []string + +func init() { + defaultSchemes = []string{ + schemeHTTP, + } +} + +// NewServer creates a new api blob syncer server but does not configure it +func NewServer(api *operations.BlobSyncerAPI) *Server { + s := new(Server) + + s.shutdown = make(chan struct{}) + s.api = api + s.interrupt = make(chan os.Signal, 1) + return s +} + +// ConfigureAPI configures the API and handlers. +func (s *Server) ConfigureAPI() { + if s.api != nil { + s.handler = configureAPI(s.api) + } +} + +// ConfigureFlags configures the additional flags defined by the handlers. Needs to be called before the parser.Parse +func (s *Server) ConfigureFlags() { + if s.api != nil { + configureFlags(s.api) + } +} + +// Server for the blob syncer API +type Server struct { + EnabledListeners []string `long:"scheme" description:"the listeners to enable, this can be repeated and defaults to the schemes in the swagger spec"` + CleanupTimeout time.Duration `long:"cleanup-timeout" description:"grace period for which to wait before killing idle connections" default:"10s"` + GracefulTimeout time.Duration `long:"graceful-timeout" description:"grace period for which to wait before shutting down the server" default:"15s"` + MaxHeaderSize flagext.ByteSize `long:"max-header-size" description:"controls the maximum number of bytes the server will read parsing the request header's keys and values, including the request line. It does not limit the size of the request body." default:"1MiB"` + + SocketPath flags.Filename `long:"socket-path" description:"the unix socket to listen on" default:"/var/run/blob-syncer.sock"` + domainSocketL net.Listener + + Host string `long:"host" description:"the IP to listen on" default:"localhost" env:"HOST"` + Port int `long:"port" description:"the port to listen on for insecure connections, defaults to a random value" env:"PORT"` + ListenLimit int `long:"listen-limit" description:"limit the number of outstanding requests"` + KeepAlive time.Duration `long:"keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)" default:"3m"` + ReadTimeout time.Duration `long:"read-timeout" description:"maximum duration before timing out read of the request" default:"30s"` + WriteTimeout time.Duration `long:"write-timeout" description:"maximum duration before timing out write of the response" default:"60s"` + httpServerL net.Listener + + TLSHost string `long:"tls-host" description:"the IP to listen on for tls, when not specified it's the same as --host" env:"TLS_HOST"` + TLSPort int `long:"tls-port" description:"the port to listen on for secure connections, defaults to a random value" env:"TLS_PORT"` + TLSCertificate flags.Filename `long:"tls-certificate" description:"the certificate to use for secure connections" env:"TLS_CERTIFICATE"` + TLSCertificateKey flags.Filename `long:"tls-key" description:"the private key to use for secure connections" env:"TLS_PRIVATE_KEY"` + TLSCACertificate flags.Filename `long:"tls-ca" description:"the certificate authority file to be used with mutual tls auth" env:"TLS_CA_CERTIFICATE"` + TLSListenLimit int `long:"tls-listen-limit" description:"limit the number of outstanding requests"` + TLSKeepAlive time.Duration `long:"tls-keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)"` + TLSReadTimeout time.Duration `long:"tls-read-timeout" description:"maximum duration before timing out read of the request"` + TLSWriteTimeout time.Duration `long:"tls-write-timeout" description:"maximum duration before timing out write of the response"` + httpsServerL net.Listener + + api *operations.BlobSyncerAPI + handler http.Handler + hasListeners bool + shutdown chan struct{} + shuttingDown int32 + interrupted bool + interrupt chan os.Signal +} + +// Logf logs message either via defined user logger or via system one if no user logger is defined. +func (s *Server) Logf(f string, args ...interface{}) { + if s.api != nil && s.api.Logger != nil { + s.api.Logger(f, args...) + } else { + log.Printf(f, args...) + } +} + +// Fatalf logs message either via defined user logger or via system one if no user logger is defined. +// Exits with non-zero status after printing +func (s *Server) Fatalf(f string, args ...interface{}) { + if s.api != nil && s.api.Logger != nil { + s.api.Logger(f, args...) + os.Exit(1) + } else { + log.Fatalf(f, args...) + } +} + +// SetAPI configures the server with the specified API. Needs to be called before Serve +func (s *Server) SetAPI(api *operations.BlobSyncerAPI) { + if api == nil { + s.api = nil + s.handler = nil + return + } + + s.api = api + s.handler = configureAPI(api) +} + +func (s *Server) hasScheme(scheme string) bool { + schemes := s.EnabledListeners + if len(schemes) == 0 { + schemes = defaultSchemes + } + + for _, v := range schemes { + if v == scheme { + return true + } + } + return false +} + +// Serve the api +func (s *Server) Serve() (err error) { + if !s.hasListeners { + if err = s.Listen(); err != nil { + return err + } + } + + // set default handler, if none is set + if s.handler == nil { + if s.api == nil { + return errors.New("can't create the default handler, as no api is set") + } + + s.SetHandler(s.api.Serve(nil)) + } + + wg := new(sync.WaitGroup) + once := new(sync.Once) + signalNotify(s.interrupt) + go handleInterrupt(once, s) + + servers := []*http.Server{} + + if s.hasScheme(schemeUnix) { + domainSocket := new(http.Server) + domainSocket.MaxHeaderBytes = int(s.MaxHeaderSize) + domainSocket.Handler = s.handler + if int64(s.CleanupTimeout) > 0 { + domainSocket.IdleTimeout = s.CleanupTimeout + } + + configureServer(domainSocket, "unix", string(s.SocketPath)) + + servers = append(servers, domainSocket) + wg.Add(1) + s.Logf("Serving blob syncer at unix://%s", s.SocketPath) + go func(l net.Listener) { + defer wg.Done() + if err := domainSocket.Serve(l); err != nil && err != http.ErrServerClosed { + s.Fatalf("%v", err) + } + s.Logf("Stopped serving blob syncer at unix://%s", s.SocketPath) + }(s.domainSocketL) + } + + if s.hasScheme(schemeHTTP) { + httpServer := new(http.Server) + httpServer.MaxHeaderBytes = int(s.MaxHeaderSize) + httpServer.ReadTimeout = s.ReadTimeout + httpServer.WriteTimeout = s.WriteTimeout + httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0) + if s.ListenLimit > 0 { + s.httpServerL = netutil.LimitListener(s.httpServerL, s.ListenLimit) + } + + if int64(s.CleanupTimeout) > 0 { + httpServer.IdleTimeout = s.CleanupTimeout + } + + httpServer.Handler = s.handler + + configureServer(httpServer, "http", s.httpServerL.Addr().String()) + + servers = append(servers, httpServer) + wg.Add(1) + s.Logf("Serving blob syncer at http://%s", s.httpServerL.Addr()) + go func(l net.Listener) { + defer wg.Done() + if err := httpServer.Serve(l); err != nil && err != http.ErrServerClosed { + s.Fatalf("%v", err) + } + s.Logf("Stopped serving blob syncer at http://%s", l.Addr()) + }(s.httpServerL) + } + + if s.hasScheme(schemeHTTPS) { + httpsServer := new(http.Server) + httpsServer.MaxHeaderBytes = int(s.MaxHeaderSize) + httpsServer.ReadTimeout = s.TLSReadTimeout + httpsServer.WriteTimeout = s.TLSWriteTimeout + httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0) + if s.TLSListenLimit > 0 { + s.httpsServerL = netutil.LimitListener(s.httpsServerL, s.TLSListenLimit) + } + if int64(s.CleanupTimeout) > 0 { + httpsServer.IdleTimeout = s.CleanupTimeout + } + httpsServer.Handler = s.handler + + // Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go + httpsServer.TLSConfig = &tls.Config{ + // Causes servers to use Go's default ciphersuite preferences, + // which are tuned to avoid attacks. Does nothing on clients. + PreferServerCipherSuites: true, + // Only use curves which have assembly implementations + // https://github.com/golang/go/tree/master/src/crypto/elliptic + CurvePreferences: []tls.CurveID{tls.CurveP256}, + // Use modern tls mode https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility + NextProtos: []string{"h2", "http/1.1"}, + // https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Only_Support_Strong_Protocols + MinVersion: tls.VersionTLS12, + // These ciphersuites support Forward Secrecy: https://en.wikipedia.org/wiki/Forward_secrecy + CipherSuites: []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + }, + } + + // build standard config from server options + if s.TLSCertificate != "" && s.TLSCertificateKey != "" { + httpsServer.TLSConfig.Certificates = make([]tls.Certificate, 1) + httpsServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(string(s.TLSCertificate), string(s.TLSCertificateKey)) + if err != nil { + return err + } + } + + if s.TLSCACertificate != "" { + // include specified CA certificate + caCert, caCertErr := os.ReadFile(string(s.TLSCACertificate)) + if caCertErr != nil { + return caCertErr + } + caCertPool := x509.NewCertPool() + ok := caCertPool.AppendCertsFromPEM(caCert) + if !ok { + return fmt.Errorf("cannot parse CA certificate") + } + httpsServer.TLSConfig.ClientCAs = caCertPool + httpsServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert + } + + // call custom TLS configurator + configureTLS(httpsServer.TLSConfig) + + if len(httpsServer.TLSConfig.Certificates) == 0 && httpsServer.TLSConfig.GetCertificate == nil { + // after standard and custom config are passed, this ends up with no certificate + if s.TLSCertificate == "" { + if s.TLSCertificateKey == "" { + s.Fatalf("the required flags `--tls-certificate` and `--tls-key` were not specified") + } + s.Fatalf("the required flag `--tls-certificate` was not specified") + } + if s.TLSCertificateKey == "" { + s.Fatalf("the required flag `--tls-key` was not specified") + } + // this happens with a wrong custom TLS configurator + s.Fatalf("no certificate was configured for TLS") + } + + configureServer(httpsServer, "https", s.httpsServerL.Addr().String()) + + servers = append(servers, httpsServer) + wg.Add(1) + s.Logf("Serving blob syncer at https://%s", s.httpsServerL.Addr()) + go func(l net.Listener) { + defer wg.Done() + if err := httpsServer.Serve(l); err != nil && err != http.ErrServerClosed { + s.Fatalf("%v", err) + } + s.Logf("Stopped serving blob syncer at https://%s", l.Addr()) + }(tls.NewListener(s.httpsServerL, httpsServer.TLSConfig)) + } + + wg.Add(1) + go s.handleShutdown(wg, &servers) + + wg.Wait() + return nil +} + +// Listen creates the listeners for the server +func (s *Server) Listen() error { + if s.hasListeners { // already done this + return nil + } + + if s.hasScheme(schemeHTTPS) { + // Use http host if https host wasn't defined + if s.TLSHost == "" { + s.TLSHost = s.Host + } + // Use http listen limit if https listen limit wasn't defined + if s.TLSListenLimit == 0 { + s.TLSListenLimit = s.ListenLimit + } + // Use http tcp keep alive if https tcp keep alive wasn't defined + if int64(s.TLSKeepAlive) == 0 { + s.TLSKeepAlive = s.KeepAlive + } + // Use http read timeout if https read timeout wasn't defined + if int64(s.TLSReadTimeout) == 0 { + s.TLSReadTimeout = s.ReadTimeout + } + // Use http write timeout if https write timeout wasn't defined + if int64(s.TLSWriteTimeout) == 0 { + s.TLSWriteTimeout = s.WriteTimeout + } + } + + if s.hasScheme(schemeUnix) { + domSockListener, err := net.Listen("unix", string(s.SocketPath)) + if err != nil { + return err + } + s.domainSocketL = domSockListener + } + + if s.hasScheme(schemeHTTP) { + listener, err := net.Listen("tcp", net.JoinHostPort(s.Host, strconv.Itoa(s.Port))) + if err != nil { + return err + } + + h, p, err := swag.SplitHostPort(listener.Addr().String()) + if err != nil { + return err + } + s.Host = h + s.Port = p + s.httpServerL = listener + } + + if s.hasScheme(schemeHTTPS) { + tlsListener, err := net.Listen("tcp", net.JoinHostPort(s.TLSHost, strconv.Itoa(s.TLSPort))) + if err != nil { + return err + } + + sh, sp, err := swag.SplitHostPort(tlsListener.Addr().String()) + if err != nil { + return err + } + s.TLSHost = sh + s.TLSPort = sp + s.httpsServerL = tlsListener + } + + s.hasListeners = true + return nil +} + +// Shutdown server and clean up resources +func (s *Server) Shutdown() error { + if atomic.CompareAndSwapInt32(&s.shuttingDown, 0, 1) { + close(s.shutdown) + } + return nil +} + +func (s *Server) handleShutdown(wg *sync.WaitGroup, serversPtr *[]*http.Server) { + // wg.Done must occur last, after s.api.ServerShutdown() + // (to preserve old behaviour) + defer wg.Done() + + <-s.shutdown + + servers := *serversPtr + + ctx, cancel := context.WithTimeout(context.TODO(), s.GracefulTimeout) + defer cancel() + + // first execute the pre-shutdown hook + s.api.PreServerShutdown() + + shutdownChan := make(chan bool) + for i := range servers { + server := servers[i] + go func() { + var success bool + defer func() { + shutdownChan <- success + }() + if err := server.Shutdown(ctx); err != nil { + // Error from closing listeners, or context timeout: + s.Logf("HTTP server Shutdown: %v", err) + } else { + success = true + } + }() + } + + // Wait until all listeners have successfully shut down before calling ServerShutdown + success := true + for range servers { + success = success && <-shutdownChan + } + if success { + s.api.ServerShutdown() + } +} + +// GetHandler returns a handler useful for testing +func (s *Server) GetHandler() http.Handler { + return s.handler +} + +// SetHandler allows for setting a http handler on this server +func (s *Server) SetHandler(handler http.Handler) { + s.handler = handler +} + +// UnixListener returns the domain socket listener +func (s *Server) UnixListener() (net.Listener, error) { + if !s.hasListeners { + if err := s.Listen(); err != nil { + return nil, err + } + } + return s.domainSocketL, nil +} + +// HTTPListener returns the http listener +func (s *Server) HTTPListener() (net.Listener, error) { + if !s.hasListeners { + if err := s.Listen(); err != nil { + return nil, err + } + } + return s.httpServerL, nil +} + +// TLSListener returns the https listener +func (s *Server) TLSListener() (net.Listener, error) { + if !s.hasListeners { + if err := s.Listen(); err != nil { + return nil, err + } + } + return s.httpsServerL, nil +} + +func handleInterrupt(once *sync.Once, s *Server) { + once.Do(func() { + for range s.interrupt { + if s.interrupted { + s.Logf("Server already shutting down") + continue + } + s.interrupted = true + s.Logf("Shutting down... ") + if err := s.Shutdown(); err != nil { + s.Logf("HTTP server Shutdown: %v", err) + } + } + }) +} + +func signalNotify(interrupt chan<- os.Signal) { + signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) +} diff --git a/scripts/.env b/scripts/.env new file mode 100644 index 0000000..c520202 --- /dev/null +++ b/scripts/.env @@ -0,0 +1,7 @@ +PRIVATE_KEY=.... +BUCKET_NAME=eth-blob +GRANTEE_BUNDLE_ACCOUNT=0x.... +GREENFIELD_RPC=https://gnfd-testnet-fullnode-tendermint-us.bnbchain.org:443 +GREENFIELD_CHAIN_ID=greenfield_5600-1 +ALLOWANCE=10000000000000000000 +SP_ADDRESS=0x5fff5a6c94b182fb965b40c7b9f30199b969ed2f \ No newline at end of file diff --git a/scripts/create_key.sh b/scripts/create_key.sh new file mode 100644 index 0000000..c838a45 --- /dev/null +++ b/scripts/create_key.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +basedir=$(cd `dirname $0`; pwd) +WORKSPACE=${basedir} +echo ${WORKSPACE} + +cd ${WORKSPACE} +git clone https://github.com/bnb-chain/greenfield-cmd.git +cd greenfield-cmd +git checkout master +make build +cd build + +./gnfd-cmd account new + +./gnfd-cmd account export --unarmoredHex --unsafe diff --git a/scripts/format.sh b/scripts/format.sh new file mode 100644 index 0000000..7c7bf59 --- /dev/null +++ b/scripts/format.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +which goimportssort || go install github.com/AanZee/goimportssort + +for entry in `find . -name "*.go" | grep -v '.pb.'`; do + echo $entry + if grep -q "DO NOT EDIT" "$entry"; then + continue + fi + goimportssort -w -local github.com/bnb-chain/ $entry +done diff --git a/scripts/set_up.sh b/scripts/set_up.sh new file mode 100644 index 0000000..ab6ff9f --- /dev/null +++ b/scripts/set_up.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash + +basedir=$(cd `dirname $0`; pwd) +WORKSPACE=${basedir} +source ${WORKSPACE}/.env +echo ${WORKSPACE} + +function prepare() { + cd ${WORKSPACE} + git clone https://github.com/bnb-chain/greenfield-cmd.git + cd greenfield-cmd + git checkout allowance + make build + cd build + + touch key.txt & echo ${PRIVATE_KEY} > key.txt + touch password.txt & echo "pass" > password.txt + ./gnfd-cmd --home ./ --passwordfile password.txt account import key.txt + + touch config.toml + { + echo rpcAddr = \""${GREENFIELD_RPC}"\" + echo chainId = \""${GREENFIELD_CHAIN_ID}"\" + } > config.toml + echo "prepared binary" +} + +function create_bucket() { + prepare + ./gnfd-cmd -c ./config.toml --home ./ --passwordfile password.txt bucket create --primarySP ${SP_ADDRESS} --chargedQuota 322122547200 gnfd://${BUCKET_NAME} + sleep 5 + ./gnfd-cmd -c ./config.toml --home ./ bucket head gnfd://${BUCKET_NAME} + echo "bucket created" +} + +function grant() { + prepare + ./gnfd-cmd -c ./config.toml --home ./ --passwordfile password.txt policy put --grantee ${GRANTEE_BUNDLE_ACCOUNT} --actions createObj grn:b::"$BUCKET_NAME" + sleep 5 + ./gnfd-cmd -c ./config.toml --home ./ --passwordfile password.txt fee grant --grantee ${GRANTEE_BUNDLE_ACCOUNT} --allowance ${ALLOWANCE} + echo "granted permission" +} + +function all() { + prepare + ./gnfd-cmd -c ./config.toml --home ./ --passwordfile password.txt bucket create --primarySP ${SP_ADDRESS} --chargedQuota 322122547200 gnfd://${BUCKET_NAME} + sleep 5 + ./gnfd-cmd -c ./config.toml --home ./ bucket head gnfd://${BUCKET_NAME} + ./gnfd-cmd -c ./config.toml --home ./ --passwordfile password.txt policy put --grantee ${GRANTEE_BUNDLE_ACCOUNT} --actions createObj grn:b::"$BUCKET_NAME" + sleep 5 + ./gnfd-cmd -c ./config.toml --home ./ --passwordfile password.txt fee grant --grantee ${GRANTEE_BUNDLE_ACCOUNT} --allowance ${ALLOWANCE} + echo "created bucket and granted permission" +} + +CMD=$1 +case ${CMD} in +--create_bucket) + create_bucket + ;; +--grant) + grant + ;; +--all) + all + ;; +esac + + diff --git a/server-distroless.dockerfile b/server-distroless.dockerfile new file mode 100644 index 0000000..18908ec --- /dev/null +++ b/server-distroless.dockerfile @@ -0,0 +1,50 @@ +FROM golang:1.20-alpine as builder + +# Set up apk dependencies +ENV PACKAGES make git libc-dev bash gcc linux-headers eudev-dev curl ca-certificates build-base + +# Set working directory for the build +WORKDIR /opt/app + +# Add source files +COPY . . + +# Install minimum necessary dependencies, remove packages +RUN apk add --no-cache $PACKAGES + +# For Private REPO +ARG GH_TOKEN="" +RUN go env -w GOPRIVATE="github.com/bnb-chain/*" +RUN git config --global url."https://${GH_TOKEN}@github.com".insteadOf "https://github.com" + +RUN make build_server + + +FROM alpine:3.17 + +ARG USER=app +ARG USER_UID=1000 +ARG USER_GID=1000 + +ENV PACKAGES ca-certificates libstdc++ curl +ENV WORKDIR=/app + +RUN apk add --no-cache $PACKAGES \ + && rm -rf /var/cache/apk/* \ + && addgroup -g ${USER_GID} ${USER} \ + && adduser -u ${USER_UID} -G ${USER} --shell /sbin/nologin --no-create-home -D ${USER} \ + && addgroup ${USER} tty \ + && sed -i -e "s/bin\/sh/bin\/bash/" /etc/passwd + +WORKDIR ${WORKDIR} +RUN chown -R ${USER_UID}:${USER_GID} ${WORKDIR} +USER ${USER_UID}:${USER_GID} + +ENV CONFIG_FILE_PATH /opt/app/config/config.json + +ENV WORKDIR=/app +WORKDIR ${WORKDIR} +COPY --from=builder /opt/app/build/blob-syncer-server ${WORKDIR} + +# Run the app +CMD /app/blob-syncer-server --host 0.0.0.0 --port 8080 --config-path "$CONFIG_FILE_PATH" \ No newline at end of file diff --git a/server.Dockerfile b/server.Dockerfile new file mode 100644 index 0000000..cacda0c --- /dev/null +++ b/server.Dockerfile @@ -0,0 +1,53 @@ +FROM golang:1.20-alpine as builder + +# Set up apk dependencies +ENV PACKAGES make git libc-dev bash gcc linux-headers eudev-dev curl ca-certificates build-base + +# Set working directory for the build +WORKDIR /opt/app + +# Add source files +COPY . . + +# Install minimum necessary dependencies, remove packages +RUN apk add --no-cache $PACKAGES + +# For Private REPO +ARG GH_TOKEN="" +RUN go env -w GOPRIVATE="github.com/bnb-chain/*" +RUN git config --global url."https://${GH_TOKEN}@github.com".insteadOf "https://github.com" + +RUN make build_server + +# Pull binary into a second stage deploy alpine container +FROM alpine:3.17 + +ARG USER=app +ARG USER_UID=1000 +ARG USER_GID=1000 + +ENV BLOB_SYNCER_SERVER_HOME /opt/app +ENV CONFIG_FILE_PATH $BLOB_SYNCER_SERVER_HOME/config/config.json +ENV DB_USERNAME "" +ENV DB_PASSWORD "" + +ENV PACKAGES ca-certificates libstdc++ +ENV WORKDIR=/app + +RUN apk add --no-cache $PACKAGES \ + && rm -rf /var/cache/apk/* \ + && addgroup -g ${USER_GID} ${USER} \ + && adduser -u ${USER_UID} -G ${USER} --shell /sbin/nologin --no-create-home -D ${USER} \ + && addgroup ${USER} tty \ + && sed -i -e "s/bin\/sh/bin\/bash/" /etc/passwd + +WORKDIR ${WORKDIR} + +COPY --from=builder /opt/app/build/blob-syncer ${WORKDIR}/ +RUN chown -R ${USER_UID}:${USER_GID} ${WORKDIR} +USER ${USER_UID}:${USER_GID} + +VOLUME [ $BLOB_SYNCER_SERVER_HOME ] + +# Run the app +CMD /app/blob-syncer-server --config-path "$CONFIG_FILE_PATH" --port 8080 \ No newline at end of file diff --git a/service/blob.go b/service/blob.go new file mode 100644 index 0000000..d0df66a --- /dev/null +++ b/service/blob.go @@ -0,0 +1,113 @@ +package service + +import ( + "fmt" + + "github.com/bnb-chain/blob-syncer/cache" + "github.com/bnb-chain/blob-syncer/config" + "github.com/bnb-chain/blob-syncer/db" + "github.com/bnb-chain/blob-syncer/external" + "github.com/bnb-chain/blob-syncer/models" + "github.com/bnb-chain/blob-syncer/util" +) + +const prefixHex = "0x" + +type Blob interface { + GetBlobSidecarsByRoot(root string, indices []int64) ([]*models.Sidecar, error) + GetBlobSidecarsBySlot(slot uint64, indices []int64) ([]*models.Sidecar, error) +} + +type BlobService struct { + blobDB db.BlobDao + bundleClient *external.BundleClient + cacheService cache.Cache + config *config.ServerConfig +} + +func NewBlobService(blobDB db.BlobDao, bundleClient *external.BundleClient, cache cache.Cache, config *config.ServerConfig) Blob { + return &BlobService{ + blobDB: blobDB, + bundleClient: bundleClient, + cacheService: cache, + config: config, + } +} + +func (b BlobService) GetBlobSidecarsBySlot(slot uint64, indices []int64) ([]*models.Sidecar, error) { + var err error + blobs, found := b.cacheService.Get(util.Uint64ToString(slot)) + if found { + blobsFound := blobs.([]*models.Sidecar) + if len(indices) != 0 { + blobReturn := make([]*models.Sidecar, 0) + for _, idx := range indices { + if int(idx) >= len(blobsFound) { + return nil, fmt.Errorf("index %d out of bound, only %d blob at slot %d", idx, len(blobsFound), slot) + } + blobReturn = append(blobReturn, blobsFound[idx]) + } + return blobReturn, nil + } + return blobsFound, nil + } + + block, err := b.blobDB.GetBlock(slot) + if err != nil { + return nil, err + } + + var blobMetas []*db.Blob + if len(indices) == 0 { + blobMetas, err = b.blobDB.GetBlobBySlot(slot) + if err != nil { + return nil, err + } + } else { + blobMetas, err = b.blobDB.GetBlobBySlotAndIndices(slot, indices) + if err != nil { + return nil, err + } + } + + sideCars := make([]*models.Sidecar, 0) + for _, meta := range blobMetas { + bundleObject, err := b.bundleClient.GetObject(b.config.BucketName, block.BundleName, meta.Name) + if err != nil { + return nil, err + } + header := &models.SidecarSignedBlockHeader{ + Message: &models.SidecarSignedBlockHeaderMessage{ + BodyRoot: fmt.Sprintf("%s%s", prefixHex, block.BodyRoot), + ParentRoot: fmt.Sprintf("%s%s", prefixHex, block.ParentRoot), + StateRoot: fmt.Sprintf("%s%s", prefixHex, block.StateRoot), + ProposerIndex: util.Uint64ToString(block.ProposerIndex), + Slot: util.Uint64ToString(block.Slot), + }, + Signature: fmt.Sprintf("%s%s", prefixHex, block.Signature), + } + sideCars = append(sideCars, + &models.Sidecar{ + Blob: bundleObject, + Index: util.Int64ToString(int64(meta.Idx)), + KzgCommitmentInclusionProof: util.SplitByComma(meta.CommitmentInclusionProof), + KzgCommitment: meta.KzgCommitment, + KzgProof: meta.KzgProof, + SignedBlockHeader: header, + }) + } + + // cache all blobs at a specified slot + if len(indices) == 0 { + b.cacheService.Set(util.Uint64ToString(slot), sideCars) + } + return sideCars, nil +} + +func (b BlobService) GetBlobSidecarsByRoot(root string, indices []int64) ([]*models.Sidecar, error) { + block, err := b.blobDB.GetBlockByRoot(root) + if err != nil { + return nil, err + } + return b.GetBlobSidecarsBySlot(block.Slot, indices) +} diff --git a/service/error.go b/service/error.go new file mode 100644 index 0000000..85e37d6 --- /dev/null +++ b/service/error.go @@ -0,0 +1,41 @@ +package service + +import ( + "fmt" + + "github.com/bnb-chain/blob-syncer/models" +) + +// Verify Interface Compliance +var _ error = (*Err)(nil) + +// Err defines service errors. +type Err struct { + Code int64 `json:"code"` + Message string `json:"error"` +} + +func (e Err) Enrich(message string) Err { + return Err{ + Code: e.Code, + Message: fmt.Sprintf("%s: %s", e.Message, message), + } +} + +func (e Err) Error() string { + return fmt.Sprintf("%d: %s", e.Code, e.Message) +} + +func InternalErrorWithError(err error) *models.Error { + return &models.Error{ + Code: 500, + Message: err.Error(), + } +} + +func BadRequestWithError(err error) *models.Error { + return &models.Error{ + Code: 400, + Message: err.Error(), + } +} diff --git a/service/init.go b/service/init.go new file mode 100644 index 0000000..8fd7d5c --- /dev/null +++ b/service/init.go @@ -0,0 +1,3 @@ +package service + +var BlobSvc Blob diff --git a/swagger.yaml b/swagger.yaml new file mode 100644 index 0000000..d017080 --- /dev/null +++ b/swagger.yaml @@ -0,0 +1,116 @@ +swagger: '2.0' +info: + version: 1.0.0 + title: Blob Syncer Service API + description: API for handling blob query in the Blob Syncer. +host: 'blob-syncer' +basePath: "/eth/v1" +schemes: + - http + +paths: + /beacon/blob_sidecars/{block_id}: + get: + tags: + - "blob" + summary: "Get blob sidecars by block num" + operationId: "getBlobSidecarsByBlockNum" + produces: + - "application/json" + parameters: + - name: "block_id" + in: "path" + description: "Block identifier. Can be one of: 'head' (canonical head in node's view), 'genesis', 'finalized', , " + required: true + type: string + minLength: 1 + - name: "indices" + in: "query" + description: "Array of indices for blob sidecars to request for in the specified block. Returns all blob sidecars in the block if not specified" + type: array + items: + type: string + responses: + "200": + description: "successful operation" + schema: + $ref: "#/definitions/GetBlobSideCarsResponse" + "400": + description: 'Bad Request' + schema: + $ref: "#/definitions/Error" + "404": + description: 'blob not found' + schema: + $ref: "#/definitions/Error" + "500": + description: 'internal server error' + schema: + $ref: "#/definitions/Error" + +definitions: + GetBlobSideCarsResponse: + type: object + properties: + code: + type: integer + description: "status code" + example: 200 + message: + type: string + description: "error message if there is error" + example: "signature invalid" + data: + description: "actual data for request" + type: array + items: + $ref: "#/definitions/Sidecar" + Sidecar: + type: object + properties: + index: + type: string + example: 1 + blob: + type: string + kzg_commitment: + type: string + kzg_proof: + type: string + kzg_commitment_inclusion_proof: + type: array + items: + type: string + signed_block_header: + type: object + properties: + signature: + type: string + message: + type: object + properties: + slot: + type: string + proposer_index: + type: string + parent_root: + type: string + state_root: + type: string + body_root: + type: string + + Error: + type: object + properties: + code: + x-omitempty: false + type: integer + format: int64 + description: "HTTP error code" + example: "400/500" + message: + x-omitempty: false + type: string + description: "Error message" + example: "Bad request/Internal server error" diff --git a/syncer-distroless.dockerfile b/syncer-distroless.dockerfile new file mode 100644 index 0000000..5a50499 --- /dev/null +++ b/syncer-distroless.dockerfile @@ -0,0 +1,50 @@ +FROM golang:1.20-alpine as builder + +# Set up apk dependencies +ENV PACKAGES make git libc-dev bash gcc linux-headers eudev-dev curl ca-certificates build-base + +# Set working directory for the build +WORKDIR /opt/app + +# Add source files +COPY . . + +# Install minimum necessary dependencies, remove packages +RUN apk add --no-cache $PACKAGES + +# For Private REPO +ARG GH_TOKEN="" +RUN go env -w GOPRIVATE="github.com/bnb-chain/*" +RUN git config --global url."https://${GH_TOKEN}@github.com".insteadOf "https://github.com" + +RUN make build_syncer + + +FROM alpine:3.17 + +ARG USER=app +ARG USER_UID=1000 +ARG USER_GID=1000 + +ENV PACKAGES ca-certificates libstdc++ curl +ENV WORKDIR=/app + +RUN apk add --no-cache $PACKAGES \ + && rm -rf /var/cache/apk/* \ + && addgroup -g ${USER_GID} ${USER} \ + && adduser -u ${USER_UID} -G ${USER} --shell /sbin/nologin --no-create-home -D ${USER} \ + && addgroup ${USER} tty \ + && sed -i -e "s/bin\/sh/bin\/bash/" /etc/passwd + +WORKDIR ${WORKDIR} +RUN chown -R ${USER_UID}:${USER_GID} ${WORKDIR} +USER ${USER_UID}:${USER_GID} + +ENV CONFIG_FILE_PATH /opt/app/config/config.json + +ENV WORKDIR=/app +WORKDIR ${WORKDIR} +COPY --from=builder /opt/app/build/blob-syncer ${WORKDIR} + +# Run the app +CMD /app/blob-syncer --config-path "$CONFIG_FILE_PATH" \ No newline at end of file diff --git a/syncer.Dockerfile b/syncer.Dockerfile new file mode 100644 index 0000000..e3e0440 --- /dev/null +++ b/syncer.Dockerfile @@ -0,0 +1,54 @@ +FROM golang:1.20-alpine as builder + +# Set up apk dependencies +ENV PACKAGES make git libc-dev bash gcc linux-headers eudev-dev curl ca-certificates build-base + +# Set working directory for the build +WORKDIR /opt/app + +# Add source files +COPY . . + +# Install minimum necessary dependencies, remove packages +RUN apk add --no-cache $PACKAGES + +# For Private REPO +ARG GH_TOKEN="" +RUN go env -w GOPRIVATE="github.com/bnb-chain/*" +RUN git config --global url."https://${GH_TOKEN}@github.com".insteadOf "https://github.com" + +RUN make build_syncer + +# Pull binary into a second stage deploy alpine container +FROM alpine:3.17 + +ARG USER=app +ARG USER_UID=1000 +ARG USER_GID=1000 + +ENV BLOB_SYNCER_HOME /opt/app +ENV CONFIG_FILE_PATH $BLOB_SYNCER_HOME/config/config.json +ENV PRIVATE_KEY "" +ENV DB_USERNAME "" +ENV DB_PASSWORD "" + +ENV PACKAGES ca-certificates libstdc++ +ENV WORKDIR=/app + +RUN apk add --no-cache $PACKAGES \ + && rm -rf /var/cache/apk/* \ + && addgroup -g ${USER_GID} ${USER} \ + && adduser -u ${USER_UID} -G ${USER} --shell /sbin/nologin --no-create-home -D ${USER} \ + && addgroup ${USER} tty \ + && sed -i -e "s/bin\/sh/bin\/bash/" /etc/passwd + +WORKDIR ${WORKDIR} + +COPY --from=builder /opt/app/build/blob-syncer ${WORKDIR}/ +RUN chown -R ${USER_UID}:${USER_GID} ${WORKDIR} +USER ${USER_UID}:${USER_GID} + +VOLUME [ $BLOB_SYNCER_HOME ] + +# Run the app +CMD /app/blob-syncer --config-path "$CONFIG_FILE_PATH" \ No newline at end of file diff --git a/syncer/conveter.go b/syncer/conveter.go new file mode 100644 index 0000000..98e196a --- /dev/null +++ b/syncer/conveter.go @@ -0,0 +1,24 @@ +package syncer + +import ( + "encoding/json" + + "github.com/prysmaticlabs/prysm/v5/api/server/structs" + v1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" +) + +// ToBlockAndExecutionPayloadDeneb extract beacon-signed block and execution payload from GetBlockV2Response +func ToBlockAndExecutionPayloadDeneb(blockResp *structs.GetBlockV2Response) (*ethpb.BeaconBlockDeneb, *v1.ExecutionPayloadDeneb, error) { + sbb := &structs.SignedBeaconBlockDeneb{Message: &structs.BeaconBlockDeneb{}} + err := json.Unmarshal(blockResp.Data.Message, sbb.Message) + if err != nil { + return nil, nil, err + } + sbb.Signature = blockResp.Data.Signature + signedBeaconBlockDeneb, err := sbb.ToConsensus() + if err != nil { + return nil, nil, err + } + return signedBeaconBlockDeneb.GetBlock(), signedBeaconBlockDeneb.GetBlock().GetBody().GetExecutionPayload(), nil +} diff --git a/syncer/syncer.go b/syncer/syncer.go new file mode 100644 index 0000000..8d09de0 --- /dev/null +++ b/syncer/syncer.go @@ -0,0 +1,437 @@ +package syncer + +import ( + "context" + "encoding/hex" + "fmt" + "math/big" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "gorm.io/gorm" + + "github.com/ethereum/go-ethereum/common/hexutil" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/prysmaticlabs/prysm/v5/api/server/structs" + v1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" + + "github.com/bnb-chain/blob-syncer/config" + "github.com/bnb-chain/blob-syncer/db" + "github.com/bnb-chain/blob-syncer/external" + "github.com/bnb-chain/blob-syncer/logging" + "github.com/bnb-chain/blob-syncer/metrics" + "github.com/bnb-chain/blob-syncer/types" + "github.com/bnb-chain/blob-syncer/util" +) + +const ( + BundleStatusFinalized = 1 + BundleStatusCreatedOnChain = 2 + BundleStatusSealedOnChain = 3 // todo The post verification process should check if a bundle is indeed sealed onchain + + LoopSleepTime = 10 * time.Millisecond + PauseTime = 90 * time.Second + RPCTimeout = 10 * time.Second +) + +type curBundleDetail struct { + name string + startSlot uint64 + finalizeSlot uint64 +} + +type BlobSyncer struct { + blobDao db.BlobDao + ethClients *external.ETHClient + bundleClient *external.BundleClient + config *config.SyncerConfig + bundleDetail *curBundleDetail +} + +func NewBlobSyncer( + blobDao db.BlobDao, + config *config.SyncerConfig, +) *BlobSyncer { + pkBz, err := hex.DecodeString(config.PrivateKey) + if err != nil { + panic(err) + } + bundleClient, err := external.NewBundleClient(config.BundleServiceEndpoints[0], external.WithPrivateKey(pkBz)) + if err != nil { + panic(err) + } + clients := external.NewETHClient(config.ETHRPCAddrs[0], config.BeaconRPCAddrs[0]) + return &BlobSyncer{ + blobDao: blobDao, + ethClients: clients, + bundleClient: bundleClient, + config: config, + } +} + +func (s *BlobSyncer) StartLoop() { + go func() { + nextSlot, err := s.calNextSlot() + if err != nil { + panic(err) + } + err = s.LoadProgressAndResume(nextSlot) + if err != nil { + panic(err) + } + syncTicker := time.NewTicker(LoopSleepTime) + for range syncTicker.C { + if err = s.sync(); err != nil { + logging.Logger.Error(err) + continue + } + } + }() + go func() { + verifyTicket := time.NewTicker(LoopSleepTime) + for range verifyTicket.C { + if err := s.verify(); err != nil { + logging.Logger.Error(err) + continue + } + } + }() +} + +func (s *BlobSyncer) sync() error { + var ( + nextSlot uint64 + err error + block, latestBlockResp *structs.GetBlockV2Response + ) + nextSlot, err = s.calNextSlot() + if err != nil { + return err + } + ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) + defer cancel() + var isForkedBlock bool + block, err = s.ethClients.BeaconClient.GetBlock(ctx, nextSlot) + if err != nil { + if err != external.ErrBlockNotFound { + return err + } + // Both try to get forked block and non-exist block will return 404. When the response is ErrBlockNotFound, + // check whether nextSlot is >= latest slot, otherwise it is a forked block, should skip it. + latestBlockResp, err = s.ethClients.BeaconClient.GetLatestBlock(ctx) + if err != nil { + logging.Logger.Errorf("failed to get latest becon block, err=%s", err.Error()) + return err + } + clBlock, _, err := ToBlockAndExecutionPayloadDeneb(latestBlockResp) + if err != nil { + logging.Logger.Errorf("failed to ToBlockAndExecutionPayloadDeneb, err=%s", err.Error()) + return err + } + if nextSlot >= uint64(clBlock.Slot) { + logging.Logger.Debugf("the next slot %d is larger than current block slot %d\n", nextSlot, clBlock.Slot) + time.Sleep(PauseTime) + return nil + } + isForkedBlock = true + } + + if block != nil && !block.Finalized { + logging.Logger.Infof("current block(slot=%d) is not finalized yet", nextSlot) + time.Sleep(PauseTime) + return nil + } + + var sideCars []*structs.Sidecar + if !isForkedBlock { + ctx, cancel = context.WithTimeout(context.Background(), RPCTimeout) + defer cancel() + sideCars, err = s.ethClients.BeaconClient.GetBlob(ctx, nextSlot) + if err != nil { + return err + } + } + + bundleName := s.bundleDetail.name + // create a new bundle in local. + if nextSlot == s.bundleDetail.startSlot { + if err = s.createLocalBundleDir(); err != nil { + logging.Logger.Errorf("failed to create local bundle dir, bundle=%s, err=%s", bundleName, err.Error()) + return err + } + } + if err = s.writeBlobToFile(nextSlot, bundleName, sideCars); err != nil { + return err + } + if nextSlot == s.bundleDetail.finalizeSlot { + err = s.finalizeCurBundle(bundleName) + if err != nil { + return err + } + logging.Logger.Infof("finalized bundle, bundle_name=%s, bucket_name=%s\n", bundleName, s.getBucketName()) + // init next bundle + startSlot := nextSlot + 1 + endSlot := nextSlot + s.getCreateBundleSlotInterval() + s.bundleDetail = &curBundleDetail{ + name: types.GetBundleName(startSlot, endSlot), + startSlot: startSlot, + finalizeSlot: endSlot, + } + } + + if isForkedBlock { + return s.blobDao.SaveBlockAndBlob(&db.Block{ + Slot: nextSlot, + BundleName: bundleName, + }, nil) + } + + blockToSave, blobToSave, err := s.ToBlockAndBlobs(block, sideCars, nextSlot, bundleName) + if err != nil { + return err + } + err = s.blobDao.SaveBlockAndBlob(blockToSave, blobToSave) + if err != nil { + logging.Logger.Errorf("failed to save block(h=%d) and Blob(count=%d), err=%s", blockToSave.Slot, len(blobToSave), err.Error()) + return err + } + metrics.SyncedSlotGauge.Set(float64(nextSlot)) + logging.Logger.Infof("saved block(slot=%d) and blobs(num=%d) to DB \n", nextSlot, len(blobToSave)) + return nil +} + +func (s *BlobSyncer) getBucketName() string { + return s.config.BucketName +} + +func (s *BlobSyncer) getCreateBundleSlotInterval() uint64 { + return s.config.GetCreateBundleSlotInterval() +} + +func (s *BlobSyncer) calNextSlot() (uint64, error) { + latestProcessedBlock, err := s.blobDao.GetLatestProcessedBlock() + if err != nil { + return 0, fmt.Errorf("failed to get latest polled block from db, error: %s", err.Error()) + } + latestPolledBlockSlot := latestProcessedBlock.Slot + nextSlot := s.config.StartSlot + if nextSlot <= latestPolledBlockSlot { + nextSlot = latestPolledBlockSlot + 1 + } + return nextSlot, nil +} + +// createLocalBundleDir creates an empty dir to hold blob files among a range of blocks, the blobs in this dir will be assembled into a bundle and uploaded to bundle service +func (s *BlobSyncer) createLocalBundleDir() error { + bundleName := s.bundleDetail.name + _, err := os.Stat(s.getBundleDir(bundleName)) + if os.IsNotExist(err) { + err = os.MkdirAll(filepath.Dir(s.getBundleDir(bundleName)), os.ModePerm) + if err != nil { + return err + } + } + return s.blobDao.CreateBundle( + &db.Bundle{ + Name: s.bundleDetail.name, + Status: db.Finalizing, + }) +} +func (s *BlobSyncer) finalizeBundle(bundleName, bundleDir, bundleFilePath string) error { + err := s.bundleClient.UploadAndFinalizeBundle(bundleName, s.getBucketName(), bundleDir, bundleFilePath) + if err != nil { + if !strings.Contains(err.Error(), "Object exists") && !strings.Contains(err.Error(), "empty bundle") { + return err + } + } + err = os.RemoveAll(bundleDir) + if err != nil { + return err + } + err = os.Remove(bundleFilePath) + if err != nil && !os.IsNotExist(err) { + return err + } + return s.blobDao.UpdateBundleStatus(bundleName, db.Finalized) +} + +func (s *BlobSyncer) finalizeCurBundle(bundleName string) error { + return s.finalizeBundle(bundleName, s.getBundleDir(bundleName), s.getBundleFilePath(bundleName)) +} + +func (s *BlobSyncer) writeBlobToFile(slot uint64, bundleName string, blobs []*structs.Sidecar) error { + for i, b := range blobs { + blobName := types.GetBlobName(slot, i) + file, err := os.Create(s.getBlobPath(bundleName, blobName)) + if err != nil { + logging.Logger.Errorf("failed to create file, err=%s", err.Error()) + return err + } + defer file.Close() + _, err = file.WriteString(b.Blob) + if err != nil { + logging.Logger.Errorf("failed to write string, err=%s", err.Error()) + return err + } + } + return nil +} + +func (s *BlobSyncer) getBundleDir(bundleName string) string { + return fmt.Sprintf("%s/%s/", s.config.TempDir, bundleName) +} + +func (s *BlobSyncer) getBlobPath(bundleName, blobName string) string { + return fmt.Sprintf("%s/%s/%s", s.config.TempDir, bundleName, blobName) +} + +func (s *BlobSyncer) getBundleFilePath(bundleName string) string { + return fmt.Sprintf("%s/%s.bundle", s.config.TempDir, bundleName) +} + +func (s *BlobSyncer) LoadProgressAndResume(nextSlot uint64) error { + var ( + startSlot uint64 + endSlot uint64 + err error + ) + finalizingBundle, err := s.blobDao.GetLatestFinalizingBundle() + if err != nil { + if err != gorm.ErrRecordNotFound { + return err + } + // There is no pending(finalizing) bundle, start a new bundle. e.g. a bundle includes + // blobs from block slot 0-9 when the block interval is config to 10 + startSlot = nextSlot + endSlot = nextSlot + s.getCreateBundleSlotInterval() - 1 + } else { + // resume + startSlot, endSlot, err = types.ParseBundleName(finalizingBundle.Name) + if err != nil { + return err + } + + // might no longer need to process the bundle even-thought it is not finalized if the user set the config to skip it. + if nextSlot > endSlot { + err = s.blobDao.UpdateBlocksStatus(startSlot, endSlot, db.Skipped) + if err != nil { + logging.Logger.Errorf("failed to update blocks status, startSlot=%d, endSlot=%d", startSlot, endSlot) + return err + } + logging.Logger.Infof("the config slot number %d is larger than the recorded bundle end slot %d, will resume from the config slot", nextSlot, endSlot) + if err = s.blobDao.UpdateBundleStatus(finalizingBundle.Name, db.Deprecated); err != nil { + return err + } + startSlot = nextSlot + endSlot = nextSlot + s.getCreateBundleSlotInterval() - 1 + } + + } + s.bundleDetail = &curBundleDetail{ + name: types.GetBundleName(startSlot, endSlot), + startSlot: startSlot, + finalizeSlot: endSlot, + } + return nil +} + +func (s *BlobSyncer) ToBlockAndBlobs(blockResp *structs.GetBlockV2Response, blobs []*structs.Sidecar, slot uint64, bundleName string) (*db.Block, []*db.Blob, error) { + var blockReturn *db.Block + blobsReturn := make([]*db.Blob, 0) + + var ( + clBlock *ethpb.BeaconBlockDeneb + executionPayload *v1.ExecutionPayloadDeneb + err error + ) + + switch blockResp.Version { + case version.String(version.Deneb): + clBlock, executionPayload, err = ToBlockAndExecutionPayloadDeneb(blockResp) + if err != nil { + logging.Logger.Errorf("failed to convert to ToBlockAndExecutionPayloadDeneb, err=%s", err.Error()) + return nil, nil, err + } + + bodyRoot, err := clBlock.GetBody().HashTreeRoot() + if err != nil { + return nil, nil, err + } + ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) + defer cancel() + header, err := s.ethClients.BeaconClient.GetHeader(ctx, slot) + if err != nil { + logging.Logger.Errorf("failed to get header, err=%s", header.Data.Root, err.Error()) + return nil, nil, err + } + rootBz, err := hexutil.Decode(header.Data.Root) + if err != nil { + logging.Logger.Errorf("failed to decode header.Data.Root=%s, err=%s", header.Data.Root, err.Error()) + return nil, nil, err + } + sigBz, err := hexutil.Decode(header.Data.Header.Signature) + if err != nil { + logging.Logger.Errorf("failed to decode header.Data.Header.Signature=%s, err=%s", header.Data.Header.Signature, err.Error()) + return nil, nil, err + } + + blockReturn = &db.Block{ + Root: hex.EncodeToString(rootBz), // get rid of 0x saved to DB + ParentRoot: hex.EncodeToString(clBlock.GetParentRoot()), + StateRoot: hex.EncodeToString(clBlock.GetStateRoot()), + BodyRoot: hex.EncodeToString(bodyRoot[:]), + Signature: hex.EncodeToString(sigBz[:]), + ProposerIndex: uint64(clBlock.ProposerIndex), + Slot: uint64(clBlock.GetSlot()), + ELBlockHeight: executionPayload.GetBlockNumber(), + BlobCount: len(blobs), + BundleName: bundleName, + } + default: + return nil, nil, fmt.Errorf("un-expected block version %s", blockResp.Version) + } + + if len(blobs) == 0 { + return blockReturn, blobsReturn, nil + } + + for _, blob := range blobs { + index, err := strconv.Atoi(blob.Index) + if err != nil { + return nil, nil, err + } + b := &db.Blob{ + Name: types.GetBlobName(slot, index), + Slot: slot, + Idx: index, + KzgProof: blob.KzgProof, + KzgCommitment: blob.KzgCommitment, + CommitmentInclusionProof: util.JoinWithComma(blob.CommitmentInclusionProof), + } + blobsReturn = append(blobsReturn, b) + } + + ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) + defer cancel() + elBlock, err := s.ethClients.Eth1Client.BlockByNumber(ctx, big.NewInt(int64(executionPayload.GetBlockNumber()))) + if err != nil { + return nil, nil, fmt.Errorf("failed to get block at height %d, err=%s", executionPayload.GetBlockNumber(), err.Error()) + } + blobIndex := 0 + for _, tx := range elBlock.Body().Transactions { + if tx.Type() == ethtypes.BlobTxType { + for _, bs := range tx.BlobHashes() { + blobsReturn[blobIndex].TxHash = hex.EncodeToString(tx.Hash().Bytes()) + blobsReturn[blobIndex].ToAddr = tx.To().String() + blobsReturn[blobIndex].VersionedHash = bs.String() + blobIndex++ + } + } + } + return blockReturn, blobsReturn, nil +} diff --git a/syncer/verifier.go b/syncer/verifier.go new file mode 100644 index 0000000..b990c4d --- /dev/null +++ b/syncer/verifier.go @@ -0,0 +1,249 @@ +package syncer + +import ( + "bytes" + "context" + "errors" + "os" + "path/filepath" + "time" + + "github.com/prysmaticlabs/prysm/v5/api/server/structs" + "gorm.io/gorm" + + "github.com/bnb-chain/blob-syncer/db" + "github.com/bnb-chain/blob-syncer/external" + "github.com/bnb-chain/blob-syncer/logging" + "github.com/bnb-chain/blob-syncer/metrics" + "github.com/bnb-chain/blob-syncer/types" + "github.com/bnb-chain/blob-syncer/util" +) + +var ( + ErrVerificationFailed = errors.New("verification failed") + ErrBundleNotSealed = errors.New("bundle not sealed yet") +) + +// Verify is used to verify the blob uploaded to bundle service is indeed in Greenfield, and integrity. +func (s *BlobSyncer) verify() error { + + var err error + verifyBlock, err := s.blobDao.GetEarliestUnverifiedBlock() + if err != nil { + if err == gorm.ErrRecordNotFound { + logging.Logger.Debugf("found no unverified block in DB") + time.Sleep(PauseTime) + return nil + } + return err + } + bundleName := verifyBlock.BundleName + _, bundleEndSlot, err := types.ParseBundleName(bundleName) + if err != nil { + return err + } + verifyBlockSlot := verifyBlock.Slot + if verifyBlock.BlobCount == 0 { + if err = s.blobDao.UpdateBlockStatus(verifyBlockSlot, db.Verified); err != nil { + logging.Logger.Errorf("failed to update block status, slot=%d err=%s", verifyBlockSlot, err.Error()) + return err + } + if bundleEndSlot == verifyBlockSlot { + logging.Logger.Debugf("update bundle status to sealed, name=%s , slot %d ", bundleName, verifyBlockSlot) + if err = s.blobDao.UpdateBundleStatus(bundleName, db.Sealed); err != nil { + logging.Logger.Errorf("failed to update bundle status to sealed, name=%s , slot %d ", bundleName, verifyBlockSlot) + return err + } + } + return nil + } + + // get blob from beacon chain again + ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) + defer cancel() + sideCars, err := s.ethClients.BeaconClient.GetBlob(ctx, verifyBlockSlot) + if err != nil { + logging.Logger.Errorf("failed to get blob at slot=%d, err=%s", verifyBlockSlot, err.Error()) + return err + } + + // get blob meta from DB + blobMetas, err := s.blobDao.GetBlobBySlot(verifyBlockSlot) + if err != nil { + return err + } + + if len(blobMetas) != len(sideCars) { + logging.Logger.Errorf("found blob number mismatch at slot=%d, bundleName=%s", verifyBlockSlot, bundleName) + return s.reUploadBundle(bundleName) + } + + // check if the bundle has been submitted to bundle service + bundle, err := s.blobDao.GetBundle(bundleName) + if err != nil { + return err + } + + if bundle.Status == db.Finalizing { + logging.Logger.Debugf("the bundle has not been submitted to bundle service yet, bundleName=%s", bundleName) + time.Sleep(PauseTime) + return nil + } + + err = s.verifyBlobAtSlot(verifyBlockSlot, sideCars, blobMetas, bundleName) + if err != nil { + if err == external.ErrorBundleNotExist || err == ErrBundleNotSealed { + return nil + } + if err == ErrVerificationFailed { + return s.reUploadBundle(bundleName) + } + return err + } + if err = s.blobDao.UpdateBlockStatus(verifyBlockSlot, db.Verified); err != nil { + logging.Logger.Errorf("failed to update block status to verified, slot=%d err=%s", verifyBlockSlot, err.Error()) + return err + } + metrics.VerifiedSlotGauge.Set(float64(verifyBlockSlot)) + if bundleEndSlot == verifyBlockSlot { + logging.Logger.Debugf("update bundle status to sealed, name=%s , slot %d ", bundleName, verifyBlockSlot) + if err = s.blobDao.UpdateBundleStatus(bundleName, db.Sealed); err != nil { + logging.Logger.Errorf("failed to update bundle status to sealed, name=%s, slot %d ", bundleName, verifyBlockSlot) + return err + } + } + logging.Logger.Infof("successfully verify at block slot %d ", verifyBlockSlot) + return nil +} + +func (s *BlobSyncer) verifyBlobAtSlot(slot uint64, sidecars []*structs.Sidecar, blobMetas []*db.Blob, bundleName string) error { + // validate the bundle is sealed + bundleInfo, err := s.bundleClient.GetBundleInfo(s.getBucketName(), bundleName) + if err != nil { + return err + } + if bundleInfo.Status == BundleStatusFinalized || bundleInfo.Status == BundleStatusCreatedOnChain { + return ErrBundleNotSealed + } + + for i := 0; i < len(sidecars); i++ { + // get blob from bundle service + blobFromBundle, err := s.bundleClient.GetObject(s.getBucketName(), bundleName, types.GetBlobName(slot, i)) + if err != nil { + if err == external.ErrorBundleObjectNotExist { + logging.Logger.Errorf("the bundle object not found in bundle service, object=%s", types.GetBlobName(slot, i)) + return ErrVerificationFailed + } + return err + } + + expectedIdx, err := util.StringToInt64(sidecars[i].Index) + if err != nil { + return err + } + + if int64(blobMetas[i].Idx) != expectedIdx { + logging.Logger.Errorf("found index mismatch") + return ErrVerificationFailed + } + + expectedKzgProofHash, err := util.GenerateHash(sidecars[i].KzgProof) + if err != nil { + return err + } + actualKzgProofHash, err := util.GenerateHash(blobMetas[i].KzgProof) + if err != nil { + return err + } + if !bytes.Equal(actualKzgProofHash, expectedKzgProofHash) { + logging.Logger.Errorf("found kzg proof mismatch") + return ErrVerificationFailed + } + + actualBlobHash, err := util.GenerateHash(blobFromBundle) + if err != nil { + return err + } + expectedBlobHash, err := util.GenerateHash(sidecars[i].Blob) + if err != nil { + return err + } + if !bytes.Equal(actualBlobHash, expectedBlobHash) { + logging.Logger.Errorf("found blob mismatch") + return ErrVerificationFailed + } + } + return nil +} + +func (s *BlobSyncer) reUploadBundle(bundleName string) error { + if err := s.blobDao.UpdateBundleStatus(bundleName, db.Deprecated); err != nil { + return err + } + newBundleName := bundleName + "_calibrated_" + util.Int64ToString(time.Now().Unix()) + startSlot, endSlot, err := types.ParseBundleName(bundleName) + if err != nil { + return err + } + _, err = os.Stat(s.getBundleDir(newBundleName)) + if os.IsNotExist(err) { + err = os.MkdirAll(filepath.Dir(s.getBundleDir(newBundleName)), os.ModePerm) + if err != nil { + return err + } + } + if err = s.blobDao.CreateBundle(&db.Bundle{ + Name: newBundleName, + Status: db.Finalizing, + Calibrated: true, + }); err != nil { + return err + } + for slot := startSlot; slot <= endSlot; slot++ { + ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) + defer cancel() + sideCars, err := s.ethClients.BeaconClient.GetBlob(ctx, slot) + if err != nil { + return err + } + if err = s.writeBlobToFile(slot, newBundleName, sideCars); err != nil { + return err + } + block, err := s.ethClients.BeaconClient.GetBlock(ctx, slot) + if err != nil { + if err == external.ErrBlockNotFound { + continue + } + return err + } + blockMeta, err := s.blobDao.GetBlock(slot) + if err != nil { + return err + } + blobMetas, err := s.blobDao.GetBlobBySlot(slot) + if err != nil { + return err + } + blockToSave, blobToSave, err := s.ToBlockAndBlobs(block, sideCars, slot, newBundleName) + if err != nil { + return err + } + blockToSave.Id = blockMeta.Id + for i, preBlob := range blobMetas { + if i < len(blobToSave) { + blobToSave[i].Id = preBlob.Id + } + } + err = s.blobDao.SaveBlockAndBlob(blockToSave, blobToSave) + if err != nil { + logging.Logger.Errorf("failed to save block(h=%d) and Blob(count=%d), err=%s", blockToSave.Slot, len(blobToSave), err.Error()) + return err + } + logging.Logger.Infof("save calibrated block(slot=%d) and blobs(num=%d) to DB \n", slot, len(blobToSave)) + } + if err = s.finalizeBundle(newBundleName, s.getBundleDir(newBundleName), s.getBundleFilePath(newBundleName)); err != nil { + logging.Logger.Errorf("failed to finalized bundle, name=%s, err=%s", newBundleName, err.Error()) + return err + } + return nil +} diff --git a/types/key.go b/types/key.go new file mode 100644 index 0000000..3042779 --- /dev/null +++ b/types/key.go @@ -0,0 +1,41 @@ +package types + +import ( + "fmt" + "strconv" + "strings" +) + +func GetBlobName(slot uint64, index int) string { + return fmt.Sprintf("blob_h%d_i%d", slot, index) +} + +func GetBundleName(startSlot, endSlot uint64) string { + return fmt.Sprintf("blobs_s%d_e%d", startSlot, endSlot) +} + +func ParseBlobName(blobName string) (slot uint64, index uint64, err error) { + parts := strings.Split(blobName, "_") + slot, err = strconv.ParseUint(parts[1][1:], 10, 64) + if err != nil { + return + } + index, err = strconv.ParseUint(parts[2][1:], 10, 64) + if err != nil { + return + } + return +} + +func ParseBundleName(bundleName string) (startSlot, endSlot uint64, err error) { + parts := strings.Split(bundleName, "_") + startSlot, err = strconv.ParseUint(parts[1][1:], 10, 64) + if err != nil { + return + } + endSlot, err = strconv.ParseUint(parts[2][1:], 10, 64) + if err != nil { + return + } + return +} diff --git a/util/util.go b/util/util.go new file mode 100644 index 0000000..2f6f859 --- /dev/null +++ b/util/util.go @@ -0,0 +1,66 @@ +package util + +import ( + "crypto/sha256" + "strconv" + "strings" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// StringToUint64 converts string to uint64 +func StringToUint64(str string) (uint64, error) { + ui64, err := strconv.ParseUint(str, 10, 64) + if err != nil { + return 0, err + } + return ui64, nil +} + +// StringToInt64 converts string to int64 +func StringToInt64(str string) (int64, error) { + i64, err := strconv.ParseInt(str, 10, 64) + if err != nil { + return 0, err + } + return i64, nil +} + +func SplitByComma(str string) []string { + str = strings.TrimSpace(str) + strArr := strings.Split(str, ",") + var trimStr []string + for _, item := range strArr { + if len(strings.TrimSpace(item)) > 0 { + trimStr = append(trimStr, strings.TrimSpace(item)) + } + } + return trimStr +} + +func JoinWithComma(slice []string) string { + return strings.Join(slice, ",") +} + +func GenerateHash(hexStr string) ([]byte, error) { + if !strings.HasPrefix(hexStr, "0x") { + hexStr = "0x" + hexStr + } + bz, err := hexutil.Decode(hexStr) + if err != nil { + return nil, nil + } + hash := sha256.New() + hash.Write(bz) + return hash.Sum(nil), nil +} + +// Uint64ToString coverts uint64 to string +func Uint64ToString(u uint64) string { + return strconv.FormatUint(u, 10) +} + +// Int64ToString coverts uint64 to string +func Int64ToString(u int64) string { + return strconv.FormatInt(u, 10) +}