diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index db157ad37..ce6958eab 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,11 +20,11 @@ jobs: goos: ["linux"] timeout-minutes: 5 steps: - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v3 with: go-version: "1.17" - - uses: actions/checkout@v2.4.0 - - uses: technote-space/get-diff-action@v6.0.1 + - uses: actions/checkout@v3 + - uses: technote-space/get-diff-action@v6 with: PATTERNS: | **/**.go @@ -41,11 +41,11 @@ jobs: needs: build timeout-minutes: 5 steps: - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v3 with: go-version: "1.17" - - uses: actions/checkout@v2.4.0 - - uses: technote-space/get-diff-action@v6.0.1 + - uses: actions/checkout@v3 + - uses: technote-space/get-diff-action@v6 with: PATTERNS: | **/**.go @@ -63,11 +63,11 @@ jobs: needs: build timeout-minutes: 5 steps: - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v3 with: go-version: "1.17" - - uses: actions/checkout@v2.4.0 - - uses: technote-space/get-diff-action@v6.0.1 + - uses: actions/checkout@v3 + - uses: technote-space/get-diff-action@v6 with: PATTERNS: | **/**.go diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index bf79568c9..524df1ef8 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -13,7 +13,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - name: Prepare id: prep run: | @@ -39,17 +39,17 @@ jobs: platforms: all - name: Set up Docker Build - uses: docker/setup-buildx-action@v1.6.0 + uses: docker/setup-buildx-action@v1.7.0 - name: Login to DockerHub if: ${{ github.event_name != 'pull_request' }} - uses: docker/login-action@v1.13.0 + uses: docker/login-action@v1.14.1 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Publish to Docker Hub - uses: docker/build-push-action@v2.9.0 + uses: docker/build-push-action@v2.10.0 with: context: . file: ./DOCKER/Dockerfile diff --git a/.github/workflows/docs-toc.yml b/.github/workflows/docs-toc.yml new file mode 100644 index 000000000..fbe759877 --- /dev/null +++ b/.github/workflows/docs-toc.yml @@ -0,0 +1,20 @@ +# Verify that important design docs have ToC entries. +name: Check documentation ToC +on: + pull_request: + push: + branches: + - master + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: technote-space/get-diff-action@v6 + with: + PATTERNS: | + docs/architecture/** + docs/rfc/** + - run: ./docs/presubmit.sh + if: env.GIT_DIFF diff --git a/.github/workflows/e2e-manual.yml b/.github/workflows/e2e-manual.yml index 1e5f6c36c..bab3fcf62 100644 --- a/.github/workflows/e2e-manual.yml +++ b/.github/workflows/e2e-manual.yml @@ -15,11 +15,11 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v3 with: go-version: '1.17' - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - name: Build working-directory: test/e2e diff --git a/.github/workflows/e2e-nightly-34x.yml b/.github/workflows/e2e-nightly-34x.yml index 38cb3a9d4..d7af7b347 100644 --- a/.github/workflows/e2e-nightly-34x.yml +++ b/.github/workflows/e2e-nightly-34x.yml @@ -20,11 +20,11 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v3 with: go-version: '1.17' - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 with: ref: 'v0.34.x' diff --git a/.github/workflows/e2e-nightly-35x.yml b/.github/workflows/e2e-nightly-35x.yml index 425108169..c397ead9c 100644 --- a/.github/workflows/e2e-nightly-35x.yml +++ b/.github/workflows/e2e-nightly-35x.yml @@ -20,11 +20,11 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v3 with: go-version: '1.17' - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 with: ref: 'v0.35.x' diff --git a/.github/workflows/e2e-nightly-master.yml b/.github/workflows/e2e-nightly-master.yml index bc4feae45..fc1fff104 100644 --- a/.github/workflows/e2e-nightly-master.yml +++ b/.github/workflows/e2e-nightly-master.yml @@ -19,11 +19,11 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v3 with: go-version: '1.17' - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - name: Build working-directory: test/e2e diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 71aec16f7..6666999fc 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -14,11 +14,11 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v3 with: go-version: '1.17' - - uses: actions/checkout@v2.4.0 - - uses: technote-space/get-diff-action@v6.0.1 + - uses: actions/checkout@v3 + - uses: technote-space/get-diff-action@v6 with: PATTERNS: | **/**.go diff --git a/.github/workflows/fuzz-nightly.yml b/.github/workflows/fuzz-nightly.yml index e12ee2321..0fcab9ae5 100644 --- a/.github/workflows/fuzz-nightly.yml +++ b/.github/workflows/fuzz-nightly.yml @@ -13,15 +13,15 @@ jobs: fuzz-nightly-test: runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v3 with: go-version: '1.17' - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - name: Install go-fuzz working-directory: test/fuzz - run: go get -u github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-build + run: go install github.com/dvyukov/go-fuzz/go-fuzz@latest github.com/dvyukov/go-fuzz/go-fuzz-build@latest - name: Fuzz mempool working-directory: test/fuzz @@ -39,14 +39,14 @@ jobs: continue-on-error: true - name: Archive crashers - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: crashers path: test/fuzz/**/crashers retention-days: 3 - name: Archive suppressions - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: suppressions path: test/fuzz/**/suppressions diff --git a/.github/workflows/jepsen.yml b/.github/workflows/jepsen.yml index 60b49443d..04e599564 100644 --- a/.github/workflows/jepsen.yml +++ b/.github/workflows/jepsen.yml @@ -46,7 +46,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the Jepsen repository - uses: actions/checkout@v2.4.0 + uses: actions/checkout@v3 with: repository: 'tendermint/jepsen' @@ -58,7 +58,7 @@ jobs: run: docker exec -i jepsen-control bash -c 'source /root/.bashrc; cd /jepsen/tendermint; lein run test --nemesis ${{ github.event.inputs.nemesis }} --workload ${{ github.event.inputs.workload }} --concurrency ${{ github.event.inputs.concurrency }} --tendermint-url ${{ github.event.inputs.tendermintUrl }} --merkleeyes-url ${{ github.event.inputs.merkleeyesUrl }} --time-limit ${{ github.event.inputs.timeLimit }} ${{ github.event.inputs.dupOrSuperByzValidators }}' - name: Archive results - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: results path: tendermint/store/latest diff --git a/.github/workflows/linkchecker.yml b/.github/workflows/linkchecker.yml index af446771a..e2ba80861 100644 --- a/.github/workflows/linkchecker.yml +++ b/.github/workflows/linkchecker.yml @@ -6,7 +6,7 @@ jobs: markdown-link-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.4.0 - - uses: gaurav-nelson/github-action-markdown-link-check@1.0.13 + - uses: actions/checkout@v3 + - uses: creachadair/github-action-markdown-link-check@master with: folder-path: "docs" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 40b00eba1..359514426 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,7 +1,11 @@ name: Golang Linter -# Lint runs golangci-lint over the entire Tendermint repository -# This workflow is run on every pull request and push to master -# The `golangci` job will pass without running if no *.{go, mod, sum} files have been modified. +# Lint runs golangci-lint over the entire Tendermint repository. +# +# This workflow is run on every pull request and push to master. +# +# The `golangci` job will pass without running if no *.{go, mod, sum} +# files have been modified. + on: pull_request: push: @@ -13,17 +17,22 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 8 steps: - - uses: actions/checkout@v2.4.0 - - uses: technote-space/get-diff-action@v6.0.1 + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: '^1.17' + - uses: technote-space/get-diff-action@v6 with: PATTERNS: | **/**.go go.mod go.sum - - uses: golangci/golangci-lint-action@v2.5.2 + - uses: golangci/golangci-lint-action@v3.1.0 with: - # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. - version: v1.42.1 + # Required: the version of golangci-lint is required and + # must be specified without patch version: we always use the + # latest patch version. + version: v1.45 args: --timeout 10m github-token: ${{ secrets.github_token }} if: env.GIT_DIFF diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index d430485ab..badae8c1f 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v2.4.0 + uses: actions/checkout@v3 - name: Lint Code Base uses: docker://github/super-linter:v4 env: diff --git a/.github/workflows/markdown-links.yml b/.github/workflows/markdown-links.yml index 3e3341d16..7af7e3ce9 100644 --- a/.github/workflows/markdown-links.yml +++ b/.github/workflows/markdown-links.yml @@ -11,7 +11,13 @@ jobs: markdown-link-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@master - - uses: gaurav-nelson/github-action-markdown-link-check@1.0.13 + - uses: actions/checkout@v3 + - uses: technote-space/get-diff-action@v6 + with: + PATTERNS: | + **/**.md + - uses: creachadair/github-action-markdown-link-check@master with: check-modified-files-only: 'yes' + config-file: '.md-link-check.json' + if: env.GIT_DIFF diff --git a/.github/workflows/proto-check.yml b/.github/workflows/proto-check.yml deleted file mode 100644 index 306e62903..000000000 --- a/.github/workflows/proto-check.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Proto Check -# Protobuf runs buf (https://buf.build/) lint and check-breakage -# This workflow is only run when a file in the proto directory -# has been modified. -on: - workflow_dispatch: # allow running workflow manually - pull_request: - paths: - - "proto/*" -jobs: - proto-lint: - runs-on: ubuntu-latest - timeout-minutes: 4 - steps: - - uses: actions/checkout@v2.4.0 - - name: lint - run: make proto-lint - proto-breakage: - runs-on: ubuntu-latest - timeout-minutes: 4 - steps: - - uses: actions/checkout@v2.4.0 - - name: check-breakage - run: make proto-check-breaking-ci diff --git a/.github/workflows/proto-dockerfile.yml b/.github/workflows/proto-dockerfile.yml deleted file mode 100644 index 0d08758b6..000000000 --- a/.github/workflows/proto-dockerfile.yml +++ /dev/null @@ -1,64 +0,0 @@ -# This workflow (re)builds and pushes a Docker image containing the -# protobuf build tools used by the other workflows. -# -# When making changes that require updates to the builder image, you -# should merge the updates first and wait for this workflow to complete, -# so that the changes will be available for the dependent workflows. -# - -name: Build & Push Proto Builder Image -on: - pull_request: - paths: - - "proto/*" - push: - branches: - - master - paths: - - "proto/*" - schedule: - # run this job once a month to recieve any go or buf updates - - cron: "0 9 1 * *" - -env: - REGISTRY: ghcr.io - IMAGE_NAME: tendermint/docker-build-proto - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2.4.0 - - name: Check out and assign tags - id: prep - run: | - DOCKER_IMAGE="${REGISTRY}/${IMAGE_NAME}" - VERSION=noop - if [[ "$GITHUB_REF" == "refs/tags/*" ]]; then - VERSION="${GITHUB_REF#refs/tags/}" - elif [[ "$GITHUB_REF" == "refs/heads/*" ]]; then - VERSION="$(echo "${GITHUB_REF#refs/heads/}" | sed -r 's#/+#-#g')" - if [[ "${{ github.event.repository.default_branch }}" = "$VERSION" ]]; then - VERSION=latest - fi - fi - TAGS="${DOCKER_IMAGE}:${VERSION}" - echo ::set-output name=tags::"${TAGS}" - - - name: Set up docker buildx - uses: docker/setup-buildx-action@v1.6.0 - - - name: Log in to the container registry - uses: docker/login-action@v1.13.0 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and publish image - uses: docker/build-push-action@v2.9.0 - with: - context: ./proto - file: ./proto/Dockerfile - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.prep.outputs.tags }} diff --git a/.github/workflows/proto-lint.yml b/.github/workflows/proto-lint.yml new file mode 100644 index 000000000..b1fbeab9d --- /dev/null +++ b/.github/workflows/proto-lint.yml @@ -0,0 +1,21 @@ +name: Protobuf Lint +on: + pull_request: + paths: + - 'proto/**' + push: + branches: + - master + paths: + - 'proto/**' + +jobs: + lint: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v3 + - uses: bufbuild/buf-setup-action@v1.4.0 + - uses: bufbuild/buf-lint-action@v1 + with: + input: 'proto' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3d65b289b..ec4fa810b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,11 +12,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2.4.0 + uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v3 with: go-version: '1.17' @@ -27,11 +27,13 @@ jobs: version: latest args: build --skip-validate # skip validate skips initial sanity checks in order to be able to fully run + - run: echo https://github.com/tendermint/tendermint/blob/${GITHUB_REF#refs/tags/}/CHANGELOG.md#${GITHUB_REF#refs/tags/} > ../release_notes.md + - name: Release uses: goreleaser/goreleaser-action@v2 if: startsWith(github.ref, 'refs/tags/') with: version: latest - args: release --rm-dist + args: release --rm-dist --release-notes=../release_notes.md env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 1109f09c1..4089abfbc 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -7,7 +7,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v4 + - uses: actions/stale@v5 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-pr-message: "This pull request has been automatically marked as stale because it has not had diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f3f5cba1d..6ce8a4d34 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,11 +16,11 @@ jobs: matrix: part: ["00", "01", "02", "03", "04", "05"] steps: - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v3 with: go-version: "1.17" - - uses: actions/checkout@v2.4.0 - - uses: technote-space/get-diff-action@v6.0.1 + - uses: actions/checkout@v3 + - uses: technote-space/get-diff-action@v6 with: PATTERNS: | **/**.go @@ -32,7 +32,7 @@ jobs: run: | make test-group-${{ matrix.part }} NUM_SPLIT=6 if: env.GIT_DIFF - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: "${{ github.sha }}-${{ matrix.part }}-coverage" path: ./build/${{ matrix.part }}.profile.out @@ -41,8 +41,8 @@ jobs: runs-on: ubuntu-latest needs: tests steps: - - uses: actions/checkout@v2.4.0 - - uses: technote-space/get-diff-action@v6.0.1 + - uses: actions/checkout@v3 + - uses: technote-space/get-diff-action@v6 with: PATTERNS: | **/**.go @@ -50,26 +50,26 @@ jobs: go.mod go.sum Makefile - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: name: "${{ github.sha }}-00-coverage" if: env.GIT_DIFF - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: name: "${{ github.sha }}-01-coverage" if: env.GIT_DIFF - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: name: "${{ github.sha }}-02-coverage" if: env.GIT_DIFF - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: name: "${{ github.sha }}-03-coverage" if: env.GIT_DIFF - run: | cat ./*profile.out | grep -v "mode: set" >> coverage.txt if: env.GIT_DIFF - - uses: codecov/codecov-action@v2.1.0 + - uses: codecov/codecov-action@v3.1.0 with: file: ./coverage.txt if: env.GIT_DIFF diff --git a/.md-link-check.json b/.md-link-check.json new file mode 100644 index 000000000..6f47fa2c9 --- /dev/null +++ b/.md-link-check.json @@ -0,0 +1,6 @@ +{ + "retryOn429": true, + "retryCount": 5, + "fallbackRetryDelay": "30s", + "aliveStatusCodes": [200, 206, 503] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 0216a533b..cfafd8c7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,39 @@ Friendly reminder: We have a [bug bounty program](https://hackerone.com/cosmos). +## v0.35.4 + +April 18, 2022 + +Special thanks to external contributors on this release: @firelizzard18 + +### FEATURES + +- [cli] [\#8300](https://github.com/tendermint/tendermint/pull/8300) Add a tool to update old config files to the latest version [backport [\#8281](https://github.com/tendermint/tendermint/pull/8281)]. (@creachadair) + +### IMPROVEMENTS + +### BUG FIXES + +- [cli] [\#8294](https://github.com/tendermint/tendermint/pull/8294) keymigrate: ensure block hash keys are correctly translated. (@creachadair) +- [cli] [\#8352](https://github.com/tendermint/tendermint/pull/8352) keymigrate: ensure transaction hash keys are correctly translated. (@creachadair) + +## v0.35.3 + +April 8, 2022 + +### FEATURES + +- [cli] [\#8081](https://github.com/tendermint/tendermint/pull/8081) add a safer-to-use `reset-state` command. (@marbar3778) + +### IMPROVEMENTS + +- [consensus] [\#8138](https://github.com/tendermint/tendermint/pull/8138) change lock handling in reactor and handleMsg for RoundState. (@williambanfield) + +### BUG FIXES + +- [cli] [\#8276](https://github.com/tendermint/tendermint/pull/8276) scmigrate: ensure target key is correctly renamed. (@creachadair) + ## v0.35.2 February 28, 2022 @@ -94,7 +127,7 @@ Special thanks to external contributors on this release: @JayT106, - [\#7106](https://github.com/tendermint/tendermint/pull/7106) Revert mutex change to ABCI Clients (@tychoish). - [\#7142](https://github.com/tendermint/tendermint/pull/7142) mempool: remove panic when recheck-tx was not sent to ABCI application (@williambanfield). -- [consensus]: [\#7060](https://github.com/tendermint/tendermint/pull/7060) +- [consensus]: [\#7060](https://github.com/tendermint/tendermint/pull/7060) wait until peerUpdates channel is closed to close remaining peers (@williambanfield) - [privval] [\#5638](https://github.com/tendermint/tendermint/pull/5638) Increase read/write timeout to 5s and calculate ping interval based on it (@JoeKash) - [evidence] [\#6375](https://github.com/tendermint/tendermint/pull/6375) Fix bug with inconsistent LightClientAttackEvidence hashing (cmwaters) @@ -230,6 +263,32 @@ Special thanks to external contributors on this release: @JayT106, - [cmd/tendermint/commands] [\#6623](https://github.com/tendermint/tendermint/pull/6623) replace `$HOME/.some/test/dir` with `t.TempDir` (@tanyabouman) - [statesync] \6807 Implement P2P state provider as an alternative to RPC (@cmwaters) +## v0.34.19 + +### BUG FIXES + +- [cli] [\#8270](https://github.com/tendermint/tendermint/issues/8270) fix reset commands (@alexanderbez). + +## v0.34.18 + +### BREAKING CHANGES + +- CLI/RPC/Config + - [cli] [\#8258](https://github.com/tendermint/tendermint/pull/8258) Fix a bug in the cli that caused `unsafe-reset-all` to panic + +## v0.34.17 + +### BREAKING CHANGES + +- CLI/RPC/Config + + - [cli] [\#8081](https://github.com/tendermint/tendermint/issues/8081) make the reset command safe to use (@marbar3778). + +### BUG FIXES + +- [consensus] [\#8079](https://github.com/tendermint/tendermint/issues/8079) start the timeout ticker before relay (backport #7844) (@creachadair). +- [consensus] [\#7992](https://github.com/tendermint/tendermint/issues/7992) [\#7994](https://github.com/tendermint/tendermint/issues/7994) change lock handling in handleMsg and reactor to alleviate issues gossiping during long ABCI calls (@williambanfield). + ## v0.34.16 Special thanks to external contributors on this release: @yihuang @@ -1374,7 +1433,7 @@ Special thanks to external contributors on this release: @jon-certik, @gracenoah *August 28, 2019* @climber73 wrote the [Writing a Tendermint Core application in Java -(gRPC)](https://github.com/tendermint/tendermint/blob/master/docs/guides/java.md) +(gRPC)](https://github.com/tendermint/tendermint/blob/master/docs/guides/java.md) guide. Special thanks to external contributors on this release: @@ -1953,7 +2012,7 @@ more details. - [rpc] [\#3269](https://github.com/tendermint/tendermint/issues/2826) Limit number of unique clientIDs with open subscriptions. Configurable via `rpc.max_subscription_clients` - [rpc] [\#3269](https://github.com/tendermint/tendermint/issues/2826) Limit number of unique queries a given client can subscribe to at once. Configurable via `rpc.max_subscriptions_per_client`. - [rpc] [\#3435](https://github.com/tendermint/tendermint/issues/3435) Default ReadTimeout and WriteTimeout changed to 10s. WriteTimeout can increased by setting `rpc.timeout_broadcast_tx_commit` in the config. - - [rpc/client] [\#3269](https://github.com/tendermint/tendermint/issues/3269) Update `EventsClient` interface to reflect new pubsub/eventBus API [ADR-33](https://github.com/tendermint/tendermint/blob/develop/docs/architecture/adr-033-pubsub.md). This includes `Subscribe`, `Unsubscribe`, and `UnsubscribeAll` methods. + - [rpc/client] [\#3269](https://github.com/tendermint/tendermint/issues/3269) Update `EventsClient` interface to reflect new pubsub/eventBus API [ADR-33](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-033-pubsub.md). This includes `Subscribe`, `Unsubscribe`, and `UnsubscribeAll` methods. * Apps - [abci] [\#3403](https://github.com/tendermint/tendermint/issues/3403) Remove `time_iota_ms` from BlockParams. This is a @@ -2006,7 +2065,7 @@ more details. - [blockchain] [\#3358](https://github.com/tendermint/tendermint/pull/3358) Fix timer leak in `BlockPool` (@guagualvcha) - [cmd] [\#3408](https://github.com/tendermint/tendermint/issues/3408) Fix `testnet` command's panic when creating non-validator configs (using `--n` flag) (@srmo) - [libs/db/remotedb/grpcdb] [\#3402](https://github.com/tendermint/tendermint/issues/3402) Close Iterator/ReverseIterator after use -- [libs/pubsub] [\#951](https://github.com/tendermint/tendermint/issues/951), [\#1880](https://github.com/tendermint/tendermint/issues/1880) Use non-blocking send when dispatching messages [ADR-33](https://github.com/tendermint/tendermint/blob/develop/docs/architecture/adr-033-pubsub.md) +- [libs/pubsub] [\#951](https://github.com/tendermint/tendermint/issues/951), [\#1880](https://github.com/tendermint/tendermint/issues/1880) Use non-blocking send when dispatching messages [ADR-33](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-033-pubsub.md) - [lite] [\#3364](https://github.com/tendermint/tendermint/issues/3364) Fix `/validators` and `/abci_query` proxy endpoints (@guagualvcha) - [p2p/conn] [\#3347](https://github.com/tendermint/tendermint/issues/3347) Reject all-zero shared secrets in the Diffie-Hellman step of secret-connection @@ -2710,7 +2769,7 @@ Special thanks to external contributors on this release: This release is mostly about the ConsensusParams - removing fields and enforcing MaxGas. It also addresses some issues found via security audit, removes various unused functions from `libs/common`, and implements -[ADR-012](https://github.com/tendermint/tendermint/blob/develop/docs/architecture/adr-012-peer-transport.md). +[ADR-012](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-012-peer-transport.md). BREAKING CHANGES: @@ -2775,7 +2834,7 @@ are affected by a change. A few more breaking changes are in the works - each will come with a clear Architecture Decision Record (ADR) explaining the change. You can review ADRs -[here](https://github.com/tendermint/tendermint/tree/develop/docs/architecture) +[here](https://github.com/tendermint/tendermint/tree/master/docs/architecture) or in the [open Pull Requests](https://github.com/tendermint/tendermint/pulls). You can also check in on the [issues marked as breaking](https://github.com/tendermint/tendermint/issues?q=is%3Aopen+is%3Aissue+label%3Abreaking). @@ -2791,7 +2850,7 @@ BREAKING CHANGES: - [abci] Added address of the original proposer of the block to Header - [abci] Change ABCI Header to match Tendermint exactly - [abci] [\#2159](https://github.com/tendermint/tendermint/issues/2159) Update use of `Validator` (see - [ADR-018](https://github.com/tendermint/tendermint/blob/develop/docs/architecture/adr-018-ABCI-Validators.md)): + [ADR-018](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-018-ABCI-Validators.md)): - Remove PubKey from `Validator` (so it's just Address and Power) - Introduce `ValidatorUpdate` (with just PubKey and Power) - InitChain and EndBlock use ValidatorUpdate @@ -2813,7 +2872,7 @@ BREAKING CHANGES: - [state] [\#1815](https://github.com/tendermint/tendermint/issues/1815) Validator set changes are now delayed by one block (!) - Add NextValidatorSet to State, changes on-disk representation of state - [state] [\#2184](https://github.com/tendermint/tendermint/issues/2184) Enforce ConsensusParams.BlockSize.MaxBytes (See - [ADR-020](https://github.com/tendermint/tendermint/blob/develop/docs/architecture/adr-020-block-size.md)). + [ADR-020](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-020-block-size.md)). - Remove ConsensusParams.BlockSize.MaxTxs - Introduce maximum sizes for all components of a block, including ChainID - [types] Updates to the block Header: @@ -2824,7 +2883,7 @@ BREAKING CHANGES: - [consensus] [\#2203](https://github.com/tendermint/tendermint/issues/2203) Implement BFT time - Timestamp in block must be monotonic and equal the median of timestamps in block's LastCommit - [crypto] [\#2239](https://github.com/tendermint/tendermint/issues/2239) Secp256k1 signature changes (See - [ADR-014](https://github.com/tendermint/tendermint/blob/develop/docs/architecture/adr-014-secp-malleability.md)): + [ADR-014](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-014-secp-malleability.md)): - format changed from DER to `r || s`, both little endian encoded as 32 bytes. - malleability removed by requiring `s` to be in canonical form. @@ -3054,7 +3113,7 @@ BREAKING CHANGES: FEATURES - [cmd] Added metrics (served under `/metrics` using a Prometheus client; disabled by default). See the new `instrumentation` section in the config and - [metrics](https://tendermint.readthedocs.io/projects/tools/en/develop/metrics.html) + [metrics](https://github.com/tendermint/tendermint/blob/master/docs/nodes/metrics.md) guide. - [p2p] Add IPv6 support to peering. - [p2p] Add `external_address` to config to allow specifying the address for @@ -3168,7 +3227,7 @@ BREAKING: FEATURES -- [rpc] the RPC documentation is now published to https://tendermint.github.io/slate +- [rpc] the RPC documentation is now published to `https://tendermint.github.io/slate` - [p2p] AllowDuplicateIP config option to refuse connections from same IP. - true by default for now, false by default in next breaking release - [docs] Add docs for query, tx indexing, events, pubsub @@ -3646,7 +3705,7 @@ containing substructs: `BaseConfig`, `P2PConfig`, `MempoolConfig`, `ConsensusCon - Change some [function and method signatures](https://gist.github.com/ebuchman/640d5fc6c2605f73497992fe107ebe0b) accomodate new config - Logger - - Replace static `log15` logger with a simple interface, and provide a new implementation using `go-kit`. + - Replace static `log15` logger with a simple interface, and provide a new implementation using `go-kit`. See our new [logging library](https://github.com/tendermint/tmlibs/log) and [blog post](https://tendermint.com/blog/abstracting-the-logger-interface-in-go) for more details - Levels `warn` and `notice` are removed (you may need to change them in your `config.toml`!) - Change some [function and method signatures](https://gist.github.com/ebuchman/640d5fc6c2605f73497992fe107ebe0b) to accept a logger diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index e484039d0..65ab5ee3b 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -19,6 +19,8 @@ Special thanks to external contributors on this release: - [rpc] \#7713 Remove unused options for websocket clients. (@creachadair) - [config] \#7930 Add new event subscription options and defaults. (@creachadair) - [rpc] \#7982 Add new Events interface and deprecate Subscribe. (@creachadair) + - [cli] \#8081 make the reset command safe to use by intoducing `reset-state` command. Fixed by \#8259. (@marbar3778, @cmwaters) + - [config] \#8222 default indexer configuration to null. (@creachadair) - Apps @@ -45,6 +47,7 @@ Special thanks to external contributors on this release: - [libs/service] \#7288 Remove SetLogger method on `service.Service` interface. (@tychoish) - [abci/client] \#7607 Simplify client interface (removes most "async" methods). (@creachadair) - [libs/json] \#7673 Remove the libs/json (tmjson) library. (@creachadair) + - [crypto] \#8412 \#8432 Remove `crypto/tmhash` package in favor of small functions in `crypto` package and cleanup of unused functions. (@tychoish) - Blockchain Protocol @@ -60,6 +63,7 @@ Special thanks to external contributors on this release: - [consensus] \#7415 Update proposal validation logic to Prevote nil if a proposal does not meet the conditions for Timelyness per the proposer-based timestamp specification. (@anca) - [consensus] \#7382 Update block validation to no longer require the block timestamp to be the median of the timestamps of the previous commit. (@anca) - [consensus] \#7711 Use the proposer timestamp for the first height instead of the genesis time. Chains will still start consensus at the genesis time. (@anca) +- [cli] \#8281 Add a tool to update old config files to the latest version. (@creachadair) ### IMPROVEMENTS @@ -79,3 +83,6 @@ Special thanks to external contributors on this release: - [light] \#7640 Light Client: fix absence proof verification (@ashcherbakov) - [light] \#7641 Light Client: fix querying against the latest height (@ashcherbakov) - [cli] [#7837](https://github.com/tendermint/tendermint/pull/7837) fix app hash in state rollback. (@yihuang) +- [cli] \#8276 scmigrate: ensure target key is correctly renamed. (@creachadair) +- [cli] \#8294 keymigrate: ensure block hash keys are correctly translated. (@creachadair) +- [cli] \#8352 keymigrate: ensure transaction hash keys are correctly translated. (@creachadair) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 8c5a99203..ec1477adc 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -20,7 +20,7 @@ This code of conduct applies to all projects run by the Tendermint/COSMOS team a * Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works. -* We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behaviour. We interpret the term “harassment” as including the definition in the [Citizen Code of Conduct](http://citizencodeofconduct.org/); if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don’t tolerate behavior that excludes people in socially marginalized groups. +* We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behaviour. We interpret the term “harassment” as including the definition in the [Citizen Code of Conduct](https://github.com/stumpsyn/policies/blob/master/citizen_code_of_conduct.md); if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don’t tolerate behavior that excludes people in socially marginalized groups. * Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel admins or the person mentioned above immediately. Whether you’re a regular contributor or a newcomer, we care about making this community a safe place for you and we’ve got your back. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e4613f84e..bfa56bea6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -105,11 +105,33 @@ specify exactly the dependency you want to update, eg. ## Protobuf -We use [Protocol Buffers](https://developers.google.com/protocol-buffers) along with [gogoproto](https://github.com/gogo/protobuf) to generate code for use across Tendermint Core. +We use [Protocol Buffers](https://developers.google.com/protocol-buffers) along +with [`gogoproto`](https://github.com/gogo/protobuf) to generate code for use +across Tendermint Core. -For linting, checking breaking changes and generating proto stubs, we use [buf](https://buf.build/). If you would like to run linting and check if the changes you have made are breaking then you will need to have docker running locally. Then the linting cmd will be `make proto-lint` and the breaking changes check will be `make proto-check-breaking`. +To generate proto stubs, lint, and check protos for breaking changes, you will +need to install [buf](https://buf.build/) and `gogoproto`. Then, from the root +of the repository, run: -We use [Docker](https://www.docker.com/) to generate the protobuf stubs. To generate the stubs yourself, make sure docker is running then run `make proto-gen`. This command uses the spec repo to get the necessary protobuf files for generating the go code. If you are modifying the proto files manually for changes in the core data structures, you will need to clone them into the go repo and comment out lines 22-37 of the file `./scripts/protocgen.sh`. +```bash +# Lint all of the .proto files in proto/tendermint +make proto-lint + +# Check if any of your local changes (prior to committing to the Git repository) +# are breaking +make proto-check-breaking + +# Generate Go code from the .proto files in proto/tendermint +make proto-gen +``` + +To automatically format `.proto` files, you will need +[`clang-format`](https://clang.llvm.org/docs/ClangFormat.html) installed. Once +installed, you can run: + +```bash +make proto-format +``` ### Visual Studio Code diff --git a/DOCKER/.gitignore b/DOCKER/.gitignore deleted file mode 100644 index 9059c6848..000000000 --- a/DOCKER/.gitignore +++ /dev/null @@ -1 +0,0 @@ -tendermint diff --git a/DOCKER/Dockerfile b/DOCKER/Dockerfile index 2785d7e24..a4792981d 100644 --- a/DOCKER/Dockerfile +++ b/DOCKER/Dockerfile @@ -2,7 +2,7 @@ FROM golang:1.17-alpine as builder RUN apk update && \ apk upgrade && \ - apk --no-cache add make + apk --no-cache add make git COPY / /tendermint WORKDIR /tendermint RUN make build-linux @@ -53,4 +53,3 @@ CMD ["start"] # Expose the data directory as a volume since there's mutable state in there VOLUME [ "$TMHOME" ] - diff --git a/DOCKER/Dockerfile.build_c-amazonlinux b/DOCKER/Dockerfile.build_c-amazonlinux deleted file mode 100644 index df492d188..000000000 --- a/DOCKER/Dockerfile.build_c-amazonlinux +++ /dev/null @@ -1,28 +0,0 @@ -FROM amazonlinux:2 - -RUN yum -y update && \ - yum -y install wget - -RUN wget http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm && \ - rpm -ivh epel-release-latest-7.noarch.rpm - -RUN yum -y groupinstall "Development Tools" -RUN yum -y install leveldb-devel which - -ENV GOVERSION=1.16.5 - -RUN cd /tmp && \ - wget https://dl.google.com/go/go${GOVERSION}.linux-amd64.tar.gz && \ - tar -C /usr/local -xf go${GOVERSION}.linux-amd64.tar.gz && \ - mkdir -p /go/src && \ - mkdir -p /go/bin - -ENV PATH=$PATH:/usr/local/go/bin:/go/bin -ENV GOBIN=/go/bin -ENV GOPATH=/go/src - -RUN mkdir -p /tendermint -WORKDIR /tendermint - -CMD ["/usr/bin/make", "build", "TENDERMINT_BUILD_OPTIONS=cleveldb"] - diff --git a/DOCKER/Dockerfile.testing b/DOCKER/Dockerfile.testing deleted file mode 100644 index 7f86ee180..000000000 --- a/DOCKER/Dockerfile.testing +++ /dev/null @@ -1,16 +0,0 @@ -FROM golang:latest - -# Grab deps (jq, hexdump, xxd, killall) -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - jq bsdmainutils vim-common psmisc netcat - -# Add testing deps for curl -RUN echo 'deb http://httpredir.debian.org/debian testing main non-free contrib' >> /etc/apt/sources.list && \ - apt-get update && \ - apt-get install -y --no-install-recommends curl - -VOLUME /go - -EXPOSE 26656 -EXPOSE 26657 diff --git a/DOCKER/Makefile b/DOCKER/Makefile deleted file mode 100644 index 8f6dff372..000000000 --- a/DOCKER/Makefile +++ /dev/null @@ -1,13 +0,0 @@ -build: - @sh -c "'$(CURDIR)/build.sh'" - -push: - @sh -c "'$(CURDIR)/push.sh'" - -build_testing: - docker build --tag tendermint/testing -f ./Dockerfile.testing . - -build_amazonlinux_buildimage: - docker build -t "tendermint/tendermint:build_c-amazonlinux" -f Dockerfile.build_c-amazonlinux . - -.PHONY: build push build_testing build_amazonlinux_buildimage diff --git a/DOCKER/README.md b/DOCKER/README.md index 63c682811..4aa868e7a 100644 --- a/DOCKER/README.md +++ b/DOCKER/README.md @@ -8,7 +8,7 @@ Official releases can be found [here](https://github.com/tendermint/tendermint/r The Dockerfile for tendermint is not expected to change in the near future. The master file used for all builds can be found [here](https://raw.githubusercontent.com/tendermint/tendermint/master/DOCKER/Dockerfile). -Respective versioned files can be found (replace the Xs with the version number). +Respective versioned files can be found at `https://raw.githubusercontent.com/tendermint/tendermint/vX.XX.XX/DOCKER/Dockerfile` (replace the Xs with the version number). ## Quick reference diff --git a/DOCKER/build.sh b/DOCKER/build.sh deleted file mode 100755 index 2aa42a6cd..000000000 --- a/DOCKER/build.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -set -e - -# Get the tag from the version, or try to figure it out. -if [ -z "$TAG" ]; then - TAG=$(awk -F\" '/TMCoreSemVer =/ { print $2; exit }' < ../version/version.go) -fi -if [ -z "$TAG" ]; then - echo "Please specify a tag." - exit 1 -fi - -TAG_NO_PATCH=${TAG%.*} - -read -p "==> Build 3 docker images with the following tags (latest, $TAG, $TAG_NO_PATCH)? y/n" -n 1 -r -echo -if [[ $REPLY =~ ^[Yy]$ ]] -then - docker build -t "tendermint/tendermint" -t "tendermint/tendermint:$TAG" -t "tendermint/tendermint:$TAG_NO_PATCH" . -fi diff --git a/DOCKER/push.sh b/DOCKER/push.sh deleted file mode 100755 index f228406d4..000000000 --- a/DOCKER/push.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash -set -e - -# Get the tag from the version, or try to figure it out. -if [ -z "$TAG" ]; then - TAG=$(awk -F\" '/TMCoreSemVer =/ { print $2; exit }' < ../version/version.go) -fi -if [ -z "$TAG" ]; then - echo "Please specify a tag." - exit 1 -fi - -TAG_NO_PATCH=${TAG%.*} - -read -p "==> Push 3 docker images with the following tags (latest, $TAG, $TAG_NO_PATCH)? y/n" -n 1 -r -echo -if [[ $REPLY =~ ^[Yy]$ ]] -then - docker push "tendermint/tendermint:latest" - docker push "tendermint/tendermint:$TAG" - docker push "tendermint/tendermint:$TAG_NO_PATCH" -fi diff --git a/Makefile b/Makefile index aac60c902..703220953 100644 --- a/Makefile +++ b/Makefile @@ -13,8 +13,6 @@ endif LD_FLAGS = -X github.com/tendermint/tendermint/version.TMVersion=$(VERSION) BUILD_FLAGS = -mod=readonly -ldflags "$(LD_FLAGS)" -BUILD_IMAGE := ghcr.io/tendermint/docker-build-proto -DOCKER_PROTO_BUILDER := docker run -v $(shell pwd):/workspace --workdir /workspace $(BUILD_IMAGE) CGO_ENABLED ?= 0 # handle nostrip @@ -73,48 +71,52 @@ install: $(BUILDDIR)/: mkdir -p $@ -# The Docker image containing the generator, formatter, and linter. -# This is generated by proto/Dockerfile. To update tools, make changes -# there and run the Build & Push Proto Builder Image workflow. -IMAGE := ghcr.io/tendermint/docker-build-proto:latest -DOCKER_PROTO_BUILDER := docker run -v $(shell pwd):/workspace --workdir /workspace $(IMAGE) -HTTPS_GIT := https://github.com/tendermint/tendermint.git ############################################################################### ### Protobuf ### ############################################################################### -proto-all: proto-lint proto-check-breaking -.PHONY: proto-all +check-proto-deps: +ifeq (,$(shell which buf)) + $(error "buf is required for Protobuf building, linting and breakage checking. See https://docs.buf.build/installation for installation instructions.") +endif +ifeq (,$(shell which protoc-gen-gogofaster)) + $(error "gogofaster plugin for protoc is required. Run 'go install github.com/gogo/protobuf/protoc-gen-gogofaster@latest' to install") +endif +.PHONY: check-proto-deps -proto-gen: +check-proto-format-deps: +ifeq (,$(shell which clang-format)) + $(error "clang-format is required for Protobuf formatting. See instructions for your platform on how to install it.") +endif +.PHONY: check-proto-format-deps + +proto-gen: check-proto-deps @echo "Generating Protobuf files" - @$(DOCKER_PROTO_BUILDER) buf generate --template=./buf.gen.yaml --config ./buf.yaml + @buf generate + @mv ./proto/tendermint/abci/types.pb.go ./abci/types/ .PHONY: proto-gen -# TODO: Should be removed when work on ABCI++ is complete. -# For more information, see https://github.com/tendermint/tendermint/issues/8066 -abci-proto-gen: - ./scripts/abci-gen.sh -.PHONY: abci-proto-gen - -proto-lint: - @$(DOCKER_PROTO_BUILDER) buf lint --error-format=json --config ./buf.yaml +# These targets are provided for convenience and are intended for local +# execution only. +proto-lint: check-proto-deps + @echo "Linting Protobuf files" + @buf lint .PHONY: proto-lint -proto-format: +proto-format: check-proto-format-deps @echo "Formatting Protobuf files" - @$(DOCKER_PROTO_BUILDER) find . -name '*.proto' -path "./proto/*" -exec clang-format -i {} \; + @find . -name '*.proto' -path "./proto/*" -exec clang-format -i {} \; .PHONY: proto-format -proto-check-breaking: - @$(DOCKER_PROTO_BUILDER) buf breaking --against .git --config ./buf.yaml +proto-check-breaking: check-proto-deps + @echo "Checking for breaking changes in Protobuf files against local branch" + @echo "Note: This is only useful if your changes have not yet been committed." + @echo " Otherwise read up on buf's \"breaking\" command usage:" + @echo " https://docs.buf.build/breaking/usage" + @buf breaking --against ".git" .PHONY: proto-check-breaking -proto-check-breaking-ci: - @$(DOCKER_PROTO_BUILDER) buf breaking --against $(HTTPS_GIT) --config ./buf.yaml -.PHONY: proto-check-breaking-ci - ############################################################################### ### Build ABCI ### ############################################################################### @@ -170,7 +172,7 @@ go.sum: go.mod draw_deps: @# requires brew install graphviz or apt-get install graphviz - go get github.com/RobotsAndPencils/goviz + go install github.com/RobotsAndPencils/goviz@latest @goviz -i github.com/tendermint/tendermint/cmd/tendermint -d 3 | dot -Tpng -o dependency-graph.png .PHONY: draw_deps @@ -235,10 +237,8 @@ build-docs: ### Docker image ### ############################################################################### -build-docker: build-linux - cp $(BUILDDIR)/tendermint DOCKER/tendermint +build-docker: docker build --label=tendermint --tag="tendermint/tendermint" -f DOCKER/Dockerfile . - rm -rf DOCKER/tendermint .PHONY: build-docker @@ -335,4 +335,3 @@ split-test-packages:$(BUILDDIR)/packages.txt split -d -n l/$(NUM_SPLIT) $< $<. test-group-%:split-test-packages cat $(BUILDDIR)/packages.txt.$* | xargs go test -mod=readonly -timeout=5m -race -coverprofile=$(BUILDDIR)/$*.profile.out - diff --git a/README.md b/README.md index 9400e6b12..3e375791f 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,7 @@ We keep a public up-to-date version of our roadmap [here](./docs/roadmap/roadmap - [Terra](https://www.terra.money/) - [Celestia](https://celestia.org/) - [Anoma](https://anoma.network/) +- [Vocdoni](https://docs.vocdoni.io/) ### Research diff --git a/UPGRADING.md b/UPGRADING.md index 49973ea52..28e44e58c 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -26,6 +26,31 @@ application concern so be very sure to test the application thoroughly using realistic workloads and the race detector to ensure your applications remains correct. +### Config Changes + +- We have added a new, experimental tool to help operators migrate + configuration files created by previous versions of Tendermint. + To try this tool, run: + + ```shell + # Install the tool. + go install github.com/tendermint/tendermint/scripts/confix@latest + + # Run the tool with the old configuration file as input. + # Replace the -config argument with your path. + confix -config ~/.tendermint/config/config.toml -out updated.toml + ``` + + This tool should be able to update configurations from v0.34 and v0.35. We + plan to extend it to handle older configuration files in the future. For now, + it will report an error (without making any changes) if it does not recognize + the version that created the file. + +- The default configuration for a newly-created node now disables indexing for + ABCI event metadata. Existing node configurations that already have indexing + turned on are not affected. Operators who wish to enable indexing for a new + node, however, must now edit the `config.toml` explicitly. + ### RPC Changes Tendermint v0.36 adds a new RPC event subscription API. The existing event @@ -63,6 +88,84 @@ callback. For more detailed information, see [ADR 075](https://tinyurl.com/adr075) which defines and describes the new API in detail. +### Timeout Parameter Changes + +Tendermint v0.36 updates how the Tendermint consensus timing parameters are +configured. These parameters, `timeout-propose`, `timeout-propose-delta`, +`timeout-prevote`, `timeout-prevote-delta`, `timeout-precommit`, +`timeout-precommit-delta`, `timeout-commit`, and `skip-timeout-commit`, were +previously configured in `config.toml`. These timing parameters have moved and +are no longer configured in the `config.toml` file. These parameters have been +migrated into the `ConsensusParameters`. Nodes with these parameters set in the +local configuration file will see a warning logged on startup indicating that +these parameters are no longer used. + +These parameters have also been pared-down. There are no longer separate +parameters for both the `prevote` and `precommit` phases of Tendermint. The +separate `timeout-prevote` and `timeout-precommit` parameters have been merged +into a single `timeout-vote` parameter that configures both of these similar +phases of the consensus protocol. + +A set of reasonable defaults have been put in place for these new parameters +that will take effect when the node starts up in version v0.36. New chains +created using v0.36 and beyond will be able to configure these parameters in the +chain's `genesis.json` file. Chains that upgrade to v0.36 from a previous +compatible version of Tendermint will begin running with the default values. +Upgrading applications that wish to use different values from the defaults for +these parameters may do so by setting the `ConsensusParams.Timeout` field of the +`FinalizeBlock` `ABCI` response. + +As a safety measure in case of unusual timing issues during the upgrade to +v0.36, an operator may override the consensus timeout values for a single node. +Note, however, that these overrides will be removed in Tendermint v0.37. See +[configuration](https://github.com/tendermint/tendermint/blob/master/docs/nodes/configuration.md) +for more information about these overrides. + +For more discussion of this, see [ADR 074](https://tinyurl.com/adr074), which +lays out the reasoning for the changes as well as [RFC +009](https://tinyurl.com/rfc009) for a discussion of the complexities of +upgrading consensus parameters. + +### CLI Changes + +The functionality around resetting a node has been extended to make it safer. The +`unsafe-reset-all` command has been replaced by a `reset` command with four +subcommands: `blockchain`, `peers`, `unsafe-signer` and `unsafe-all`. + +- `tendermint reset blockchain`: Clears a node of all blocks, consensus state, evidence, + and indexed transactions. NOTE: This command does not reset application state. + If you need to rollback the last application state (to recover from application + nondeterminism), see instead the `tendermint rollback` command. +- `tendermint reset peers`: Clears the peer store, which persists information on peers used + by the networking layer. This can be used to get rid of stale addresses or to switch + to a predefined set of static peers. +- `tendermint reset unsafe-signer`: Resets the watermark level of the PrivVal File signer + allowing it to sign votes from the genesis height. This should only be used in testing as + it can lead to the node double signing. +- `tendermint reset unsafe-all`: A summation of the other three commands. This will delete + the entire `data` directory which may include application data as well. + +### Go API Changes + +#### `crypto` Package Cleanup + +The `github.com/tendermint/tendermint/crypto/tmhash` package was removed +to improve clarity. Users are encouraged to use the standard library +`crypto/sha256` package directly. However, as a convenience, some constants +and one function have moved to the Tendermint `crypto` package: + +- The `crypto.Checksum` function returns the sha256 checksum of a + byteslice. This is a wrapper around `sha256.Sum265` from the + standard libary, but provided as a function to ease type + requirements (the library function returns a `[32]byte` rather than + a `[]byte`). +- `tmhash.TruncatedSize` is now `crypto.AddressSize` which was + previously an alias for the same value. +- `tmhash.Size` and `tmhash.BlockSize` are now `crypto.HashSize` and + `crypto.HashSize`. +- `tmhash.SumTruncated` is now available via `crypto.AddressHash` or by + `crypto.Checksum(<...>)[:crypto.AddressSize]` + ## v0.35 ### ABCI Changes @@ -177,11 +280,13 @@ the full RPC interface provided as direct function calls. Import the the node service as in the following: ```go - node := node.NewDefault() //construct the node object - // start and set up the node service +logger := log.NewNopLogger() - client := local.New(node.(local.NodeService)) - // use client object to interact with the node +// Construct and start up a node with default settings. +node := node.NewDefault(logger) + +// Construct a local (in-memory) RPC client to the node. +client := local.New(logger, node.(local.NodeService)) ``` ### gRPC Support diff --git a/abci/client/client.go b/abci/client/client.go index de72a0606..4508dcbc8 100644 --- a/abci/client/client.go +++ b/abci/client/client.go @@ -17,35 +17,18 @@ const ( //go:generate ../../scripts/mockery_generate.sh Client -// Client defines an interface for an ABCI client. -// -// All methods return the appropriate protobuf ResponseXxx struct and -// an error. +// Client defines the interface for an ABCI client. // // NOTE these are client errors, eg. ABCI socket connectivity issues. // Application-related errors are reflected in response via ABCI error codes -// and logs. +// and (potentially) error response. type Client interface { service.Service + types.Application Error() error - Flush(context.Context) error - Echo(ctx context.Context, msg string) (*types.ResponseEcho, error) - Info(context.Context, types.RequestInfo) (*types.ResponseInfo, error) - CheckTx(context.Context, types.RequestCheckTx) (*types.ResponseCheckTx, error) - Query(context.Context, types.RequestQuery) (*types.ResponseQuery, error) - Commit(context.Context) (*types.ResponseCommit, error) - InitChain(context.Context, types.RequestInitChain) (*types.ResponseInitChain, error) - PrepareProposal(context.Context, types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) - ProcessProposal(context.Context, types.RequestProcessProposal) (*types.ResponseProcessProposal, error) - ExtendVote(context.Context, types.RequestExtendVote) (*types.ResponseExtendVote, error) - VerifyVoteExtension(context.Context, types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) - FinalizeBlock(context.Context, types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) - ListSnapshots(context.Context, types.RequestListSnapshots) (*types.ResponseListSnapshots, error) - OfferSnapshot(context.Context, types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) - LoadSnapshotChunk(context.Context, types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) - ApplySnapshotChunk(context.Context, types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) + Echo(context.Context, string) (*types.ResponseEcho, error) } //---------------------------------------- diff --git a/abci/client/grpc_client.go b/abci/client/grpc_client.go index 251939253..1e163056d 100644 --- a/abci/client/grpc_client.go +++ b/abci/client/grpc_client.go @@ -52,6 +52,9 @@ func dialerFunc(ctx context.Context, addr string) (net.Conn, error) { } func (cli *grpcClient) OnStart(ctx context.Context) error { + timer := time.NewTimer(0) + defer timer.Stop() + RETRY_LOOP: for { conn, err := grpc.Dial(cli.addr, @@ -63,8 +66,13 @@ RETRY_LOOP: return err } cli.logger.Error(fmt.Sprintf("abci.grpcClient failed to connect to %v. Retrying...\n", cli.addr), "err", err) - time.Sleep(time.Second * dialRetryIntervalSeconds) - continue RETRY_LOOP + timer.Reset(time.Second * dialRetryIntervalSeconds) + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + continue RETRY_LOOP + } } cli.logger.Info("Dialed server. Waiting for echo.", "addr", cli.addr) @@ -82,7 +90,13 @@ RETRY_LOOP: } cli.logger.Error("Echo failed", "err", err) - time.Sleep(time.Second * echoRetryIntervalSeconds) + timer.Reset(time.Second * echoRetryIntervalSeconds) + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + continue ENSURE_CONNECTED + } } cli.client = client @@ -114,15 +128,15 @@ func (cli *grpcClient) Echo(ctx context.Context, msg string) (*types.ResponseEch return cli.client.Echo(ctx, types.ToRequestEcho(msg).GetEcho(), grpc.WaitForReady(true)) } -func (cli *grpcClient) Info(ctx context.Context, params types.RequestInfo) (*types.ResponseInfo, error) { +func (cli *grpcClient) Info(ctx context.Context, params *types.RequestInfo) (*types.ResponseInfo, error) { return cli.client.Info(ctx, types.ToRequestInfo(params).GetInfo(), grpc.WaitForReady(true)) } -func (cli *grpcClient) CheckTx(ctx context.Context, params types.RequestCheckTx) (*types.ResponseCheckTx, error) { +func (cli *grpcClient) CheckTx(ctx context.Context, params *types.RequestCheckTx) (*types.ResponseCheckTx, error) { return cli.client.CheckTx(ctx, types.ToRequestCheckTx(params).GetCheckTx(), grpc.WaitForReady(true)) } -func (cli *grpcClient) Query(ctx context.Context, params types.RequestQuery) (*types.ResponseQuery, error) { +func (cli *grpcClient) Query(ctx context.Context, params *types.RequestQuery) (*types.ResponseQuery, error) { return cli.client.Query(ctx, types.ToRequestQuery(params).GetQuery(), grpc.WaitForReady(true)) } @@ -130,42 +144,42 @@ func (cli *grpcClient) Commit(ctx context.Context) (*types.ResponseCommit, error return cli.client.Commit(ctx, types.ToRequestCommit().GetCommit(), grpc.WaitForReady(true)) } -func (cli *grpcClient) InitChain(ctx context.Context, params types.RequestInitChain) (*types.ResponseInitChain, error) { +func (cli *grpcClient) InitChain(ctx context.Context, params *types.RequestInitChain) (*types.ResponseInitChain, error) { return cli.client.InitChain(ctx, types.ToRequestInitChain(params).GetInitChain(), grpc.WaitForReady(true)) } -func (cli *grpcClient) ListSnapshots(ctx context.Context, params types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { +func (cli *grpcClient) ListSnapshots(ctx context.Context, params *types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { return cli.client.ListSnapshots(ctx, types.ToRequestListSnapshots(params).GetListSnapshots(), grpc.WaitForReady(true)) } -func (cli *grpcClient) OfferSnapshot(ctx context.Context, params types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { +func (cli *grpcClient) OfferSnapshot(ctx context.Context, params *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { return cli.client.OfferSnapshot(ctx, types.ToRequestOfferSnapshot(params).GetOfferSnapshot(), grpc.WaitForReady(true)) } -func (cli *grpcClient) LoadSnapshotChunk(ctx context.Context, params types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { +func (cli *grpcClient) LoadSnapshotChunk(ctx context.Context, params *types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { return cli.client.LoadSnapshotChunk(ctx, types.ToRequestLoadSnapshotChunk(params).GetLoadSnapshotChunk(), grpc.WaitForReady(true)) } -func (cli *grpcClient) ApplySnapshotChunk(ctx context.Context, params types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { +func (cli *grpcClient) ApplySnapshotChunk(ctx context.Context, params *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { return cli.client.ApplySnapshotChunk(ctx, types.ToRequestApplySnapshotChunk(params).GetApplySnapshotChunk(), grpc.WaitForReady(true)) } -func (cli *grpcClient) PrepareProposal(ctx context.Context, params types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { +func (cli *grpcClient) PrepareProposal(ctx context.Context, params *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { return cli.client.PrepareProposal(ctx, types.ToRequestPrepareProposal(params).GetPrepareProposal(), grpc.WaitForReady(true)) } -func (cli *grpcClient) ProcessProposal(ctx context.Context, params types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { +func (cli *grpcClient) ProcessProposal(ctx context.Context, params *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { return cli.client.ProcessProposal(ctx, types.ToRequestProcessProposal(params).GetProcessProposal(), grpc.WaitForReady(true)) } -func (cli *grpcClient) ExtendVote(ctx context.Context, params types.RequestExtendVote) (*types.ResponseExtendVote, error) { +func (cli *grpcClient) ExtendVote(ctx context.Context, params *types.RequestExtendVote) (*types.ResponseExtendVote, error) { return cli.client.ExtendVote(ctx, types.ToRequestExtendVote(params).GetExtendVote(), grpc.WaitForReady(true)) } -func (cli *grpcClient) VerifyVoteExtension(ctx context.Context, params types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) { +func (cli *grpcClient) VerifyVoteExtension(ctx context.Context, params *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) { return cli.client.VerifyVoteExtension(ctx, types.ToRequestVerifyVoteExtension(params).GetVerifyVoteExtension(), grpc.WaitForReady(true)) } -func (cli *grpcClient) FinalizeBlock(ctx context.Context, params types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { +func (cli *grpcClient) FinalizeBlock(ctx context.Context, params *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { return cli.client.FinalizeBlock(ctx, types.ToRequestFinalizeBlock(params).GetFinalizeBlock(), grpc.WaitForReady(true)) } diff --git a/abci/client/local_client.go b/abci/client/local_client.go index 938ed2ab6..1002c64e8 100644 --- a/abci/client/local_client.go +++ b/abci/client/local_client.go @@ -34,81 +34,7 @@ func NewLocalClient(logger log.Logger, app types.Application) Client { func (*localClient) OnStart(context.Context) error { return nil } func (*localClient) OnStop() {} func (*localClient) Error() error { return nil } - -//------------------------------------------------------- - -func (*localClient) Flush(context.Context) error { return nil } - -func (app *localClient) Echo(_ context.Context, msg string) (*types.ResponseEcho, error) { +func (*localClient) Flush(context.Context) error { return nil } +func (*localClient) Echo(_ context.Context, msg string) (*types.ResponseEcho, error) { return &types.ResponseEcho{Message: msg}, nil } - -func (app *localClient) Info(ctx context.Context, req types.RequestInfo) (*types.ResponseInfo, error) { - res := app.Application.Info(req) - return &res, nil -} - -func (app *localClient) CheckTx(_ context.Context, req types.RequestCheckTx) (*types.ResponseCheckTx, error) { - res := app.Application.CheckTx(req) - return &res, nil -} - -func (app *localClient) Query(_ context.Context, req types.RequestQuery) (*types.ResponseQuery, error) { - res := app.Application.Query(req) - return &res, nil -} - -func (app *localClient) Commit(ctx context.Context) (*types.ResponseCommit, error) { - res := app.Application.Commit() - return &res, nil -} - -func (app *localClient) InitChain(_ context.Context, req types.RequestInitChain) (*types.ResponseInitChain, error) { - res := app.Application.InitChain(req) - return &res, nil -} - -func (app *localClient) ListSnapshots(_ context.Context, req types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { - res := app.Application.ListSnapshots(req) - return &res, nil -} - -func (app *localClient) OfferSnapshot(_ context.Context, req types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { - res := app.Application.OfferSnapshot(req) - return &res, nil -} - -func (app *localClient) LoadSnapshotChunk(_ context.Context, req types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { - res := app.Application.LoadSnapshotChunk(req) - return &res, nil -} - -func (app *localClient) ApplySnapshotChunk(_ context.Context, req types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { - res := app.Application.ApplySnapshotChunk(req) - return &res, nil -} - -func (app *localClient) PrepareProposal(_ context.Context, req types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { - res := app.Application.PrepareProposal(req) - return &res, nil -} - -func (app *localClient) ProcessProposal(_ context.Context, req types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { - res := app.Application.ProcessProposal(req) - return &res, nil -} - -func (app *localClient) ExtendVote(_ context.Context, req types.RequestExtendVote) (*types.ResponseExtendVote, error) { - res := app.Application.ExtendVote(req) - return &res, nil -} - -func (app *localClient) VerifyVoteExtension(_ context.Context, req types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) { - res := app.Application.VerifyVoteExtension(req) - return &res, nil -} - -func (app *localClient) FinalizeBlock(_ context.Context, req types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { - res := app.Application.FinalizeBlock(req) - return &res, nil -} diff --git a/abci/client/mocks/client.go b/abci/client/mocks/client.go index 37df53979..e5f2898f3 100644 --- a/abci/client/mocks/client.go +++ b/abci/client/mocks/client.go @@ -4,8 +4,10 @@ package mocks import ( context "context" + testing "testing" mock "github.com/stretchr/testify/mock" + types "github.com/tendermint/tendermint/abci/types" ) @@ -15,11 +17,11 @@ type Client struct { } // ApplySnapshotChunk provides a mock function with given fields: _a0, _a1 -func (_m *Client) ApplySnapshotChunk(_a0 context.Context, _a1 types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { +func (_m *Client) ApplySnapshotChunk(_a0 context.Context, _a1 *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseApplySnapshotChunk - if rf, ok := ret.Get(0).(func(context.Context, types.RequestApplySnapshotChunk) *types.ResponseApplySnapshotChunk); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestApplySnapshotChunk) *types.ResponseApplySnapshotChunk); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -28,7 +30,7 @@ func (_m *Client) ApplySnapshotChunk(_a0 context.Context, _a1 types.RequestApply } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestApplySnapshotChunk) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestApplySnapshotChunk) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -38,11 +40,11 @@ func (_m *Client) ApplySnapshotChunk(_a0 context.Context, _a1 types.RequestApply } // CheckTx provides a mock function with given fields: _a0, _a1 -func (_m *Client) CheckTx(_a0 context.Context, _a1 types.RequestCheckTx) (*types.ResponseCheckTx, error) { +func (_m *Client) CheckTx(_a0 context.Context, _a1 *types.RequestCheckTx) (*types.ResponseCheckTx, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseCheckTx - if rf, ok := ret.Get(0).(func(context.Context, types.RequestCheckTx) *types.ResponseCheckTx); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestCheckTx) *types.ResponseCheckTx); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -51,7 +53,7 @@ func (_m *Client) CheckTx(_a0 context.Context, _a1 types.RequestCheckTx) (*types } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestCheckTx) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestCheckTx) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -83,13 +85,13 @@ func (_m *Client) Commit(_a0 context.Context) (*types.ResponseCommit, error) { return r0, r1 } -// Echo provides a mock function with given fields: ctx, msg -func (_m *Client) Echo(ctx context.Context, msg string) (*types.ResponseEcho, error) { - ret := _m.Called(ctx, msg) +// Echo provides a mock function with given fields: _a0, _a1 +func (_m *Client) Echo(_a0 context.Context, _a1 string) (*types.ResponseEcho, error) { + ret := _m.Called(_a0, _a1) var r0 *types.ResponseEcho if rf, ok := ret.Get(0).(func(context.Context, string) *types.ResponseEcho); ok { - r0 = rf(ctx, msg) + r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*types.ResponseEcho) @@ -98,7 +100,7 @@ func (_m *Client) Echo(ctx context.Context, msg string) (*types.ResponseEcho, er var r1 error if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, msg) + r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) } @@ -121,11 +123,11 @@ func (_m *Client) Error() error { } // ExtendVote provides a mock function with given fields: _a0, _a1 -func (_m *Client) ExtendVote(_a0 context.Context, _a1 types.RequestExtendVote) (*types.ResponseExtendVote, error) { +func (_m *Client) ExtendVote(_a0 context.Context, _a1 *types.RequestExtendVote) (*types.ResponseExtendVote, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseExtendVote - if rf, ok := ret.Get(0).(func(context.Context, types.RequestExtendVote) *types.ResponseExtendVote); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestExtendVote) *types.ResponseExtendVote); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -134,7 +136,7 @@ func (_m *Client) ExtendVote(_a0 context.Context, _a1 types.RequestExtendVote) ( } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestExtendVote) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestExtendVote) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -144,11 +146,11 @@ func (_m *Client) ExtendVote(_a0 context.Context, _a1 types.RequestExtendVote) ( } // FinalizeBlock provides a mock function with given fields: _a0, _a1 -func (_m *Client) FinalizeBlock(_a0 context.Context, _a1 types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { +func (_m *Client) FinalizeBlock(_a0 context.Context, _a1 *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseFinalizeBlock - if rf, ok := ret.Get(0).(func(context.Context, types.RequestFinalizeBlock) *types.ResponseFinalizeBlock); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestFinalizeBlock) *types.ResponseFinalizeBlock); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -157,7 +159,7 @@ func (_m *Client) FinalizeBlock(_a0 context.Context, _a1 types.RequestFinalizeBl } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestFinalizeBlock) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestFinalizeBlock) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -181,11 +183,11 @@ func (_m *Client) Flush(_a0 context.Context) error { } // Info provides a mock function with given fields: _a0, _a1 -func (_m *Client) Info(_a0 context.Context, _a1 types.RequestInfo) (*types.ResponseInfo, error) { +func (_m *Client) Info(_a0 context.Context, _a1 *types.RequestInfo) (*types.ResponseInfo, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseInfo - if rf, ok := ret.Get(0).(func(context.Context, types.RequestInfo) *types.ResponseInfo); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestInfo) *types.ResponseInfo); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -194,7 +196,7 @@ func (_m *Client) Info(_a0 context.Context, _a1 types.RequestInfo) (*types.Respo } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestInfo) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestInfo) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -204,11 +206,11 @@ func (_m *Client) Info(_a0 context.Context, _a1 types.RequestInfo) (*types.Respo } // InitChain provides a mock function with given fields: _a0, _a1 -func (_m *Client) InitChain(_a0 context.Context, _a1 types.RequestInitChain) (*types.ResponseInitChain, error) { +func (_m *Client) InitChain(_a0 context.Context, _a1 *types.RequestInitChain) (*types.ResponseInitChain, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseInitChain - if rf, ok := ret.Get(0).(func(context.Context, types.RequestInitChain) *types.ResponseInitChain); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestInitChain) *types.ResponseInitChain); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -217,7 +219,7 @@ func (_m *Client) InitChain(_a0 context.Context, _a1 types.RequestInitChain) (*t } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestInitChain) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestInitChain) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -241,11 +243,11 @@ func (_m *Client) IsRunning() bool { } // ListSnapshots provides a mock function with given fields: _a0, _a1 -func (_m *Client) ListSnapshots(_a0 context.Context, _a1 types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { +func (_m *Client) ListSnapshots(_a0 context.Context, _a1 *types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseListSnapshots - if rf, ok := ret.Get(0).(func(context.Context, types.RequestListSnapshots) *types.ResponseListSnapshots); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestListSnapshots) *types.ResponseListSnapshots); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -254,7 +256,7 @@ func (_m *Client) ListSnapshots(_a0 context.Context, _a1 types.RequestListSnapsh } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestListSnapshots) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestListSnapshots) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -264,11 +266,11 @@ func (_m *Client) ListSnapshots(_a0 context.Context, _a1 types.RequestListSnapsh } // LoadSnapshotChunk provides a mock function with given fields: _a0, _a1 -func (_m *Client) LoadSnapshotChunk(_a0 context.Context, _a1 types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { +func (_m *Client) LoadSnapshotChunk(_a0 context.Context, _a1 *types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseLoadSnapshotChunk - if rf, ok := ret.Get(0).(func(context.Context, types.RequestLoadSnapshotChunk) *types.ResponseLoadSnapshotChunk); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestLoadSnapshotChunk) *types.ResponseLoadSnapshotChunk); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -277,7 +279,7 @@ func (_m *Client) LoadSnapshotChunk(_a0 context.Context, _a1 types.RequestLoadSn } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestLoadSnapshotChunk) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestLoadSnapshotChunk) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -287,11 +289,11 @@ func (_m *Client) LoadSnapshotChunk(_a0 context.Context, _a1 types.RequestLoadSn } // OfferSnapshot provides a mock function with given fields: _a0, _a1 -func (_m *Client) OfferSnapshot(_a0 context.Context, _a1 types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { +func (_m *Client) OfferSnapshot(_a0 context.Context, _a1 *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseOfferSnapshot - if rf, ok := ret.Get(0).(func(context.Context, types.RequestOfferSnapshot) *types.ResponseOfferSnapshot); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestOfferSnapshot) *types.ResponseOfferSnapshot); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -300,7 +302,7 @@ func (_m *Client) OfferSnapshot(_a0 context.Context, _a1 types.RequestOfferSnaps } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestOfferSnapshot) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestOfferSnapshot) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -310,11 +312,11 @@ func (_m *Client) OfferSnapshot(_a0 context.Context, _a1 types.RequestOfferSnaps } // PrepareProposal provides a mock function with given fields: _a0, _a1 -func (_m *Client) PrepareProposal(_a0 context.Context, _a1 types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { +func (_m *Client) PrepareProposal(_a0 context.Context, _a1 *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponsePrepareProposal - if rf, ok := ret.Get(0).(func(context.Context, types.RequestPrepareProposal) *types.ResponsePrepareProposal); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestPrepareProposal) *types.ResponsePrepareProposal); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -323,7 +325,7 @@ func (_m *Client) PrepareProposal(_a0 context.Context, _a1 types.RequestPrepareP } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestPrepareProposal) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestPrepareProposal) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -333,11 +335,11 @@ func (_m *Client) PrepareProposal(_a0 context.Context, _a1 types.RequestPrepareP } // ProcessProposal provides a mock function with given fields: _a0, _a1 -func (_m *Client) ProcessProposal(_a0 context.Context, _a1 types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { +func (_m *Client) ProcessProposal(_a0 context.Context, _a1 *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseProcessProposal - if rf, ok := ret.Get(0).(func(context.Context, types.RequestProcessProposal) *types.ResponseProcessProposal); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestProcessProposal) *types.ResponseProcessProposal); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -346,7 +348,7 @@ func (_m *Client) ProcessProposal(_a0 context.Context, _a1 types.RequestProcessP } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestProcessProposal) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestProcessProposal) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -356,11 +358,11 @@ func (_m *Client) ProcessProposal(_a0 context.Context, _a1 types.RequestProcessP } // Query provides a mock function with given fields: _a0, _a1 -func (_m *Client) Query(_a0 context.Context, _a1 types.RequestQuery) (*types.ResponseQuery, error) { +func (_m *Client) Query(_a0 context.Context, _a1 *types.RequestQuery) (*types.ResponseQuery, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseQuery - if rf, ok := ret.Get(0).(func(context.Context, types.RequestQuery) *types.ResponseQuery); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestQuery) *types.ResponseQuery); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -369,7 +371,7 @@ func (_m *Client) Query(_a0 context.Context, _a1 types.RequestQuery) (*types.Res } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestQuery) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestQuery) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -393,11 +395,11 @@ func (_m *Client) Start(_a0 context.Context) error { } // VerifyVoteExtension provides a mock function with given fields: _a0, _a1 -func (_m *Client) VerifyVoteExtension(_a0 context.Context, _a1 types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) { +func (_m *Client) VerifyVoteExtension(_a0 context.Context, _a1 *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseVerifyVoteExtension - if rf, ok := ret.Get(0).(func(context.Context, types.RequestVerifyVoteExtension) *types.ResponseVerifyVoteExtension); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestVerifyVoteExtension) *types.ResponseVerifyVoteExtension); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -406,7 +408,7 @@ func (_m *Client) VerifyVoteExtension(_a0 context.Context, _a1 types.RequestVeri } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestVerifyVoteExtension) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestVerifyVoteExtension) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -419,3 +421,13 @@ func (_m *Client) VerifyVoteExtension(_a0 context.Context, _a1 types.RequestVeri func (_m *Client) Wait() { _m.Called() } + +// NewClient creates a new instance of Client. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewClient(t testing.TB) *Client { + mock := &Client{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/abci/client/socket_client.go b/abci/client/socket_client.go index dcf5fa519..aa4fdcbe9 100644 --- a/abci/client/socket_client.go +++ b/abci/client/socket_client.go @@ -64,6 +64,8 @@ func (cli *socketClient) OnStart(ctx context.Context) error { err error conn net.Conn ) + timer := time.NewTimer(0) + defer timer.Stop() for { conn, err = tmnet.Connect(cli.addr) @@ -73,8 +75,15 @@ func (cli *socketClient) OnStart(ctx context.Context) error { } cli.logger.Error(fmt.Sprintf("abci.socketClient failed to connect to %v. Retrying after %vs...", cli.addr, dialRetryIntervalSeconds), "err", err) - time.Sleep(time.Second * dialRetryIntervalSeconds) - continue + + timer.Reset(time.Second * dialRetryIntervalSeconds) + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + continue + } + } cli.conn = conn @@ -90,11 +99,7 @@ func (cli *socketClient) OnStop() { if cli.conn != nil { cli.conn.Close() } - - // this timeout is arbitrary. - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - cli.drainQueue(ctx) + cli.drainQueue() } // Error returns an error if the client was stopped abruptly. @@ -113,12 +118,6 @@ func (cli *socketClient) sendRequestsRoutine(ctx context.Context, conn io.Writer case <-ctx.Done(): return case reqres := <-cli.reqQueue: - if ctx.Err() != nil { - return - } - - cli.willSendReq(reqres) - if err := types.WriteMessage(reqres.Request, bw); err != nil { cli.stopForError(fmt.Errorf("write to buffer: %w", err)) return @@ -162,6 +161,11 @@ func (cli *socketClient) recvResponseRoutine(ctx context.Context, conn io.Reader func (cli *socketClient) willSendReq(reqres *requestAndResponse) { cli.mtx.Lock() defer cli.mtx.Unlock() + + if !cli.IsRunning() { + return + } + cli.reqSent.PushBack(reqres) } @@ -189,138 +193,13 @@ func (cli *socketClient) didRecvResponse(res *types.Response) error { //---------------------------------------- -func (cli *socketClient) Flush(ctx context.Context) error { - _, err := cli.doRequest(ctx, types.ToRequestFlush()) - if err != nil { - return err - } - return nil -} - -func (cli *socketClient) Echo(ctx context.Context, msg string) (*types.ResponseEcho, error) { - res, err := cli.doRequest(ctx, types.ToRequestEcho(msg)) - if err != nil { - return nil, err - } - return res.GetEcho(), nil -} - -func (cli *socketClient) Info(ctx context.Context, req types.RequestInfo) (*types.ResponseInfo, error) { - res, err := cli.doRequest(ctx, types.ToRequestInfo(req)) - if err != nil { - return nil, err - } - return res.GetInfo(), nil -} - -func (cli *socketClient) CheckTx(ctx context.Context, req types.RequestCheckTx) (*types.ResponseCheckTx, error) { - res, err := cli.doRequest(ctx, types.ToRequestCheckTx(req)) - if err != nil { - return nil, err - } - return res.GetCheckTx(), nil -} - -func (cli *socketClient) Query(ctx context.Context, req types.RequestQuery) (*types.ResponseQuery, error) { - res, err := cli.doRequest(ctx, types.ToRequestQuery(req)) - if err != nil { - return nil, err - } - return res.GetQuery(), nil -} - -func (cli *socketClient) Commit(ctx context.Context) (*types.ResponseCommit, error) { - res, err := cli.doRequest(ctx, types.ToRequestCommit()) - if err != nil { - return nil, err - } - return res.GetCommit(), nil -} - -func (cli *socketClient) InitChain(ctx context.Context, req types.RequestInitChain) (*types.ResponseInitChain, error) { - res, err := cli.doRequest(ctx, types.ToRequestInitChain(req)) - if err != nil { - return nil, err - } - return res.GetInitChain(), nil -} - -func (cli *socketClient) ListSnapshots(ctx context.Context, req types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { - res, err := cli.doRequest(ctx, types.ToRequestListSnapshots(req)) - if err != nil { - return nil, err - } - return res.GetListSnapshots(), nil -} - -func (cli *socketClient) OfferSnapshot(ctx context.Context, req types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { - res, err := cli.doRequest(ctx, types.ToRequestOfferSnapshot(req)) - if err != nil { - return nil, err - } - return res.GetOfferSnapshot(), nil -} - -func (cli *socketClient) LoadSnapshotChunk(ctx context.Context, req types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { - res, err := cli.doRequest(ctx, types.ToRequestLoadSnapshotChunk(req)) - if err != nil { - return nil, err - } - return res.GetLoadSnapshotChunk(), nil -} - -func (cli *socketClient) ApplySnapshotChunk(ctx context.Context, req types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { - res, err := cli.doRequest(ctx, types.ToRequestApplySnapshotChunk(req)) - if err != nil { - return nil, err - } - return res.GetApplySnapshotChunk(), nil -} - -func (cli *socketClient) PrepareProposal(ctx context.Context, req types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { - res, err := cli.doRequest(ctx, types.ToRequestPrepareProposal(req)) - if err != nil { - return nil, err - } - return res.GetPrepareProposal(), nil -} - -func (cli *socketClient) ProcessProposal(ctx context.Context, req types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { - res, err := cli.doRequest(ctx, types.ToRequestProcessProposal(req)) - if err != nil { - return nil, err - } - return res.GetProcessProposal(), nil -} - -func (cli *socketClient) ExtendVote(ctx context.Context, req types.RequestExtendVote) (*types.ResponseExtendVote, error) { - res, err := cli.doRequest(ctx, types.ToRequestExtendVote(req)) - if err != nil { - return nil, err - } - return res.GetExtendVote(), nil -} - -func (cli *socketClient) VerifyVoteExtension(ctx context.Context, req types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) { - res, err := cli.doRequest(ctx, types.ToRequestVerifyVoteExtension(req)) - if err != nil { - return nil, err - } - return res.GetVerifyVoteExtension(), nil -} - -func (cli *socketClient) FinalizeBlock(ctx context.Context, req types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { - res, err := cli.doRequest(ctx, types.ToRequestFinalizeBlock(req)) - if err != nil { - return nil, err - } - return res.GetFinalizeBlock(), nil -} - -//---------------------------------------- - func (cli *socketClient) doRequest(ctx context.Context, req *types.Request) (*types.Response, error) { + if !cli.IsRunning() { + return nil, errors.New("client has stopped") + } + reqres := makeReqRes(req) + cli.willSendReq(reqres) select { case cli.reqQueue <- reqres: @@ -342,7 +221,7 @@ func (cli *socketClient) doRequest(ctx context.Context, req *types.Request) (*ty // drainQueue marks as complete and discards all remaining pending requests // from the queue. -func (cli *socketClient) drainQueue(ctx context.Context) { +func (cli *socketClient) drainQueue() { cli.mtx.Lock() defer cli.mtx.Unlock() @@ -351,22 +230,136 @@ func (cli *socketClient) drainQueue(ctx context.Context) { reqres := req.Value.(*requestAndResponse) reqres.markDone() } +} - // Mark all queued messages as resolved. - // - // TODO(creachadair): We can't simply range the channel, because it is never - // closed, and the writer continues to add work. - // See https://github.com/tendermint/tendermint/issues/6996. - for { - select { - case <-ctx.Done(): - return - case reqres := <-cli.reqQueue: - reqres.markDone() - default: - return - } +//---------------------------------------- + +func (cli *socketClient) Flush(ctx context.Context) error { + _, err := cli.doRequest(ctx, types.ToRequestFlush()) + if err != nil { + return err } + return nil +} + +func (cli *socketClient) Echo(ctx context.Context, msg string) (*types.ResponseEcho, error) { + res, err := cli.doRequest(ctx, types.ToRequestEcho(msg)) + if err != nil { + return nil, err + } + return res.GetEcho(), nil +} + +func (cli *socketClient) Info(ctx context.Context, req *types.RequestInfo) (*types.ResponseInfo, error) { + res, err := cli.doRequest(ctx, types.ToRequestInfo(req)) + if err != nil { + return nil, err + } + return res.GetInfo(), nil +} + +func (cli *socketClient) CheckTx(ctx context.Context, req *types.RequestCheckTx) (*types.ResponseCheckTx, error) { + res, err := cli.doRequest(ctx, types.ToRequestCheckTx(req)) + if err != nil { + return nil, err + } + return res.GetCheckTx(), nil +} + +func (cli *socketClient) Query(ctx context.Context, req *types.RequestQuery) (*types.ResponseQuery, error) { + res, err := cli.doRequest(ctx, types.ToRequestQuery(req)) + if err != nil { + return nil, err + } + return res.GetQuery(), nil +} + +func (cli *socketClient) Commit(ctx context.Context) (*types.ResponseCommit, error) { + res, err := cli.doRequest(ctx, types.ToRequestCommit()) + if err != nil { + return nil, err + } + return res.GetCommit(), nil +} + +func (cli *socketClient) InitChain(ctx context.Context, req *types.RequestInitChain) (*types.ResponseInitChain, error) { + res, err := cli.doRequest(ctx, types.ToRequestInitChain(req)) + if err != nil { + return nil, err + } + return res.GetInitChain(), nil +} + +func (cli *socketClient) ListSnapshots(ctx context.Context, req *types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { + res, err := cli.doRequest(ctx, types.ToRequestListSnapshots(req)) + if err != nil { + return nil, err + } + return res.GetListSnapshots(), nil +} + +func (cli *socketClient) OfferSnapshot(ctx context.Context, req *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { + res, err := cli.doRequest(ctx, types.ToRequestOfferSnapshot(req)) + if err != nil { + return nil, err + } + return res.GetOfferSnapshot(), nil +} + +func (cli *socketClient) LoadSnapshotChunk(ctx context.Context, req *types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { + res, err := cli.doRequest(ctx, types.ToRequestLoadSnapshotChunk(req)) + if err != nil { + return nil, err + } + return res.GetLoadSnapshotChunk(), nil +} + +func (cli *socketClient) ApplySnapshotChunk(ctx context.Context, req *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { + res, err := cli.doRequest(ctx, types.ToRequestApplySnapshotChunk(req)) + if err != nil { + return nil, err + } + return res.GetApplySnapshotChunk(), nil +} + +func (cli *socketClient) PrepareProposal(ctx context.Context, req *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { + res, err := cli.doRequest(ctx, types.ToRequestPrepareProposal(req)) + if err != nil { + return nil, err + } + return res.GetPrepareProposal(), nil +} + +func (cli *socketClient) ProcessProposal(ctx context.Context, req *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { + res, err := cli.doRequest(ctx, types.ToRequestProcessProposal(req)) + if err != nil { + return nil, err + } + return res.GetProcessProposal(), nil +} + +func (cli *socketClient) ExtendVote(ctx context.Context, req *types.RequestExtendVote) (*types.ResponseExtendVote, error) { + res, err := cli.doRequest(ctx, types.ToRequestExtendVote(req)) + if err != nil { + return nil, err + } + return res.GetExtendVote(), nil +} + +func (cli *socketClient) VerifyVoteExtension(ctx context.Context, req *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) { + res, err := cli.doRequest(ctx, types.ToRequestVerifyVoteExtension(req)) + if err != nil { + return nil, err + } + return res.GetVerifyVoteExtension(), nil +} + +func (cli *socketClient) FinalizeBlock(ctx context.Context, req *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { + res, err := cli.doRequest(ctx, types.ToRequestFinalizeBlock(req)) + if err != nil { + return nil, err + } + return res.GetFinalizeBlock(), nil } //---------------------------------------- diff --git a/abci/cmd/abci-cli/abci-cli.go b/abci/cmd/abci-cli/abci-cli.go index 7bfea4a4c..cb1603be3 100644 --- a/abci/cmd/abci-cli/abci-cli.go +++ b/abci/cmd/abci-cli/abci-cli.go @@ -482,7 +482,7 @@ func cmdInfo(cmd *cobra.Command, args []string) error { if len(args) == 1 { version = args[0] } - res, err := client.Info(cmd.Context(), types.RequestInfo{Version: version}) + res, err := client.Info(cmd.Context(), &types.RequestInfo{Version: version}) if err != nil { return err } @@ -511,7 +511,7 @@ func cmdFinalizeBlock(cmd *cobra.Command, args []string) error { } txs[i] = txBytes } - res, err := client.FinalizeBlock(cmd.Context(), types.RequestFinalizeBlock{Txs: txs}) + res, err := client.FinalizeBlock(cmd.Context(), &types.RequestFinalizeBlock{Txs: txs}) if err != nil { return err } @@ -539,7 +539,7 @@ func cmdCheckTx(cmd *cobra.Command, args []string) error { if err != nil { return err } - res, err := client.CheckTx(cmd.Context(), types.RequestCheckTx{Tx: txBytes}) + res, err := client.CheckTx(cmd.Context(), &types.RequestCheckTx{Tx: txBytes}) if err != nil { return err } @@ -579,7 +579,7 @@ func cmdQuery(cmd *cobra.Command, args []string) error { return err } - resQuery, err := client.Query(cmd.Context(), types.RequestQuery{ + resQuery, err := client.Query(cmd.Context(), &types.RequestQuery{ Data: queryBytes, Path: flagPath, Height: int64(flagHeight), diff --git a/abci/example/example_test.go b/abci/example/example_test.go index 9d9d1548f..066d4071d 100644 --- a/abci/example/example_test.go +++ b/abci/example/example_test.go @@ -53,7 +53,7 @@ func TestGRPC(t *testing.T) { logger := log.NewNopLogger() t.Log("### Testing GRPC") - testGRPCSync(ctx, t, logger, types.NewGRPCApplication(types.NewBaseApplication())) + testGRPCSync(ctx, t, logger, types.NewBaseApplication()) } func testBulk(ctx context.Context, t *testing.T, logger log.Logger, app types.Application) { @@ -77,7 +77,7 @@ func testBulk(ctx context.Context, t *testing.T, logger log.Logger, app types.Ap require.NoError(t, err) // Construct request - rfb := types.RequestFinalizeBlock{Txs: make([][]byte, numDeliverTxs)} + rfb := &types.RequestFinalizeBlock{Txs: make([][]byte, numDeliverTxs)} for counter := 0; counter < numDeliverTxs; counter++ { rfb.Txs[counter] = []byte("test") } @@ -101,7 +101,7 @@ func dialerFunc(ctx context.Context, addr string) (net.Conn, error) { return tmnet.Connect(addr) } -func testGRPCSync(ctx context.Context, t *testing.T, logger log.Logger, app types.ABCIApplicationServer) { +func testGRPCSync(ctx context.Context, t *testing.T, logger log.Logger, app types.Application) { t.Helper() numDeliverTxs := 680000 socketFile := fmt.Sprintf("/tmp/test-%08x.sock", rand.Int31n(1<<30)) diff --git a/abci/example/kvstore/helpers.go b/abci/example/kvstore/helpers.go index 38bb42ea8..8a2a8a35b 100644 --- a/abci/example/kvstore/helpers.go +++ b/abci/example/kvstore/helpers.go @@ -1,6 +1,7 @@ package kvstore import ( + "context" mrand "math/rand" "github.com/tendermint/tendermint/abci/types" @@ -32,8 +33,9 @@ func RandVals(cnt int) []types.ValidatorUpdate { // InitKVStore initializes the kvstore app with some data, // which allows tests to pass and is fine as long as you // don't make any tx that modify the validator state -func InitKVStore(app *PersistentKVStoreApplication) { - app.InitChain(types.RequestInitChain{ +func InitKVStore(ctx context.Context, app *PersistentKVStoreApplication) error { + _, err := app.InitChain(ctx, &types.RequestInitChain{ Validators: RandVals(1), }) + return err } diff --git a/abci/example/kvstore/kvstore.go b/abci/example/kvstore/kvstore.go index d2b2d99fe..61d129239 100644 --- a/abci/example/kvstore/kvstore.go +++ b/abci/example/kvstore/kvstore.go @@ -2,6 +2,7 @@ package kvstore import ( "bytes" + "context" "encoding/base64" "encoding/binary" "encoding/json" @@ -90,7 +91,7 @@ func NewApplication() *Application { } } -func (app *Application) InitChain(req types.RequestInitChain) types.ResponseInitChain { +func (app *Application) InitChain(_ context.Context, req *types.RequestInitChain) (*types.ResponseInitChain, error) { app.mu.Lock() defer app.mu.Unlock() @@ -101,19 +102,19 @@ func (app *Application) InitChain(req types.RequestInitChain) types.ResponseInit panic("problem updating validators") } } - return types.ResponseInitChain{} + return &types.ResponseInitChain{}, nil } -func (app *Application) Info(req types.RequestInfo) types.ResponseInfo { +func (app *Application) Info(_ context.Context, req *types.RequestInfo) (*types.ResponseInfo, error) { app.mu.Lock() defer app.mu.Unlock() - return types.ResponseInfo{ + return &types.ResponseInfo{ Data: fmt.Sprintf("{\"size\":%v}", app.state.Size), Version: version.ABCIVersion, AppVersion: ProtocolVersion, LastBlockHeight: app.state.Height, LastBlockAppHash: app.state.AppHash, - } + }, nil } // tx is either "val:pubkey!power" or "key=value" or just arbitrary bytes @@ -166,7 +167,7 @@ func (app *Application) Close() error { return app.state.db.Close() } -func (app *Application) FinalizeBlock(req types.RequestFinalizeBlock) types.ResponseFinalizeBlock { +func (app *Application) FinalizeBlock(_ context.Context, req *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { app.mu.Lock() defer app.mu.Unlock() @@ -175,7 +176,7 @@ func (app *Application) FinalizeBlock(req types.RequestFinalizeBlock) types.Resp // Punish validators who committed equivocation. for _, ev := range req.ByzantineValidators { - if ev.Type == types.EvidenceType_DUPLICATE_VOTE { + if ev.Type == types.MisbehaviorType_DUPLICATE_VOTE { addr := string(ev.Validator.Address) if pubKey, ok := app.valAddrToPubKeyMap[addr]; ok { app.updateValidator(types.ValidatorUpdate{ @@ -195,14 +196,14 @@ func (app *Application) FinalizeBlock(req types.RequestFinalizeBlock) types.Resp respTxs[i] = app.handleTx(tx) } - return types.ResponseFinalizeBlock{TxResults: respTxs, ValidatorUpdates: app.ValUpdates} + return &types.ResponseFinalizeBlock{TxResults: respTxs, ValidatorUpdates: app.ValUpdates}, nil } -func (*Application) CheckTx(req types.RequestCheckTx) types.ResponseCheckTx { - return types.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1} +func (*Application) CheckTx(_ context.Context, req *types.RequestCheckTx) (*types.ResponseCheckTx, error) { + return &types.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1}, nil } -func (app *Application) Commit() types.ResponseCommit { +func (app *Application) Commit(_ context.Context) (*types.ResponseCommit, error) { app.mu.Lock() defer app.mu.Unlock() @@ -213,15 +214,15 @@ func (app *Application) Commit() types.ResponseCommit { app.state.Height++ saveState(app.state) - resp := types.ResponseCommit{Data: appHash} + resp := &types.ResponseCommit{Data: appHash} if app.RetainBlocks > 0 && app.state.Height >= app.RetainBlocks { resp.RetainHeight = app.state.Height - app.RetainBlocks + 1 } - return resp + return resp, nil } // Returns an associated value or nil if missing. -func (app *Application) Query(reqQuery types.RequestQuery) types.ResponseQuery { +func (app *Application) Query(_ context.Context, reqQuery *types.RequestQuery) (*types.ResponseQuery, error) { app.mu.Lock() defer app.mu.Unlock() @@ -232,10 +233,10 @@ func (app *Application) Query(reqQuery types.RequestQuery) types.ResponseQuery { panic(err) } - return types.ResponseQuery{ + return &types.ResponseQuery{ Key: reqQuery.Data, Value: value, - } + }, nil } if reqQuery.Prove { @@ -257,7 +258,7 @@ func (app *Application) Query(reqQuery types.RequestQuery) types.ResponseQuery { resQuery.Log = "exists" } - return resQuery + return &resQuery, nil } value, err := app.state.db.Get(prefixKey(reqQuery.Data)) @@ -277,23 +278,25 @@ func (app *Application) Query(reqQuery types.RequestQuery) types.ResponseQuery { resQuery.Log = "exists" } - return resQuery + return &resQuery, nil } -func (app *Application) PrepareProposal(req types.RequestPrepareProposal) types.ResponsePrepareProposal { +func (app *Application) PrepareProposal(_ context.Context, req *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { app.mu.Lock() defer app.mu.Unlock() - return types.ResponsePrepareProposal{BlockData: app.substPrepareTx(req.BlockData)} + return &types.ResponsePrepareProposal{ + TxRecords: app.substPrepareTx(req.Txs, req.MaxTxBytes), + }, nil } -func (*Application) ProcessProposal(req types.RequestProcessProposal) types.ResponseProcessProposal { +func (*Application) ProcessProposal(_ context.Context, req *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { for _, tx := range req.Txs { if len(tx) == 0 { - return types.ResponseProcessProposal{Accept: false} + return &types.ResponseProcessProposal{Status: types.ResponseProcessProposal_REJECT}, nil } } - return types.ResponseProcessProposal{Accept: true} + return &types.ResponseProcessProposal{Status: types.ResponseProcessProposal_ACCEPT}, nil } //--------------------------------------------- @@ -420,7 +423,7 @@ func (app *Application) updateValidator(v types.ValidatorUpdate) *types.ExecTxRe const PreparePrefix = "prepare" func isPrepareTx(tx []byte) bool { - return strings.HasPrefix(string(tx), PreparePrefix) + return bytes.HasPrefix(tx, []byte(PreparePrefix)) } // execPrepareTx is noop. tx data is considered as placeholder @@ -430,16 +433,34 @@ func (app *Application) execPrepareTx(tx []byte) *types.ExecTxResult { return &types.ExecTxResult{} } -// substPrepareTx subst all the preparetx in the blockdata -// to null string(could be any arbitrary string). -func (app *Application) substPrepareTx(blockData [][]byte) [][]byte { - // TODO: this mechanism will change with the current spec of PrepareProposal - // We now have a special type for marking a tx as changed - for i, tx := range blockData { +// substPrepareTx substitutes all the transactions prefixed with 'prepare' in the +// proposal for transactions with the prefix stripped. +// It marks all of the original transactions as 'REMOVED' so that +// Tendermint will remove them from its mempool. +func (app *Application) substPrepareTx(blockData [][]byte, maxTxBytes int64) []*types.TxRecord { + trs := make([]*types.TxRecord, 0, len(blockData)) + var removed []*types.TxRecord + var totalBytes int64 + for _, tx := range blockData { + txMod := tx + action := types.TxRecord_UNMODIFIED if isPrepareTx(tx) { - blockData[i] = make([]byte, len(tx)) + removed = append(removed, &types.TxRecord{ + Tx: tx, + Action: types.TxRecord_REMOVED, + }) + txMod = bytes.TrimPrefix(tx, []byte(PreparePrefix)) + action = types.TxRecord_ADDED } + totalBytes += int64(len(txMod)) + if totalBytes > maxTxBytes { + break + } + trs = append(trs, &types.TxRecord{ + Tx: txMod, + Action: action, + }) } - return blockData + return append(trs, removed...) } diff --git a/abci/example/kvstore/kvstore_test.go b/abci/example/kvstore/kvstore_test.go index 002c1cb41..9631bc1ab 100644 --- a/abci/example/kvstore/kvstore_test.go +++ b/abci/example/kvstore/kvstore_test.go @@ -16,7 +16,6 @@ import ( "github.com/tendermint/tendermint/abci/example/code" abciserver "github.com/tendermint/tendermint/abci/server" "github.com/tendermint/tendermint/abci/types" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) const ( @@ -24,37 +23,43 @@ const ( testValue = "def" ) -func testKVStore(t *testing.T, app types.Application, tx []byte, key, value string) { - req := types.RequestFinalizeBlock{Txs: [][]byte{tx}} - ar := app.FinalizeBlock(req) +func testKVStore(ctx context.Context, t *testing.T, app types.Application, tx []byte, key, value string) { + req := &types.RequestFinalizeBlock{Txs: [][]byte{tx}} + ar, err := app.FinalizeBlock(ctx, req) + require.NoError(t, err) require.Equal(t, 1, len(ar.TxResults)) require.False(t, ar.TxResults[0].IsErr()) // repeating tx doesn't raise error - ar = app.FinalizeBlock(req) + ar, err = app.FinalizeBlock(ctx, req) + require.NoError(t, err) require.Equal(t, 1, len(ar.TxResults)) require.False(t, ar.TxResults[0].IsErr()) // commit - app.Commit() + _, err = app.Commit(ctx) + require.NoError(t, err) - info := app.Info(types.RequestInfo{}) + info, err := app.Info(ctx, &types.RequestInfo{}) + require.NoError(t, err) require.NotZero(t, info.LastBlockHeight) // make sure query is fine - resQuery := app.Query(types.RequestQuery{ + resQuery, err := app.Query(ctx, &types.RequestQuery{ Path: "/store", Data: []byte(key), }) + require.NoError(t, err) require.Equal(t, code.CodeTypeOK, resQuery.Code) require.Equal(t, key, string(resQuery.Key)) require.Equal(t, value, string(resQuery.Value)) require.EqualValues(t, info.LastBlockHeight, resQuery.Height) // make sure proof is fine - resQuery = app.Query(types.RequestQuery{ + resQuery, err = app.Query(ctx, &types.RequestQuery{ Path: "/store", Data: []byte(key), Prove: true, }) + require.NoError(t, err) require.EqualValues(t, code.CodeTypeOK, resQuery.Code) require.Equal(t, key, string(resQuery.Key)) require.Equal(t, value, string(resQuery.Value)) @@ -62,18 +67,24 @@ func testKVStore(t *testing.T, app types.Application, tx []byte, key, value stri } func TestKVStoreKV(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + kvstore := NewApplication() key := testKey value := key tx := []byte(key) - testKVStore(t, kvstore, tx, key, value) + testKVStore(ctx, t, kvstore, tx, key, value) value = testValue tx = []byte(key + "=" + value) - testKVStore(t, kvstore, tx, key, value) + testKVStore(ctx, t, kvstore, tx, key, value) } func TestPersistentKVStoreKV(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + dir := t.TempDir() logger := log.NewNopLogger() @@ -81,22 +92,30 @@ func TestPersistentKVStoreKV(t *testing.T) { key := testKey value := key tx := []byte(key) - testKVStore(t, kvstore, tx, key, value) + testKVStore(ctx, t, kvstore, tx, key, value) value = testValue tx = []byte(key + "=" + value) - testKVStore(t, kvstore, tx, key, value) + testKVStore(ctx, t, kvstore, tx, key, value) } func TestPersistentKVStoreInfo(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() dir := t.TempDir() logger := log.NewNopLogger() kvstore := NewPersistentKVStoreApplication(logger, dir) - InitKVStore(kvstore) + if err := InitKVStore(ctx, kvstore); err != nil { + t.Fatal(err) + } height := int64(0) - resInfo := kvstore.Info(types.RequestInfo{}) + resInfo, err := kvstore.Info(ctx, &types.RequestInfo{}) + if err != nil { + t.Fatal(err) + } + if resInfo.LastBlockHeight != height { t.Fatalf("expected height of %d, got %d", height, resInfo.LastBlockHeight) } @@ -104,13 +123,19 @@ func TestPersistentKVStoreInfo(t *testing.T) { // make and apply block height = int64(1) hash := []byte("foo") - header := tmproto.Header{ - Height: height, + if _, err := kvstore.FinalizeBlock(ctx, &types.RequestFinalizeBlock{Hash: hash, Height: height}); err != nil { + t.Fatal(err) } - kvstore.FinalizeBlock(types.RequestFinalizeBlock{Hash: hash, Header: header}) - kvstore.Commit() - resInfo = kvstore.Info(types.RequestInfo{}) + if _, err := kvstore.Commit(ctx); err != nil { + t.Fatal(err) + + } + + resInfo, err = kvstore.Info(ctx, &types.RequestInfo{}) + if err != nil { + t.Fatal(err) + } if resInfo.LastBlockHeight != height { t.Fatalf("expected height of %d, got %d", height, resInfo.LastBlockHeight) } @@ -119,6 +144,9 @@ func TestPersistentKVStoreInfo(t *testing.T) { // add a validator, remove a validator, update a validator func TestValUpdates(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + kvstore := NewApplication() // init with some validators @@ -126,9 +154,12 @@ func TestValUpdates(t *testing.T) { nInit := 5 vals := RandVals(total) // initialize with the first nInit - kvstore.InitChain(types.RequestInitChain{ + _, err := kvstore.InitChain(ctx, &types.RequestInitChain{ Validators: vals[:nInit], }) + if err != nil { + t.Fatal(err) + } vals1, vals2 := vals[:nInit], kvstore.Validators() valsEqual(t, vals1, vals2) @@ -141,7 +172,7 @@ func TestValUpdates(t *testing.T) { tx1 := MakeValSetChangeTx(v1.PubKey, v1.Power) tx2 := MakeValSetChangeTx(v2.PubKey, v2.Power) - makeApplyBlock(t, kvstore, 1, diff, tx1, tx2) + makeApplyBlock(ctx, t, kvstore, 1, diff, tx1, tx2) vals1, vals2 = vals[:nInit+2], kvstore.Validators() valsEqual(t, vals1, vals2) @@ -156,7 +187,7 @@ func TestValUpdates(t *testing.T) { tx2 = MakeValSetChangeTx(v2.PubKey, v2.Power) tx3 := MakeValSetChangeTx(v3.PubKey, v3.Power) - makeApplyBlock(t, kvstore, 2, diff, tx1, tx2, tx3) + makeApplyBlock(ctx, t, kvstore, 2, diff, tx1, tx2, tx3) vals1 = append(vals[:nInit-2], vals[nInit+1]) // nolint: gocritic vals2 = kvstore.Validators() @@ -172,7 +203,7 @@ func TestValUpdates(t *testing.T) { diff = []types.ValidatorUpdate{v1} tx1 = MakeValSetChangeTx(v1.PubKey, v1.Power) - makeApplyBlock(t, kvstore, 3, diff, tx1) + makeApplyBlock(ctx, t, kvstore, 3, diff, tx1) vals1 = append([]types.ValidatorUpdate{v1}, vals1[1:]...) vals2 = kvstore.Validators() @@ -180,26 +211,23 @@ func TestValUpdates(t *testing.T) { } -func makeApplyBlock( - t *testing.T, - kvstore types.Application, - heightInt int, - diff []types.ValidatorUpdate, - txs ...[]byte) { +func makeApplyBlock(ctx context.Context, t *testing.T, kvstore types.Application, heightInt int, diff []types.ValidatorUpdate, txs ...[]byte) { // make and apply block height := int64(heightInt) hash := []byte("foo") - header := tmproto.Header{ - Height: height, - } - - resFinalizeBlock := kvstore.FinalizeBlock(types.RequestFinalizeBlock{ + resFinalizeBlock, err := kvstore.FinalizeBlock(ctx, &types.RequestFinalizeBlock{ Hash: hash, - Header: header, + Height: height, Txs: txs, }) + if err != nil { + t.Fatal(err) + } - kvstore.Commit() + _, err = kvstore.Commit(ctx) + if err != nil { + t.Fatal(err) + } valsEqual(t, diff, resFinalizeBlock.ValidatorUpdates) @@ -268,8 +296,7 @@ func makeGRPCClientServer( // Start the listener socket := fmt.Sprintf("unix://%s.sock", name) - gapp := types.NewGRPCApplication(app) - server := abciserver.NewGRPCServer(logger.With("module", "abci-server"), socket, gapp) + server := abciserver.NewGRPCServer(logger.With("module", "abci-server"), socket, app) if err := server.Start(ctx); err != nil { cancel() @@ -323,12 +350,12 @@ func runClientTests(ctx context.Context, t *testing.T, client abciclient.Client) } func testClient(ctx context.Context, t *testing.T, app abciclient.Client, tx []byte, key, value string) { - ar, err := app.FinalizeBlock(ctx, types.RequestFinalizeBlock{Txs: [][]byte{tx}}) + ar, err := app.FinalizeBlock(ctx, &types.RequestFinalizeBlock{Txs: [][]byte{tx}}) require.NoError(t, err) require.Equal(t, 1, len(ar.TxResults)) require.False(t, ar.TxResults[0].IsErr()) // repeating FinalizeBlock doesn't raise error - ar, err = app.FinalizeBlock(ctx, types.RequestFinalizeBlock{Txs: [][]byte{tx}}) + ar, err = app.FinalizeBlock(ctx, &types.RequestFinalizeBlock{Txs: [][]byte{tx}}) require.NoError(t, err) require.Equal(t, 1, len(ar.TxResults)) require.False(t, ar.TxResults[0].IsErr()) @@ -336,12 +363,12 @@ func testClient(ctx context.Context, t *testing.T, app abciclient.Client, tx []b _, err = app.Commit(ctx) require.NoError(t, err) - info, err := app.Info(ctx, types.RequestInfo{}) + info, err := app.Info(ctx, &types.RequestInfo{}) require.NoError(t, err) require.NotZero(t, info.LastBlockHeight) // make sure query is fine - resQuery, err := app.Query(ctx, types.RequestQuery{ + resQuery, err := app.Query(ctx, &types.RequestQuery{ Path: "/store", Data: []byte(key), }) @@ -352,7 +379,7 @@ func testClient(ctx context.Context, t *testing.T, app abciclient.Client, tx []b require.EqualValues(t, info.LastBlockHeight, resQuery.Height) // make sure proof is fine - resQuery, err = app.Query(ctx, types.RequestQuery{ + resQuery, err = app.Query(ctx, &types.RequestQuery{ Path: "/store", Data: []byte(key), Prove: true, diff --git a/abci/example/kvstore/persistent_kvstore.go b/abci/example/kvstore/persistent_kvstore.go index 2a6e8aa19..bede5d517 100644 --- a/abci/example/kvstore/persistent_kvstore.go +++ b/abci/example/kvstore/persistent_kvstore.go @@ -1,14 +1,13 @@ package kvstore import ( - "bytes" + "context" dbm "github.com/tendermint/tm-db" "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" cryptoproto "github.com/tendermint/tendermint/proto/tendermint/crypto" - ptypes "github.com/tendermint/tendermint/proto/tendermint/types" ) const ( @@ -38,41 +37,10 @@ func NewPersistentKVStoreApplication(logger log.Logger, dbDir string) *Persisten } } -func (app *PersistentKVStoreApplication) OfferSnapshot(req types.RequestOfferSnapshot) types.ResponseOfferSnapshot { - return types.ResponseOfferSnapshot{Result: types.ResponseOfferSnapshot_ABORT} +func (app *PersistentKVStoreApplication) OfferSnapshot(_ context.Context, req *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { + return &types.ResponseOfferSnapshot{Result: types.ResponseOfferSnapshot_ABORT}, nil } -func (app *PersistentKVStoreApplication) ApplySnapshotChunk(req types.RequestApplySnapshotChunk) types.ResponseApplySnapshotChunk { - return types.ResponseApplySnapshotChunk{Result: types.ResponseApplySnapshotChunk_ABORT} -} - -func (app *PersistentKVStoreApplication) ExtendVote(req types.RequestExtendVote) types.ResponseExtendVote { - return types.ResponseExtendVote{VoteExtension: ConstructVoteExtension(req.Vote.ValidatorAddress)} -} - -func (app *PersistentKVStoreApplication) VerifyVoteExtension(req types.RequestVerifyVoteExtension) types.ResponseVerifyVoteExtension { - return types.RespondVerifyVoteExtension(app.verifyExtension(req.Vote.ValidatorAddress, req.Vote.VoteExtension)) -} - -// ----------------------------- - -func ConstructVoteExtension(valAddr []byte) *ptypes.VoteExtension { - return &ptypes.VoteExtension{ - AppDataToSign: valAddr, - AppDataSelfAuthenticating: valAddr, - } -} - -func (app *PersistentKVStoreApplication) verifyExtension(valAddr []byte, ext *ptypes.VoteExtension) bool { - if ext == nil { - return false - } - canonical := ConstructVoteExtension(valAddr) - if !bytes.Equal(canonical.AppDataToSign, ext.AppDataToSign) { - return false - } - if !bytes.Equal(canonical.AppDataSelfAuthenticating, ext.AppDataSelfAuthenticating) { - return false - } - return true +func (app *PersistentKVStoreApplication) ApplySnapshotChunk(_ context.Context, req *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { + return &types.ResponseApplySnapshotChunk{Result: types.ResponseApplySnapshotChunk_ABORT}, nil } diff --git a/abci/server/grpc_server.go b/abci/server/grpc_server.go index c1446d17a..0dfee8169 100644 --- a/abci/server/grpc_server.go +++ b/abci/server/grpc_server.go @@ -20,11 +20,11 @@ type GRPCServer struct { addr string server *grpc.Server - app types.ABCIApplicationServer + app types.Application } // NewGRPCServer returns a new gRPC ABCI server -func NewGRPCServer(logger log.Logger, protoAddr string, app types.ABCIApplicationServer) service.Service { +func NewGRPCServer(logger log.Logger, protoAddr string, app types.Application) service.Service { proto, addr := tmnet.ProtocolAndAddress(protoAddr) s := &GRPCServer{ logger: logger, @@ -44,7 +44,7 @@ func (s *GRPCServer) OnStart(ctx context.Context) error { } s.server = grpc.NewServer() - types.RegisterABCIApplicationServer(s.server, s.app) + types.RegisterABCIApplicationServer(s.server, &gRPCApplication{Application: s.app}) s.logger.Info("Listening", "proto", s.proto, "addr", s.addr) go func() { @@ -62,3 +62,22 @@ func (s *GRPCServer) OnStart(ctx context.Context) error { // OnStop stops the gRPC server. func (s *GRPCServer) OnStop() { s.server.Stop() } + +//------------------------------------------------------- + +// gRPCApplication is a gRPC shim for Application +type gRPCApplication struct { + types.Application +} + +func (app *gRPCApplication) Echo(_ context.Context, req *types.RequestEcho) (*types.ResponseEcho, error) { + return &types.ResponseEcho{Message: req.Message}, nil +} + +func (app *gRPCApplication) Flush(_ context.Context, req *types.RequestFlush) (*types.ResponseFlush, error) { + return &types.ResponseFlush{}, nil +} + +func (app *gRPCApplication) Commit(ctx context.Context, req *types.RequestCommit) (*types.ResponseCommit, error) { + return app.Application.Commit(ctx) +} diff --git a/abci/server/server.go b/abci/server/server.go index 2a6d50fd2..0e0173ca6 100644 --- a/abci/server/server.go +++ b/abci/server/server.go @@ -23,7 +23,7 @@ func NewServer(logger log.Logger, protoAddr, transport string, app types.Applica case "socket": s = NewSocketServer(logger, protoAddr, app) case "grpc": - s = NewGRPCServer(logger, protoAddr, types.NewGRPCApplication(app)) + s = NewGRPCServer(logger, protoAddr, app) default: err = fmt.Errorf("unknown server type %s", transport) } diff --git a/abci/server/socket_server.go b/abci/server/socket_server.go index 72dd1221c..570ecfb4e 100644 --- a/abci/server/socket_server.go +++ b/abci/server/socket_server.go @@ -159,12 +159,7 @@ func (s *SocketServer) acceptConnectionsRoutine(ctx context.Context) { } // Read requests from conn and deal with them -func (s *SocketServer) handleRequests( - ctx context.Context, - closer func(error), - conn io.Reader, - responses chan<- *types.Response, -) { +func (s *SocketServer) handleRequests(ctx context.Context, closer func(error), conn io.Reader, responses chan<- *types.Response) { var bufReader = bufio.NewReader(conn) defer func() { @@ -184,7 +179,12 @@ func (s *SocketServer) handleRequests( return } - resp := s.processRequest(req) + resp, err := s.processRequest(ctx, req) + if err != nil { + closer(err) + return + } + select { case <-ctx.Done(): closer(ctx.Err()) @@ -194,42 +194,99 @@ func (s *SocketServer) handleRequests( } } -func (s *SocketServer) processRequest(req *types.Request) *types.Response { +func (s *SocketServer) processRequest(ctx context.Context, req *types.Request) (*types.Response, error) { switch r := req.Value.(type) { case *types.Request_Echo: - return types.ToResponseEcho(r.Echo.Message) + return types.ToResponseEcho(r.Echo.Message), nil case *types.Request_Flush: - return types.ToResponseFlush() + return types.ToResponseFlush(), nil case *types.Request_Info: - return types.ToResponseInfo(s.app.Info(*r.Info)) + res, err := s.app.Info(ctx, r.Info) + if err != nil { + return nil, err + } + + return types.ToResponseInfo(res), nil case *types.Request_CheckTx: - return types.ToResponseCheckTx(s.app.CheckTx(*r.CheckTx)) + res, err := s.app.CheckTx(ctx, r.CheckTx) + if err != nil { + return nil, err + } + return types.ToResponseCheckTx(res), nil case *types.Request_Commit: - return types.ToResponseCommit(s.app.Commit()) + res, err := s.app.Commit(ctx) + if err != nil { + return nil, err + } + return types.ToResponseCommit(res), nil case *types.Request_Query: - return types.ToResponseQuery(s.app.Query(*r.Query)) + res, err := s.app.Query(ctx, r.Query) + if err != nil { + return nil, err + } + return types.ToResponseQuery(res), nil case *types.Request_InitChain: - return types.ToResponseInitChain(s.app.InitChain(*r.InitChain)) + res, err := s.app.InitChain(ctx, r.InitChain) + if err != nil { + return nil, err + } + return types.ToResponseInitChain(res), nil case *types.Request_ListSnapshots: - return types.ToResponseListSnapshots(s.app.ListSnapshots(*r.ListSnapshots)) + res, err := s.app.ListSnapshots(ctx, r.ListSnapshots) + if err != nil { + return nil, err + } + return types.ToResponseListSnapshots(res), nil case *types.Request_OfferSnapshot: - return types.ToResponseOfferSnapshot(s.app.OfferSnapshot(*r.OfferSnapshot)) + res, err := s.app.OfferSnapshot(ctx, r.OfferSnapshot) + if err != nil { + return nil, err + } + return types.ToResponseOfferSnapshot(res), nil case *types.Request_PrepareProposal: - return types.ToResponsePrepareProposal(s.app.PrepareProposal(*r.PrepareProposal)) + res, err := s.app.PrepareProposal(ctx, r.PrepareProposal) + if err != nil { + return nil, err + } + return types.ToResponsePrepareProposal(res), nil case *types.Request_ProcessProposal: - return types.ToResponseProcessProposal(s.app.ProcessProposal(*r.ProcessProposal)) + res, err := s.app.ProcessProposal(ctx, r.ProcessProposal) + if err != nil { + return nil, err + } + return types.ToResponseProcessProposal(res), nil case *types.Request_LoadSnapshotChunk: - return types.ToResponseLoadSnapshotChunk(s.app.LoadSnapshotChunk(*r.LoadSnapshotChunk)) + res, err := s.app.LoadSnapshotChunk(ctx, r.LoadSnapshotChunk) + if err != nil { + return nil, err + } + return types.ToResponseLoadSnapshotChunk(res), nil case *types.Request_ApplySnapshotChunk: - return types.ToResponseApplySnapshotChunk(s.app.ApplySnapshotChunk(*r.ApplySnapshotChunk)) + res, err := s.app.ApplySnapshotChunk(ctx, r.ApplySnapshotChunk) + if err != nil { + return nil, err + } + return types.ToResponseApplySnapshotChunk(res), nil case *types.Request_ExtendVote: - return types.ToResponseExtendVote(s.app.ExtendVote(*r.ExtendVote)) + res, err := s.app.ExtendVote(ctx, r.ExtendVote) + if err != nil { + return nil, err + } + return types.ToResponseExtendVote(res), nil case *types.Request_VerifyVoteExtension: - return types.ToResponseVerifyVoteExtension(s.app.VerifyVoteExtension(*r.VerifyVoteExtension)) + res, err := s.app.VerifyVoteExtension(ctx, r.VerifyVoteExtension) + if err != nil { + return nil, err + } + return types.ToResponseVerifyVoteExtension(res), nil case *types.Request_FinalizeBlock: - return types.ToResponseFinalizeBlock(s.app.FinalizeBlock(*r.FinalizeBlock)) + res, err := s.app.FinalizeBlock(ctx, r.FinalizeBlock) + if err != nil { + return nil, err + } + return types.ToResponseFinalizeBlock(res), nil default: - return types.ToResponseException("Unknown request") + return types.ToResponseException("Unknown request"), errors.New("unknown request type") } } diff --git a/abci/tests/server/client.go b/abci/tests/server/client.go index 9273e8046..b02fd0b14 100644 --- a/abci/tests/server/client.go +++ b/abci/tests/server/client.go @@ -21,7 +21,7 @@ func InitChain(ctx context.Context, client abciclient.Client) error { power := mrand.Int() vals[i] = types.UpdateValidator(pubkey, int64(power), "") } - _, err := client.InitChain(ctx, types.RequestInitChain{ + _, err := client.InitChain(ctx, &types.RequestInitChain{ Validators: vals, }) if err != nil { @@ -50,7 +50,7 @@ func Commit(ctx context.Context, client abciclient.Client, hashExp []byte) error } func FinalizeBlock(ctx context.Context, client abciclient.Client, txBytes [][]byte, codeExp []uint32, dataExp []byte) error { - res, _ := client.FinalizeBlock(ctx, types.RequestFinalizeBlock{Txs: txBytes}) + res, _ := client.FinalizeBlock(ctx, &types.RequestFinalizeBlock{Txs: txBytes}) for i, tx := range res.TxResults { code, data, log := tx.Code, tx.Data, tx.Log if code != codeExp[i] { @@ -71,7 +71,7 @@ func FinalizeBlock(ctx context.Context, client abciclient.Client, txBytes [][]by } func CheckTx(ctx context.Context, client abciclient.Client, txBytes []byte, codeExp uint32, dataExp []byte) error { - res, _ := client.CheckTx(ctx, types.RequestCheckTx{Tx: txBytes}) + res, _ := client.CheckTx(ctx, &types.RequestCheckTx{Tx: txBytes}) code, data, log := res.Code, res.Data, res.Log if code != codeExp { fmt.Println("Failed test: CheckTx") diff --git a/abci/types/application.go b/abci/types/application.go index 6961ea200..e74b87743 100644 --- a/abci/types/application.go +++ b/abci/types/application.go @@ -1,40 +1,36 @@ package types -import ( - "context" -) +import "context" //go:generate ../../scripts/mockery_generate.sh Application // Application is an interface that enables any finite, deterministic state machine // to be driven by a blockchain-based replication engine via the ABCI. -// All methods take a RequestXxx argument and return a ResponseXxx argument, -// except CheckTx/DeliverTx, which take `tx []byte`, and `Commit`, which takes nothing. type Application interface { // Info/Query Connection - Info(RequestInfo) ResponseInfo // Return application info - Query(RequestQuery) ResponseQuery // Query for state + Info(context.Context, *RequestInfo) (*ResponseInfo, error) // Return application info + Query(context.Context, *RequestQuery) (*ResponseQuery, error) // Query for state // Mempool Connection - CheckTx(RequestCheckTx) ResponseCheckTx // Validate a tx for the mempool + CheckTx(context.Context, *RequestCheckTx) (*ResponseCheckTx, error) // Validate a tx for the mempool // Consensus Connection - InitChain(RequestInitChain) ResponseInitChain // Initialize blockchain w validators/other info from TendermintCore - PrepareProposal(RequestPrepareProposal) ResponsePrepareProposal - ProcessProposal(RequestProcessProposal) ResponseProcessProposal + InitChain(context.Context, *RequestInitChain) (*ResponseInitChain, error) // Initialize blockchain w validators/other info from TendermintCore + PrepareProposal(context.Context, *RequestPrepareProposal) (*ResponsePrepareProposal, error) + ProcessProposal(context.Context, *RequestProcessProposal) (*ResponseProcessProposal, error) // Commit the state and return the application Merkle root hash - Commit() ResponseCommit + Commit(context.Context) (*ResponseCommit, error) // Create application specific vote extension - ExtendVote(RequestExtendVote) ResponseExtendVote + ExtendVote(context.Context, *RequestExtendVote) (*ResponseExtendVote, error) // Verify application's vote extension data - VerifyVoteExtension(RequestVerifyVoteExtension) ResponseVerifyVoteExtension + VerifyVoteExtension(context.Context, *RequestVerifyVoteExtension) (*ResponseVerifyVoteExtension, error) // Deliver the decided block with its txs to the Application - FinalizeBlock(RequestFinalizeBlock) ResponseFinalizeBlock + FinalizeBlock(context.Context, *RequestFinalizeBlock) (*ResponseFinalizeBlock, error) // State Sync Connection - ListSnapshots(RequestListSnapshots) ResponseListSnapshots // List available snapshots - OfferSnapshot(RequestOfferSnapshot) ResponseOfferSnapshot // Offer a snapshot to the application - LoadSnapshotChunk(RequestLoadSnapshotChunk) ResponseLoadSnapshotChunk // Load a snapshot chunk - ApplySnapshotChunk(RequestApplySnapshotChunk) ResponseApplySnapshotChunk // Apply a shapshot chunk + ListSnapshots(context.Context, *RequestListSnapshots) (*ResponseListSnapshots, error) // List available snapshots + OfferSnapshot(context.Context, *RequestOfferSnapshot) (*ResponseOfferSnapshot, error) // Offer a snapshot to the application + LoadSnapshotChunk(context.Context, *RequestLoadSnapshotChunk) (*ResponseLoadSnapshotChunk, error) // Load a snapshot chunk + ApplySnapshotChunk(context.Context, *RequestApplySnapshotChunk) (*ResponseApplySnapshotChunk, error) // Apply a shapshot chunk } //------------------------------------------------------- @@ -48,164 +44,78 @@ func NewBaseApplication() *BaseApplication { return &BaseApplication{} } -func (BaseApplication) Info(req RequestInfo) ResponseInfo { - return ResponseInfo{} +func (BaseApplication) Info(_ context.Context, req *RequestInfo) (*ResponseInfo, error) { + return &ResponseInfo{}, nil } -func (BaseApplication) CheckTx(req RequestCheckTx) ResponseCheckTx { - return ResponseCheckTx{Code: CodeTypeOK} +func (BaseApplication) CheckTx(_ context.Context, req *RequestCheckTx) (*ResponseCheckTx, error) { + return &ResponseCheckTx{Code: CodeTypeOK}, nil } -func (BaseApplication) Commit() ResponseCommit { - return ResponseCommit{} +func (BaseApplication) Commit(_ context.Context) (*ResponseCommit, error) { + return &ResponseCommit{}, nil } -func (BaseApplication) ExtendVote(req RequestExtendVote) ResponseExtendVote { - return ResponseExtendVote{} +func (BaseApplication) ExtendVote(_ context.Context, req *RequestExtendVote) (*ResponseExtendVote, error) { + return &ResponseExtendVote{}, nil } -func (BaseApplication) VerifyVoteExtension(req RequestVerifyVoteExtension) ResponseVerifyVoteExtension { - return ResponseVerifyVoteExtension{ - Result: ResponseVerifyVoteExtension_ACCEPT, +func (BaseApplication) VerifyVoteExtension(_ context.Context, req *RequestVerifyVoteExtension) (*ResponseVerifyVoteExtension, error) { + return &ResponseVerifyVoteExtension{ + Status: ResponseVerifyVoteExtension_ACCEPT, + }, nil +} + +func (BaseApplication) Query(_ context.Context, req *RequestQuery) (*ResponseQuery, error) { + return &ResponseQuery{Code: CodeTypeOK}, nil +} + +func (BaseApplication) InitChain(_ context.Context, req *RequestInitChain) (*ResponseInitChain, error) { + return &ResponseInitChain{}, nil +} + +func (BaseApplication) ListSnapshots(_ context.Context, req *RequestListSnapshots) (*ResponseListSnapshots, error) { + return &ResponseListSnapshots{}, nil +} + +func (BaseApplication) OfferSnapshot(_ context.Context, req *RequestOfferSnapshot) (*ResponseOfferSnapshot, error) { + return &ResponseOfferSnapshot{}, nil +} + +func (BaseApplication) LoadSnapshotChunk(_ context.Context, _ *RequestLoadSnapshotChunk) (*ResponseLoadSnapshotChunk, error) { + return &ResponseLoadSnapshotChunk{}, nil +} + +func (BaseApplication) ApplySnapshotChunk(_ context.Context, req *RequestApplySnapshotChunk) (*ResponseApplySnapshotChunk, error) { + return &ResponseApplySnapshotChunk{}, nil +} + +func (BaseApplication) PrepareProposal(_ context.Context, req *RequestPrepareProposal) (*ResponsePrepareProposal, error) { + trs := make([]*TxRecord, 0, len(req.Txs)) + var totalBytes int64 + for _, tx := range req.Txs { + totalBytes += int64(len(tx)) + if totalBytes > req.MaxTxBytes { + break + } + trs = append(trs, &TxRecord{ + Action: TxRecord_UNMODIFIED, + Tx: tx, + }) } + return &ResponsePrepareProposal{TxRecords: trs}, nil } -func (BaseApplication) Query(req RequestQuery) ResponseQuery { - return ResponseQuery{Code: CodeTypeOK} +func (BaseApplication) ProcessProposal(_ context.Context, req *RequestProcessProposal) (*ResponseProcessProposal, error) { + return &ResponseProcessProposal{Status: ResponseProcessProposal_ACCEPT}, nil } -func (BaseApplication) InitChain(req RequestInitChain) ResponseInitChain { - return ResponseInitChain{} -} - -func (BaseApplication) ListSnapshots(req RequestListSnapshots) ResponseListSnapshots { - return ResponseListSnapshots{} -} - -func (BaseApplication) OfferSnapshot(req RequestOfferSnapshot) ResponseOfferSnapshot { - return ResponseOfferSnapshot{} -} - -func (BaseApplication) LoadSnapshotChunk(req RequestLoadSnapshotChunk) ResponseLoadSnapshotChunk { - return ResponseLoadSnapshotChunk{} -} - -func (BaseApplication) ApplySnapshotChunk(req RequestApplySnapshotChunk) ResponseApplySnapshotChunk { - return ResponseApplySnapshotChunk{} -} - -func (BaseApplication) PrepareProposal(req RequestPrepareProposal) ResponsePrepareProposal { - return ResponsePrepareProposal{} -} - -func (BaseApplication) ProcessProposal(req RequestProcessProposal) ResponseProcessProposal { - return ResponseProcessProposal{} -} - -func (BaseApplication) FinalizeBlock(req RequestFinalizeBlock) ResponseFinalizeBlock { +func (BaseApplication) FinalizeBlock(_ context.Context, req *RequestFinalizeBlock) (*ResponseFinalizeBlock, error) { txs := make([]*ExecTxResult, len(req.Txs)) for i := range req.Txs { txs[i] = &ExecTxResult{Code: CodeTypeOK} } - return ResponseFinalizeBlock{ + return &ResponseFinalizeBlock{ TxResults: txs, - } -} - -//------------------------------------------------------- - -// GRPCApplication is a GRPC wrapper for Application -type GRPCApplication struct { - app Application -} - -func NewGRPCApplication(app Application) *GRPCApplication { - return &GRPCApplication{app} -} - -func (app *GRPCApplication) Echo(ctx context.Context, req *RequestEcho) (*ResponseEcho, error) { - return &ResponseEcho{Message: req.Message}, nil -} - -func (app *GRPCApplication) Flush(ctx context.Context, req *RequestFlush) (*ResponseFlush, error) { - return &ResponseFlush{}, nil -} - -func (app *GRPCApplication) Info(ctx context.Context, req *RequestInfo) (*ResponseInfo, error) { - res := app.app.Info(*req) - return &res, nil -} - -func (app *GRPCApplication) CheckTx(ctx context.Context, req *RequestCheckTx) (*ResponseCheckTx, error) { - res := app.app.CheckTx(*req) - return &res, nil -} - -func (app *GRPCApplication) Query(ctx context.Context, req *RequestQuery) (*ResponseQuery, error) { - res := app.app.Query(*req) - return &res, nil -} - -func (app *GRPCApplication) Commit(ctx context.Context, req *RequestCommit) (*ResponseCommit, error) { - res := app.app.Commit() - return &res, nil -} - -func (app *GRPCApplication) InitChain(ctx context.Context, req *RequestInitChain) (*ResponseInitChain, error) { - res := app.app.InitChain(*req) - return &res, nil -} - -func (app *GRPCApplication) ListSnapshots( - ctx context.Context, req *RequestListSnapshots) (*ResponseListSnapshots, error) { - res := app.app.ListSnapshots(*req) - return &res, nil -} - -func (app *GRPCApplication) OfferSnapshot( - ctx context.Context, req *RequestOfferSnapshot) (*ResponseOfferSnapshot, error) { - res := app.app.OfferSnapshot(*req) - return &res, nil -} - -func (app *GRPCApplication) LoadSnapshotChunk( - ctx context.Context, req *RequestLoadSnapshotChunk) (*ResponseLoadSnapshotChunk, error) { - res := app.app.LoadSnapshotChunk(*req) - return &res, nil -} - -func (app *GRPCApplication) ApplySnapshotChunk( - ctx context.Context, req *RequestApplySnapshotChunk) (*ResponseApplySnapshotChunk, error) { - res := app.app.ApplySnapshotChunk(*req) - return &res, nil -} - -func (app *GRPCApplication) ExtendVote( - ctx context.Context, req *RequestExtendVote) (*ResponseExtendVote, error) { - res := app.app.ExtendVote(*req) - return &res, nil -} - -func (app *GRPCApplication) VerifyVoteExtension( - ctx context.Context, req *RequestVerifyVoteExtension) (*ResponseVerifyVoteExtension, error) { - res := app.app.VerifyVoteExtension(*req) - return &res, nil -} - -func (app *GRPCApplication) PrepareProposal( - ctx context.Context, req *RequestPrepareProposal) (*ResponsePrepareProposal, error) { - res := app.app.PrepareProposal(*req) - return &res, nil -} - -func (app *GRPCApplication) ProcessProposal( - ctx context.Context, req *RequestProcessProposal) (*ResponseProcessProposal, error) { - res := app.app.ProcessProposal(*req) - return &res, nil -} - -func (app *GRPCApplication) FinalizeBlock( - ctx context.Context, req *RequestFinalizeBlock) (*ResponseFinalizeBlock, error) { - res := app.app.FinalizeBlock(*req) - return &res, nil + }, nil } diff --git a/abci/types/messages.go b/abci/types/messages.go index 74d9b9d1a..80ab19525 100644 --- a/abci/types/messages.go +++ b/abci/types/messages.go @@ -39,15 +39,15 @@ func ToRequestFlush() *Request { } } -func ToRequestInfo(req RequestInfo) *Request { +func ToRequestInfo(req *RequestInfo) *Request { return &Request{ - Value: &Request_Info{&req}, + Value: &Request_Info{req}, } } -func ToRequestCheckTx(req RequestCheckTx) *Request { +func ToRequestCheckTx(req *RequestCheckTx) *Request { return &Request{ - Value: &Request_CheckTx{&req}, + Value: &Request_CheckTx{req}, } } @@ -57,69 +57,69 @@ func ToRequestCommit() *Request { } } -func ToRequestQuery(req RequestQuery) *Request { +func ToRequestQuery(req *RequestQuery) *Request { return &Request{ - Value: &Request_Query{&req}, + Value: &Request_Query{req}, } } -func ToRequestInitChain(req RequestInitChain) *Request { +func ToRequestInitChain(req *RequestInitChain) *Request { return &Request{ - Value: &Request_InitChain{&req}, + Value: &Request_InitChain{req}, } } -func ToRequestListSnapshots(req RequestListSnapshots) *Request { +func ToRequestListSnapshots(req *RequestListSnapshots) *Request { return &Request{ - Value: &Request_ListSnapshots{&req}, + Value: &Request_ListSnapshots{req}, } } -func ToRequestOfferSnapshot(req RequestOfferSnapshot) *Request { +func ToRequestOfferSnapshot(req *RequestOfferSnapshot) *Request { return &Request{ - Value: &Request_OfferSnapshot{&req}, + Value: &Request_OfferSnapshot{req}, } } -func ToRequestLoadSnapshotChunk(req RequestLoadSnapshotChunk) *Request { +func ToRequestLoadSnapshotChunk(req *RequestLoadSnapshotChunk) *Request { return &Request{ - Value: &Request_LoadSnapshotChunk{&req}, + Value: &Request_LoadSnapshotChunk{req}, } } -func ToRequestApplySnapshotChunk(req RequestApplySnapshotChunk) *Request { +func ToRequestApplySnapshotChunk(req *RequestApplySnapshotChunk) *Request { return &Request{ - Value: &Request_ApplySnapshotChunk{&req}, + Value: &Request_ApplySnapshotChunk{req}, } } -func ToRequestExtendVote(req RequestExtendVote) *Request { +func ToRequestExtendVote(req *RequestExtendVote) *Request { return &Request{ - Value: &Request_ExtendVote{&req}, + Value: &Request_ExtendVote{req}, } } -func ToRequestVerifyVoteExtension(req RequestVerifyVoteExtension) *Request { +func ToRequestVerifyVoteExtension(req *RequestVerifyVoteExtension) *Request { return &Request{ - Value: &Request_VerifyVoteExtension{&req}, + Value: &Request_VerifyVoteExtension{req}, } } -func ToRequestPrepareProposal(req RequestPrepareProposal) *Request { +func ToRequestPrepareProposal(req *RequestPrepareProposal) *Request { return &Request{ - Value: &Request_PrepareProposal{&req}, + Value: &Request_PrepareProposal{req}, } } -func ToRequestProcessProposal(req RequestProcessProposal) *Request { +func ToRequestProcessProposal(req *RequestProcessProposal) *Request { return &Request{ - Value: &Request_ProcessProposal{&req}, + Value: &Request_ProcessProposal{req}, } } -func ToRequestFinalizeBlock(req RequestFinalizeBlock) *Request { +func ToRequestFinalizeBlock(req *RequestFinalizeBlock) *Request { return &Request{ - Value: &Request_FinalizeBlock{&req}, + Value: &Request_FinalizeBlock{req}, } } @@ -143,86 +143,86 @@ func ToResponseFlush() *Response { } } -func ToResponseInfo(res ResponseInfo) *Response { +func ToResponseInfo(res *ResponseInfo) *Response { return &Response{ - Value: &Response_Info{&res}, + Value: &Response_Info{res}, } } -func ToResponseCheckTx(res ResponseCheckTx) *Response { +func ToResponseCheckTx(res *ResponseCheckTx) *Response { return &Response{ - Value: &Response_CheckTx{&res}, + Value: &Response_CheckTx{res}, } } -func ToResponseCommit(res ResponseCommit) *Response { +func ToResponseCommit(res *ResponseCommit) *Response { return &Response{ - Value: &Response_Commit{&res}, + Value: &Response_Commit{res}, } } -func ToResponseQuery(res ResponseQuery) *Response { +func ToResponseQuery(res *ResponseQuery) *Response { return &Response{ - Value: &Response_Query{&res}, + Value: &Response_Query{res}, } } -func ToResponseInitChain(res ResponseInitChain) *Response { +func ToResponseInitChain(res *ResponseInitChain) *Response { return &Response{ - Value: &Response_InitChain{&res}, + Value: &Response_InitChain{res}, } } -func ToResponseListSnapshots(res ResponseListSnapshots) *Response { +func ToResponseListSnapshots(res *ResponseListSnapshots) *Response { return &Response{ - Value: &Response_ListSnapshots{&res}, + Value: &Response_ListSnapshots{res}, } } -func ToResponseOfferSnapshot(res ResponseOfferSnapshot) *Response { +func ToResponseOfferSnapshot(res *ResponseOfferSnapshot) *Response { return &Response{ - Value: &Response_OfferSnapshot{&res}, + Value: &Response_OfferSnapshot{res}, } } -func ToResponseLoadSnapshotChunk(res ResponseLoadSnapshotChunk) *Response { +func ToResponseLoadSnapshotChunk(res *ResponseLoadSnapshotChunk) *Response { return &Response{ - Value: &Response_LoadSnapshotChunk{&res}, + Value: &Response_LoadSnapshotChunk{res}, } } -func ToResponseApplySnapshotChunk(res ResponseApplySnapshotChunk) *Response { +func ToResponseApplySnapshotChunk(res *ResponseApplySnapshotChunk) *Response { return &Response{ - Value: &Response_ApplySnapshotChunk{&res}, + Value: &Response_ApplySnapshotChunk{res}, } } -func ToResponseExtendVote(res ResponseExtendVote) *Response { +func ToResponseExtendVote(res *ResponseExtendVote) *Response { return &Response{ - Value: &Response_ExtendVote{&res}, + Value: &Response_ExtendVote{res}, } } -func ToResponseVerifyVoteExtension(res ResponseVerifyVoteExtension) *Response { +func ToResponseVerifyVoteExtension(res *ResponseVerifyVoteExtension) *Response { return &Response{ - Value: &Response_VerifyVoteExtension{&res}, + Value: &Response_VerifyVoteExtension{res}, } } -func ToResponsePrepareProposal(res ResponsePrepareProposal) *Response { +func ToResponsePrepareProposal(res *ResponsePrepareProposal) *Response { return &Response{ - Value: &Response_PrepareProposal{&res}, + Value: &Response_PrepareProposal{res}, } } -func ToResponseProcessProposal(res ResponseProcessProposal) *Response { +func ToResponseProcessProposal(res *ResponseProcessProposal) *Response { return &Response{ - Value: &Response_ProcessProposal{&res}, + Value: &Response_ProcessProposal{res}, } } -func ToResponseFinalizeBlock(res ResponseFinalizeBlock) *Response { +func ToResponseFinalizeBlock(res *ResponseFinalizeBlock) *Response { return &Response{ - Value: &Response_FinalizeBlock{&res}, + Value: &Response_FinalizeBlock{res}, } } diff --git a/abci/types/mocks/application.go b/abci/types/mocks/application.go index 30bf0f84c..2d35c481f 100644 --- a/abci/types/mocks/application.go +++ b/abci/types/mocks/application.go @@ -3,7 +3,11 @@ package mocks import ( + context "context" + testing "testing" + mock "github.com/stretchr/testify/mock" + types "github.com/tendermint/tendermint/abci/types" ) @@ -12,198 +16,334 @@ type Application struct { mock.Mock } -// ApplySnapshotChunk provides a mock function with given fields: _a0 -func (_m *Application) ApplySnapshotChunk(_a0 types.RequestApplySnapshotChunk) types.ResponseApplySnapshotChunk { +// ApplySnapshotChunk provides a mock function with given fields: _a0, _a1 +func (_m *Application) ApplySnapshotChunk(_a0 context.Context, _a1 *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { + ret := _m.Called(_a0, _a1) + + var r0 *types.ResponseApplySnapshotChunk + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestApplySnapshotChunk) *types.ResponseApplySnapshotChunk); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseApplySnapshotChunk) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestApplySnapshotChunk) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CheckTx provides a mock function with given fields: _a0, _a1 +func (_m *Application) CheckTx(_a0 context.Context, _a1 *types.RequestCheckTx) (*types.ResponseCheckTx, error) { + ret := _m.Called(_a0, _a1) + + var r0 *types.ResponseCheckTx + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestCheckTx) *types.ResponseCheckTx); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseCheckTx) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestCheckTx) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Commit provides a mock function with given fields: _a0 +func (_m *Application) Commit(_a0 context.Context) (*types.ResponseCommit, error) { ret := _m.Called(_a0) - var r0 types.ResponseApplySnapshotChunk - if rf, ok := ret.Get(0).(func(types.RequestApplySnapshotChunk) types.ResponseApplySnapshotChunk); ok { + var r0 *types.ResponseCommit + if rf, ok := ret.Get(0).(func(context.Context) *types.ResponseCommit); ok { r0 = rf(_a0) } else { - r0 = ret.Get(0).(types.ResponseApplySnapshotChunk) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseCommit) + } } - return r0 -} - -// CheckTx provides a mock function with given fields: _a0 -func (_m *Application) CheckTx(_a0 types.RequestCheckTx) types.ResponseCheckTx { - ret := _m.Called(_a0) - - var r0 types.ResponseCheckTx - if rf, ok := ret.Get(0).(func(types.RequestCheckTx) types.ResponseCheckTx); ok { - r0 = rf(_a0) + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) } else { - r0 = ret.Get(0).(types.ResponseCheckTx) + r1 = ret.Error(1) } - return r0 + return r0, r1 } -// Commit provides a mock function with given fields: -func (_m *Application) Commit() types.ResponseCommit { - ret := _m.Called() +// ExtendVote provides a mock function with given fields: _a0, _a1 +func (_m *Application) ExtendVote(_a0 context.Context, _a1 *types.RequestExtendVote) (*types.ResponseExtendVote, error) { + ret := _m.Called(_a0, _a1) - var r0 types.ResponseCommit - if rf, ok := ret.Get(0).(func() types.ResponseCommit); ok { - r0 = rf() + var r0 *types.ResponseExtendVote + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestExtendVote) *types.ResponseExtendVote); ok { + r0 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseCommit) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseExtendVote) + } } - return r0 -} - -// ExtendVote provides a mock function with given fields: _a0 -func (_m *Application) ExtendVote(_a0 types.RequestExtendVote) types.ResponseExtendVote { - ret := _m.Called(_a0) - - var r0 types.ResponseExtendVote - if rf, ok := ret.Get(0).(func(types.RequestExtendVote) types.ResponseExtendVote); ok { - r0 = rf(_a0) + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestExtendVote) error); ok { + r1 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseExtendVote) + r1 = ret.Error(1) } - return r0 + return r0, r1 } -// FinalizeBlock provides a mock function with given fields: _a0 -func (_m *Application) FinalizeBlock(_a0 types.RequestFinalizeBlock) types.ResponseFinalizeBlock { - ret := _m.Called(_a0) +// FinalizeBlock provides a mock function with given fields: _a0, _a1 +func (_m *Application) FinalizeBlock(_a0 context.Context, _a1 *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { + ret := _m.Called(_a0, _a1) - var r0 types.ResponseFinalizeBlock - if rf, ok := ret.Get(0).(func(types.RequestFinalizeBlock) types.ResponseFinalizeBlock); ok { - r0 = rf(_a0) + var r0 *types.ResponseFinalizeBlock + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestFinalizeBlock) *types.ResponseFinalizeBlock); ok { + r0 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseFinalizeBlock) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseFinalizeBlock) + } } - return r0 -} - -// Info provides a mock function with given fields: _a0 -func (_m *Application) Info(_a0 types.RequestInfo) types.ResponseInfo { - ret := _m.Called(_a0) - - var r0 types.ResponseInfo - if rf, ok := ret.Get(0).(func(types.RequestInfo) types.ResponseInfo); ok { - r0 = rf(_a0) + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestFinalizeBlock) error); ok { + r1 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseInfo) + r1 = ret.Error(1) } - return r0 + return r0, r1 } -// InitChain provides a mock function with given fields: _a0 -func (_m *Application) InitChain(_a0 types.RequestInitChain) types.ResponseInitChain { - ret := _m.Called(_a0) +// Info provides a mock function with given fields: _a0, _a1 +func (_m *Application) Info(_a0 context.Context, _a1 *types.RequestInfo) (*types.ResponseInfo, error) { + ret := _m.Called(_a0, _a1) - var r0 types.ResponseInitChain - if rf, ok := ret.Get(0).(func(types.RequestInitChain) types.ResponseInitChain); ok { - r0 = rf(_a0) + var r0 *types.ResponseInfo + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestInfo) *types.ResponseInfo); ok { + r0 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseInitChain) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseInfo) + } } - return r0 -} - -// ListSnapshots provides a mock function with given fields: _a0 -func (_m *Application) ListSnapshots(_a0 types.RequestListSnapshots) types.ResponseListSnapshots { - ret := _m.Called(_a0) - - var r0 types.ResponseListSnapshots - if rf, ok := ret.Get(0).(func(types.RequestListSnapshots) types.ResponseListSnapshots); ok { - r0 = rf(_a0) + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestInfo) error); ok { + r1 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseListSnapshots) + r1 = ret.Error(1) } - return r0 + return r0, r1 } -// LoadSnapshotChunk provides a mock function with given fields: _a0 -func (_m *Application) LoadSnapshotChunk(_a0 types.RequestLoadSnapshotChunk) types.ResponseLoadSnapshotChunk { - ret := _m.Called(_a0) +// InitChain provides a mock function with given fields: _a0, _a1 +func (_m *Application) InitChain(_a0 context.Context, _a1 *types.RequestInitChain) (*types.ResponseInitChain, error) { + ret := _m.Called(_a0, _a1) - var r0 types.ResponseLoadSnapshotChunk - if rf, ok := ret.Get(0).(func(types.RequestLoadSnapshotChunk) types.ResponseLoadSnapshotChunk); ok { - r0 = rf(_a0) + var r0 *types.ResponseInitChain + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestInitChain) *types.ResponseInitChain); ok { + r0 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseLoadSnapshotChunk) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseInitChain) + } } - return r0 -} - -// OfferSnapshot provides a mock function with given fields: _a0 -func (_m *Application) OfferSnapshot(_a0 types.RequestOfferSnapshot) types.ResponseOfferSnapshot { - ret := _m.Called(_a0) - - var r0 types.ResponseOfferSnapshot - if rf, ok := ret.Get(0).(func(types.RequestOfferSnapshot) types.ResponseOfferSnapshot); ok { - r0 = rf(_a0) + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestInitChain) error); ok { + r1 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseOfferSnapshot) + r1 = ret.Error(1) } - return r0 + return r0, r1 } -// PrepareProposal provides a mock function with given fields: _a0 -func (_m *Application) PrepareProposal(_a0 types.RequestPrepareProposal) types.ResponsePrepareProposal { - ret := _m.Called(_a0) +// ListSnapshots provides a mock function with given fields: _a0, _a1 +func (_m *Application) ListSnapshots(_a0 context.Context, _a1 *types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { + ret := _m.Called(_a0, _a1) - var r0 types.ResponsePrepareProposal - if rf, ok := ret.Get(0).(func(types.RequestPrepareProposal) types.ResponsePrepareProposal); ok { - r0 = rf(_a0) + var r0 *types.ResponseListSnapshots + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestListSnapshots) *types.ResponseListSnapshots); ok { + r0 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponsePrepareProposal) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseListSnapshots) + } } - return r0 -} - -// ProcessProposal provides a mock function with given fields: _a0 -func (_m *Application) ProcessProposal(_a0 types.RequestProcessProposal) types.ResponseProcessProposal { - ret := _m.Called(_a0) - - var r0 types.ResponseProcessProposal - if rf, ok := ret.Get(0).(func(types.RequestProcessProposal) types.ResponseProcessProposal); ok { - r0 = rf(_a0) + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestListSnapshots) error); ok { + r1 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseProcessProposal) + r1 = ret.Error(1) } - return r0 + return r0, r1 } -// Query provides a mock function with given fields: _a0 -func (_m *Application) Query(_a0 types.RequestQuery) types.ResponseQuery { - ret := _m.Called(_a0) +// LoadSnapshotChunk provides a mock function with given fields: _a0, _a1 +func (_m *Application) LoadSnapshotChunk(_a0 context.Context, _a1 *types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { + ret := _m.Called(_a0, _a1) - var r0 types.ResponseQuery - if rf, ok := ret.Get(0).(func(types.RequestQuery) types.ResponseQuery); ok { - r0 = rf(_a0) + var r0 *types.ResponseLoadSnapshotChunk + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestLoadSnapshotChunk) *types.ResponseLoadSnapshotChunk); ok { + r0 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseQuery) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseLoadSnapshotChunk) + } } - return r0 -} - -// VerifyVoteExtension provides a mock function with given fields: _a0 -func (_m *Application) VerifyVoteExtension(_a0 types.RequestVerifyVoteExtension) types.ResponseVerifyVoteExtension { - ret := _m.Called(_a0) - - var r0 types.ResponseVerifyVoteExtension - if rf, ok := ret.Get(0).(func(types.RequestVerifyVoteExtension) types.ResponseVerifyVoteExtension); ok { - r0 = rf(_a0) + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestLoadSnapshotChunk) error); ok { + r1 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseVerifyVoteExtension) + r1 = ret.Error(1) } - return r0 + return r0, r1 +} + +// OfferSnapshot provides a mock function with given fields: _a0, _a1 +func (_m *Application) OfferSnapshot(_a0 context.Context, _a1 *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { + ret := _m.Called(_a0, _a1) + + var r0 *types.ResponseOfferSnapshot + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestOfferSnapshot) *types.ResponseOfferSnapshot); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseOfferSnapshot) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestOfferSnapshot) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PrepareProposal provides a mock function with given fields: _a0, _a1 +func (_m *Application) PrepareProposal(_a0 context.Context, _a1 *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { + ret := _m.Called(_a0, _a1) + + var r0 *types.ResponsePrepareProposal + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestPrepareProposal) *types.ResponsePrepareProposal); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponsePrepareProposal) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestPrepareProposal) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProcessProposal provides a mock function with given fields: _a0, _a1 +func (_m *Application) ProcessProposal(_a0 context.Context, _a1 *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { + ret := _m.Called(_a0, _a1) + + var r0 *types.ResponseProcessProposal + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestProcessProposal) *types.ResponseProcessProposal); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseProcessProposal) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestProcessProposal) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Query provides a mock function with given fields: _a0, _a1 +func (_m *Application) Query(_a0 context.Context, _a1 *types.RequestQuery) (*types.ResponseQuery, error) { + ret := _m.Called(_a0, _a1) + + var r0 *types.ResponseQuery + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestQuery) *types.ResponseQuery); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseQuery) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestQuery) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// VerifyVoteExtension provides a mock function with given fields: _a0, _a1 +func (_m *Application) VerifyVoteExtension(_a0 context.Context, _a1 *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) { + ret := _m.Called(_a0, _a1) + + var r0 *types.ResponseVerifyVoteExtension + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestVerifyVoteExtension) *types.ResponseVerifyVoteExtension); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseVerifyVoteExtension) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestVerifyVoteExtension) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewApplication creates a new instance of Application. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewApplication(t testing.TB) *Application { + mock := &Application{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock } diff --git a/abci/types/mocks/base.go b/abci/types/mocks/base.go deleted file mode 100644 index f9d84b858..000000000 --- a/abci/types/mocks/base.go +++ /dev/null @@ -1,189 +0,0 @@ -package mocks - -import ( - types "github.com/tendermint/tendermint/abci/types" -) - -// BaseMock provides a wrapper around the generated Application mock and a BaseApplication. -// BaseMock first tries to use the mock's implementation of the method. -// If no functionality was provided for the mock by the user, BaseMock dispatches -// to the BaseApplication and uses its functionality. -// BaseMock allows users to provide mocked functionality for only the methods that matter -// for their test while avoiding a panic if the code calls Application methods that are -// not relevant to the test. -type BaseMock struct { - base *types.BaseApplication - *Application -} - -func NewBaseMock() BaseMock { - return BaseMock{ - base: types.NewBaseApplication(), - Application: new(Application), - } -} - -// Info/Query Connection -// Return application info -func (m BaseMock) Info(input types.RequestInfo) types.ResponseInfo { - var ret types.ResponseInfo - defer func() { - if r := recover(); r != nil { - ret = m.base.Info(input) - } - }() - ret = m.Application.Info(input) - return ret -} - -func (m BaseMock) Query(input types.RequestQuery) types.ResponseQuery { - var ret types.ResponseQuery - defer func() { - if r := recover(); r != nil { - ret = m.base.Query(input) - } - }() - ret = m.Application.Query(input) - return ret -} - -// Mempool Connection -// Validate a tx for the mempool -func (m BaseMock) CheckTx(input types.RequestCheckTx) types.ResponseCheckTx { - var ret types.ResponseCheckTx - defer func() { - if r := recover(); r != nil { - ret = m.base.CheckTx(input) - } - }() - ret = m.Application.CheckTx(input) - return ret -} - -// Consensus Connection -// Initialize blockchain w validators/other info from TendermintCore -func (m BaseMock) InitChain(input types.RequestInitChain) types.ResponseInitChain { - var ret types.ResponseInitChain - defer func() { - if r := recover(); r != nil { - ret = m.base.InitChain(input) - } - }() - ret = m.Application.InitChain(input) - return ret -} - -func (m BaseMock) PrepareProposal(input types.RequestPrepareProposal) types.ResponsePrepareProposal { - var ret types.ResponsePrepareProposal - defer func() { - if r := recover(); r != nil { - ret = m.base.PrepareProposal(input) - } - }() - ret = m.Application.PrepareProposal(input) - return ret -} - -func (m BaseMock) ProcessProposal(input types.RequestProcessProposal) types.ResponseProcessProposal { - var ret types.ResponseProcessProposal - defer func() { - if r := recover(); r != nil { - ret = m.base.ProcessProposal(input) - } - }() - ret = m.Application.ProcessProposal(input) - return ret -} - -// Commit the state and return the application Merkle root hash -func (m BaseMock) Commit() types.ResponseCommit { - var ret types.ResponseCommit - defer func() { - if r := recover(); r != nil { - ret = m.base.Commit() - } - }() - ret = m.Application.Commit() - return ret -} - -// Create application specific vote extension -func (m BaseMock) ExtendVote(input types.RequestExtendVote) types.ResponseExtendVote { - var ret types.ResponseExtendVote - defer func() { - if r := recover(); r != nil { - ret = m.base.ExtendVote(input) - } - }() - ret = m.Application.ExtendVote(input) - return ret -} - -// Verify application's vote extension data -func (m BaseMock) VerifyVoteExtension(input types.RequestVerifyVoteExtension) types.ResponseVerifyVoteExtension { - var ret types.ResponseVerifyVoteExtension - defer func() { - if r := recover(); r != nil { - ret = m.base.VerifyVoteExtension(input) - } - }() - ret = m.Application.VerifyVoteExtension(input) - return ret -} - -// State Sync Connection -// List available snapshots -func (m BaseMock) ListSnapshots(input types.RequestListSnapshots) types.ResponseListSnapshots { - var ret types.ResponseListSnapshots - defer func() { - if r := recover(); r != nil { - ret = m.base.ListSnapshots(input) - } - }() - ret = m.Application.ListSnapshots(input) - return ret -} - -func (m BaseMock) OfferSnapshot(input types.RequestOfferSnapshot) types.ResponseOfferSnapshot { - var ret types.ResponseOfferSnapshot - defer func() { - if r := recover(); r != nil { - ret = m.base.OfferSnapshot(input) - } - }() - ret = m.Application.OfferSnapshot(input) - return ret -} - -func (m BaseMock) LoadSnapshotChunk(input types.RequestLoadSnapshotChunk) types.ResponseLoadSnapshotChunk { - var ret types.ResponseLoadSnapshotChunk - defer func() { - if r := recover(); r != nil { - ret = m.base.LoadSnapshotChunk(input) - } - }() - ret = m.Application.LoadSnapshotChunk(input) - return ret -} - -func (m BaseMock) ApplySnapshotChunk(input types.RequestApplySnapshotChunk) types.ResponseApplySnapshotChunk { - var ret types.ResponseApplySnapshotChunk - defer func() { - if r := recover(); r != nil { - ret = m.base.ApplySnapshotChunk(input) - } - }() - ret = m.Application.ApplySnapshotChunk(input) - return ret -} - -func (m BaseMock) FinalizeBlock(input types.RequestFinalizeBlock) types.ResponseFinalizeBlock { - var ret types.ResponseFinalizeBlock - defer func() { - if r := recover(); r != nil { - ret = m.base.FinalizeBlock(input) - } - }() - ret = m.Application.FinalizeBlock(input) - return ret -} diff --git a/abci/types/result.go b/abci/types/types.go similarity index 71% rename from abci/types/result.go rename to abci/types/types.go index a7198b633..d13947d1a 100644 --- a/abci/types/result.go +++ b/abci/types/types.go @@ -5,8 +5,6 @@ import ( "encoding/json" "github.com/gogo/protobuf/jsonpb" - - types "github.com/tendermint/tendermint/proto/tendermint/types" ) const ( @@ -53,19 +51,27 @@ func (r ResponseQuery) IsErr() bool { return r.Code != CodeTypeOK } -// IsUnknown returns true if Code is Unknown -func (r ResponseVerifyVoteExtension) IsUnknown() bool { - return r.Result == ResponseVerifyVoteExtension_UNKNOWN +func (r ResponseProcessProposal) IsAccepted() bool { + return r.Status == ResponseProcessProposal_ACCEPT +} + +func (r ResponseProcessProposal) IsStatusUnknown() bool { + return r.Status == ResponseProcessProposal_UNKNOWN +} + +// IsStatusUnknown returns true if Code is Unknown +func (r ResponseVerifyVoteExtension) IsStatusUnknown() bool { + return r.Status == ResponseVerifyVoteExtension_UNKNOWN } // IsOK returns true if Code is OK func (r ResponseVerifyVoteExtension) IsOK() bool { - return r.Result == ResponseVerifyVoteExtension_ACCEPT + return r.Status == ResponseVerifyVoteExtension_ACCEPT } // IsErr returns true if Code is something other than OK. func (r ResponseVerifyVoteExtension) IsErr() bool { - return r.Result != ResponseVerifyVoteExtension_ACCEPT + return r.Status != ResponseVerifyVoteExtension_ACCEPT } //--------------------------------------------------------------------------- @@ -149,21 +155,40 @@ var _ jsonRoundTripper = (*EventAttribute)(nil) // ----------------------------------------------- // construct Result data -func RespondExtendVote(appDataToSign, appDataSelfAuthenticating []byte) ResponseExtendVote { - return ResponseExtendVote{ - VoteExtension: &types.VoteExtension{ - AppDataToSign: appDataToSign, - AppDataSelfAuthenticating: appDataSelfAuthenticating, - }, +func RespondVerifyVoteExtension(ok bool) ResponseVerifyVoteExtension { + status := ResponseVerifyVoteExtension_REJECT + if ok { + status = ResponseVerifyVoteExtension_ACCEPT + } + return ResponseVerifyVoteExtension{ + Status: status, } } -func RespondVerifyVoteExtension(ok bool) ResponseVerifyVoteExtension { - result := ResponseVerifyVoteExtension_REJECT - if ok { - result = ResponseVerifyVoteExtension_ACCEPT - } - return ResponseVerifyVoteExtension{ - Result: result, +// deterministicExecTxResult constructs a copy of response that omits +// non-deterministic fields. The input response is not modified. +func deterministicExecTxResult(response *ExecTxResult) *ExecTxResult { + return &ExecTxResult{ + Code: response.Code, + Data: response.Data, + GasWanted: response.GasWanted, + GasUsed: response.GasUsed, } } + +// MarshalTxResults encodes the the TxResults as a list of byte +// slices. It strips off the non-deterministic pieces of the TxResults +// so that the resulting data can be used for hash comparisons and used +// in Merkle proofs. +func MarshalTxResults(r []*ExecTxResult) ([][]byte, error) { + s := make([][]byte, len(r)) + for i, e := range r { + d := deterministicExecTxResult(e) + b, err := d.Marshal() + if err != nil { + return nil, err + } + s[i] = b + } + return s, nil +} diff --git a/abci/types/types.pb.go b/abci/types/types.pb.go index 06a25a86d..dd1308628 100644 --- a/abci/types/types.pb.go +++ b/abci/types/types.pb.go @@ -58,31 +58,31 @@ func (CheckTxType) EnumDescriptor() ([]byte, []int) { return fileDescriptor_252557cfdd89a31a, []int{0} } -type EvidenceType int32 +type MisbehaviorType int32 const ( - EvidenceType_UNKNOWN EvidenceType = 0 - EvidenceType_DUPLICATE_VOTE EvidenceType = 1 - EvidenceType_LIGHT_CLIENT_ATTACK EvidenceType = 2 + MisbehaviorType_UNKNOWN MisbehaviorType = 0 + MisbehaviorType_DUPLICATE_VOTE MisbehaviorType = 1 + MisbehaviorType_LIGHT_CLIENT_ATTACK MisbehaviorType = 2 ) -var EvidenceType_name = map[int32]string{ +var MisbehaviorType_name = map[int32]string{ 0: "UNKNOWN", 1: "DUPLICATE_VOTE", 2: "LIGHT_CLIENT_ATTACK", } -var EvidenceType_value = map[string]int32{ +var MisbehaviorType_value = map[string]int32{ "UNKNOWN": 0, "DUPLICATE_VOTE": 1, "LIGHT_CLIENT_ATTACK": 2, } -func (x EvidenceType) String() string { - return proto.EnumName(EvidenceType_name, int32(x)) +func (x MisbehaviorType) String() string { + return proto.EnumName(MisbehaviorType_name, int32(x)) } -func (EvidenceType) EnumDescriptor() ([]byte, []int) { +func (MisbehaviorType) EnumDescriptor() ([]byte, []int) { return fileDescriptor_252557cfdd89a31a, []int{1} } @@ -160,37 +160,94 @@ func (ResponseApplySnapshotChunk_Result) EnumDescriptor() ([]byte, []int) { return fileDescriptor_252557cfdd89a31a, []int{35, 0} } -type ResponseVerifyVoteExtension_Result int32 +type ResponseProcessProposal_ProposalStatus int32 const ( - ResponseVerifyVoteExtension_UNKNOWN ResponseVerifyVoteExtension_Result = 0 - ResponseVerifyVoteExtension_ACCEPT ResponseVerifyVoteExtension_Result = 1 - ResponseVerifyVoteExtension_SLASH ResponseVerifyVoteExtension_Result = 2 - ResponseVerifyVoteExtension_REJECT ResponseVerifyVoteExtension_Result = 3 + ResponseProcessProposal_UNKNOWN ResponseProcessProposal_ProposalStatus = 0 + ResponseProcessProposal_ACCEPT ResponseProcessProposal_ProposalStatus = 1 + ResponseProcessProposal_REJECT ResponseProcessProposal_ProposalStatus = 2 ) -var ResponseVerifyVoteExtension_Result_name = map[int32]string{ +var ResponseProcessProposal_ProposalStatus_name = map[int32]string{ 0: "UNKNOWN", 1: "ACCEPT", - 2: "SLASH", - 3: "REJECT", + 2: "REJECT", } -var ResponseVerifyVoteExtension_Result_value = map[string]int32{ +var ResponseProcessProposal_ProposalStatus_value = map[string]int32{ "UNKNOWN": 0, "ACCEPT": 1, - "SLASH": 2, - "REJECT": 3, + "REJECT": 2, } -func (x ResponseVerifyVoteExtension_Result) String() string { - return proto.EnumName(ResponseVerifyVoteExtension_Result_name, int32(x)) +func (x ResponseProcessProposal_ProposalStatus) String() string { + return proto.EnumName(ResponseProcessProposal_ProposalStatus_name, int32(x)) } -func (ResponseVerifyVoteExtension_Result) EnumDescriptor() ([]byte, []int) { +func (ResponseProcessProposal_ProposalStatus) EnumDescriptor() ([]byte, []int) { return fileDescriptor_252557cfdd89a31a, []int{37, 0} } +type ResponseVerifyVoteExtension_VerifyStatus int32 + +const ( + ResponseVerifyVoteExtension_UNKNOWN ResponseVerifyVoteExtension_VerifyStatus = 0 + ResponseVerifyVoteExtension_ACCEPT ResponseVerifyVoteExtension_VerifyStatus = 1 + ResponseVerifyVoteExtension_REJECT ResponseVerifyVoteExtension_VerifyStatus = 2 +) + +var ResponseVerifyVoteExtension_VerifyStatus_name = map[int32]string{ + 0: "UNKNOWN", + 1: "ACCEPT", + 2: "REJECT", +} + +var ResponseVerifyVoteExtension_VerifyStatus_value = map[string]int32{ + "UNKNOWN": 0, + "ACCEPT": 1, + "REJECT": 2, +} + +func (x ResponseVerifyVoteExtension_VerifyStatus) String() string { + return proto.EnumName(ResponseVerifyVoteExtension_VerifyStatus_name, int32(x)) +} + +func (ResponseVerifyVoteExtension_VerifyStatus) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_252557cfdd89a31a, []int{39, 0} +} + +// TxAction contains App-provided information on what to do with a transaction that is part of a raw proposal +type TxRecord_TxAction int32 + +const ( + TxRecord_UNKNOWN TxRecord_TxAction = 0 + TxRecord_UNMODIFIED TxRecord_TxAction = 1 + TxRecord_ADDED TxRecord_TxAction = 2 + TxRecord_REMOVED TxRecord_TxAction = 3 +) + +var TxRecord_TxAction_name = map[int32]string{ + 0: "UNKNOWN", + 1: "UNMODIFIED", + 2: "ADDED", + 3: "REMOVED", +} + +var TxRecord_TxAction_value = map[string]int32{ + "UNKNOWN": 0, + "UNMODIFIED": 1, + "ADDED": 2, + "REMOVED": 3, +} + +func (x TxRecord_TxAction) String() string { + return proto.EnumName(TxRecord_TxAction_name, int32(x)) +} + +func (TxRecord_TxAction) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_252557cfdd89a31a, []int{47, 0} +} + type Request struct { // Types that are valid to be assigned to Value: // *Request_Echo @@ -804,7 +861,7 @@ type RequestBeginBlock struct { Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` Header types1.Header `protobuf:"bytes,2,opt,name=header,proto3" json:"header"` LastCommitInfo CommitInfo `protobuf:"bytes,3,opt,name=last_commit_info,json=lastCommitInfo,proto3" json:"last_commit_info"` - ByzantineValidators []Evidence `protobuf:"bytes,4,rep,name=byzantine_validators,json=byzantineValidators,proto3" json:"byzantine_validators"` + ByzantineValidators []Misbehavior `protobuf:"bytes,4,rep,name=byzantine_validators,json=byzantineValidators,proto3" json:"byzantine_validators"` } func (m *RequestBeginBlock) Reset() { *m = RequestBeginBlock{} } @@ -861,7 +918,7 @@ func (m *RequestBeginBlock) GetLastCommitInfo() CommitInfo { return CommitInfo{} } -func (m *RequestBeginBlock) GetByzantineValidators() []Evidence { +func (m *RequestBeginBlock) GetByzantineValidators() []Misbehavior { if m != nil { return m.ByzantineValidators } @@ -1256,113 +1313,26 @@ func (m *RequestApplySnapshotChunk) GetSender() string { return "" } -// Extends a vote with application-side injection -type RequestExtendVote struct { - Vote *types1.Vote `protobuf:"bytes,1,opt,name=vote,proto3" json:"vote,omitempty"` -} - -func (m *RequestExtendVote) Reset() { *m = RequestExtendVote{} } -func (m *RequestExtendVote) String() string { return proto.CompactTextString(m) } -func (*RequestExtendVote) ProtoMessage() {} -func (*RequestExtendVote) Descriptor() ([]byte, []int) { - return fileDescriptor_252557cfdd89a31a, []int{15} -} -func (m *RequestExtendVote) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *RequestExtendVote) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_RequestExtendVote.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *RequestExtendVote) XXX_Merge(src proto.Message) { - xxx_messageInfo_RequestExtendVote.Merge(m, src) -} -func (m *RequestExtendVote) XXX_Size() int { - return m.Size() -} -func (m *RequestExtendVote) XXX_DiscardUnknown() { - xxx_messageInfo_RequestExtendVote.DiscardUnknown(m) -} - -var xxx_messageInfo_RequestExtendVote proto.InternalMessageInfo - -func (m *RequestExtendVote) GetVote() *types1.Vote { - if m != nil { - return m.Vote - } - return nil -} - -// Verify the vote extension -type RequestVerifyVoteExtension struct { - Vote *types1.Vote `protobuf:"bytes,1,opt,name=vote,proto3" json:"vote,omitempty"` -} - -func (m *RequestVerifyVoteExtension) Reset() { *m = RequestVerifyVoteExtension{} } -func (m *RequestVerifyVoteExtension) String() string { return proto.CompactTextString(m) } -func (*RequestVerifyVoteExtension) ProtoMessage() {} -func (*RequestVerifyVoteExtension) Descriptor() ([]byte, []int) { - return fileDescriptor_252557cfdd89a31a, []int{16} -} -func (m *RequestVerifyVoteExtension) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *RequestVerifyVoteExtension) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_RequestVerifyVoteExtension.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *RequestVerifyVoteExtension) XXX_Merge(src proto.Message) { - xxx_messageInfo_RequestVerifyVoteExtension.Merge(m, src) -} -func (m *RequestVerifyVoteExtension) XXX_Size() int { - return m.Size() -} -func (m *RequestVerifyVoteExtension) XXX_DiscardUnknown() { - xxx_messageInfo_RequestVerifyVoteExtension.DiscardUnknown(m) -} - -var xxx_messageInfo_RequestVerifyVoteExtension proto.InternalMessageInfo - -func (m *RequestVerifyVoteExtension) GetVote() *types1.Vote { - if m != nil { - return m.Vote - } - return nil -} - type RequestPrepareProposal struct { - // block_data is an array of transactions that will be included in a block, + // the modified transactions cannot exceed this size. + MaxTxBytes int64 `protobuf:"varint,1,opt,name=max_tx_bytes,json=maxTxBytes,proto3" json:"max_tx_bytes,omitempty"` + // txs is an array of transactions that will be included in a block, // sent to the app for possible modifications. - // applications can not exceed the size of the data passed to it. - BlockData [][]byte `protobuf:"bytes,1,rep,name=block_data,json=blockData,proto3" json:"block_data,omitempty"` - // If an application decides to populate block_data with extra information, they can not exceed this value. - BlockDataSize int64 `protobuf:"varint,2,opt,name=block_data_size,json=blockDataSize,proto3" json:"block_data_size,omitempty"` - // votes includes all votes from the previous block. This contains vote extension data that can be used in proposal - // preparation. The votes here will then form the last commit that gets sent in the proposed block. - Votes []*types1.Vote `protobuf:"bytes,3,rep,name=votes,proto3" json:"votes,omitempty"` + Txs [][]byte `protobuf:"bytes,2,rep,name=txs,proto3" json:"txs,omitempty"` + LocalLastCommit ExtendedCommitInfo `protobuf:"bytes,3,opt,name=local_last_commit,json=localLastCommit,proto3" json:"local_last_commit"` + ByzantineValidators []Misbehavior `protobuf:"bytes,4,rep,name=byzantine_validators,json=byzantineValidators,proto3" json:"byzantine_validators"` + Height int64 `protobuf:"varint,5,opt,name=height,proto3" json:"height,omitempty"` + Time time.Time `protobuf:"bytes,6,opt,name=time,proto3,stdtime" json:"time"` + NextValidatorsHash []byte `protobuf:"bytes,7,opt,name=next_validators_hash,json=nextValidatorsHash,proto3" json:"next_validators_hash,omitempty"` + // address of the public key of the validator proposing the block. + ProposerAddress []byte `protobuf:"bytes,8,opt,name=proposer_address,json=proposerAddress,proto3" json:"proposer_address,omitempty"` } func (m *RequestPrepareProposal) Reset() { *m = RequestPrepareProposal{} } func (m *RequestPrepareProposal) String() string { return proto.CompactTextString(m) } func (*RequestPrepareProposal) ProtoMessage() {} func (*RequestPrepareProposal) Descriptor() ([]byte, []int) { - return fileDescriptor_252557cfdd89a31a, []int{17} + return fileDescriptor_252557cfdd89a31a, []int{15} } func (m *RequestPrepareProposal) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1391,40 +1361,80 @@ func (m *RequestPrepareProposal) XXX_DiscardUnknown() { var xxx_messageInfo_RequestPrepareProposal proto.InternalMessageInfo -func (m *RequestPrepareProposal) GetBlockData() [][]byte { +func (m *RequestPrepareProposal) GetMaxTxBytes() int64 { if m != nil { - return m.BlockData - } - return nil -} - -func (m *RequestPrepareProposal) GetBlockDataSize() int64 { - if m != nil { - return m.BlockDataSize + return m.MaxTxBytes } return 0 } -func (m *RequestPrepareProposal) GetVotes() []*types1.Vote { +func (m *RequestPrepareProposal) GetTxs() [][]byte { if m != nil { - return m.Votes + return m.Txs + } + return nil +} + +func (m *RequestPrepareProposal) GetLocalLastCommit() ExtendedCommitInfo { + if m != nil { + return m.LocalLastCommit + } + return ExtendedCommitInfo{} +} + +func (m *RequestPrepareProposal) GetByzantineValidators() []Misbehavior { + if m != nil { + return m.ByzantineValidators + } + return nil +} + +func (m *RequestPrepareProposal) GetHeight() int64 { + if m != nil { + return m.Height + } + return 0 +} + +func (m *RequestPrepareProposal) GetTime() time.Time { + if m != nil { + return m.Time + } + return time.Time{} +} + +func (m *RequestPrepareProposal) GetNextValidatorsHash() []byte { + if m != nil { + return m.NextValidatorsHash + } + return nil +} + +func (m *RequestPrepareProposal) GetProposerAddress() []byte { + if m != nil { + return m.ProposerAddress } return nil } type RequestProcessProposal struct { - Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` - Header types1.Header `protobuf:"bytes,2,opt,name=header,proto3" json:"header"` - Txs [][]byte `protobuf:"bytes,3,rep,name=txs,proto3" json:"txs,omitempty"` - ProposedLastCommit CommitInfo `protobuf:"bytes,4,opt,name=proposed_last_commit,json=proposedLastCommit,proto3" json:"proposed_last_commit"` - ByzantineValidators []Evidence `protobuf:"bytes,5,rep,name=byzantine_validators,json=byzantineValidators,proto3" json:"byzantine_validators"` + Txs [][]byte `protobuf:"bytes,1,rep,name=txs,proto3" json:"txs,omitempty"` + ProposedLastCommit CommitInfo `protobuf:"bytes,2,opt,name=proposed_last_commit,json=proposedLastCommit,proto3" json:"proposed_last_commit"` + ByzantineValidators []Misbehavior `protobuf:"bytes,3,rep,name=byzantine_validators,json=byzantineValidators,proto3" json:"byzantine_validators"` + // hash is the merkle root hash of the fields of the proposed block. + Hash []byte `protobuf:"bytes,4,opt,name=hash,proto3" json:"hash,omitempty"` + Height int64 `protobuf:"varint,5,opt,name=height,proto3" json:"height,omitempty"` + Time time.Time `protobuf:"bytes,6,opt,name=time,proto3,stdtime" json:"time"` + NextValidatorsHash []byte `protobuf:"bytes,7,opt,name=next_validators_hash,json=nextValidatorsHash,proto3" json:"next_validators_hash,omitempty"` + // address of the public key of the original proposer of the block. + ProposerAddress []byte `protobuf:"bytes,8,opt,name=proposer_address,json=proposerAddress,proto3" json:"proposer_address,omitempty"` } func (m *RequestProcessProposal) Reset() { *m = RequestProcessProposal{} } func (m *RequestProcessProposal) String() string { return proto.CompactTextString(m) } func (*RequestProcessProposal) ProtoMessage() {} func (*RequestProcessProposal) Descriptor() ([]byte, []int) { - return fileDescriptor_252557cfdd89a31a, []int{18} + return fileDescriptor_252557cfdd89a31a, []int{16} } func (m *RequestProcessProposal) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1453,20 +1463,6 @@ func (m *RequestProcessProposal) XXX_DiscardUnknown() { var xxx_messageInfo_RequestProcessProposal proto.InternalMessageInfo -func (m *RequestProcessProposal) GetHash() []byte { - if m != nil { - return m.Hash - } - return nil -} - -func (m *RequestProcessProposal) GetHeader() types1.Header { - if m != nil { - return m.Header - } - return types1.Header{} -} - func (m *RequestProcessProposal) GetTxs() [][]byte { if m != nil { return m.Txs @@ -1481,19 +1477,181 @@ func (m *RequestProcessProposal) GetProposedLastCommit() CommitInfo { return CommitInfo{} } -func (m *RequestProcessProposal) GetByzantineValidators() []Evidence { +func (m *RequestProcessProposal) GetByzantineValidators() []Misbehavior { if m != nil { return m.ByzantineValidators } return nil } +func (m *RequestProcessProposal) GetHash() []byte { + if m != nil { + return m.Hash + } + return nil +} + +func (m *RequestProcessProposal) GetHeight() int64 { + if m != nil { + return m.Height + } + return 0 +} + +func (m *RequestProcessProposal) GetTime() time.Time { + if m != nil { + return m.Time + } + return time.Time{} +} + +func (m *RequestProcessProposal) GetNextValidatorsHash() []byte { + if m != nil { + return m.NextValidatorsHash + } + return nil +} + +func (m *RequestProcessProposal) GetProposerAddress() []byte { + if m != nil { + return m.ProposerAddress + } + return nil +} + +// Extends a vote with application-side injection +type RequestExtendVote struct { + Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` + Height int64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` +} + +func (m *RequestExtendVote) Reset() { *m = RequestExtendVote{} } +func (m *RequestExtendVote) String() string { return proto.CompactTextString(m) } +func (*RequestExtendVote) ProtoMessage() {} +func (*RequestExtendVote) Descriptor() ([]byte, []int) { + return fileDescriptor_252557cfdd89a31a, []int{17} +} +func (m *RequestExtendVote) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RequestExtendVote) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RequestExtendVote.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *RequestExtendVote) XXX_Merge(src proto.Message) { + xxx_messageInfo_RequestExtendVote.Merge(m, src) +} +func (m *RequestExtendVote) XXX_Size() int { + return m.Size() +} +func (m *RequestExtendVote) XXX_DiscardUnknown() { + xxx_messageInfo_RequestExtendVote.DiscardUnknown(m) +} + +var xxx_messageInfo_RequestExtendVote proto.InternalMessageInfo + +func (m *RequestExtendVote) GetHash() []byte { + if m != nil { + return m.Hash + } + return nil +} + +func (m *RequestExtendVote) GetHeight() int64 { + if m != nil { + return m.Height + } + return 0 +} + +// Verify the vote extension +type RequestVerifyVoteExtension struct { + Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` + ValidatorAddress []byte `protobuf:"bytes,2,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty"` + Height int64 `protobuf:"varint,3,opt,name=height,proto3" json:"height,omitempty"` + VoteExtension []byte `protobuf:"bytes,4,opt,name=vote_extension,json=voteExtension,proto3" json:"vote_extension,omitempty"` +} + +func (m *RequestVerifyVoteExtension) Reset() { *m = RequestVerifyVoteExtension{} } +func (m *RequestVerifyVoteExtension) String() string { return proto.CompactTextString(m) } +func (*RequestVerifyVoteExtension) ProtoMessage() {} +func (*RequestVerifyVoteExtension) Descriptor() ([]byte, []int) { + return fileDescriptor_252557cfdd89a31a, []int{18} +} +func (m *RequestVerifyVoteExtension) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RequestVerifyVoteExtension) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RequestVerifyVoteExtension.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *RequestVerifyVoteExtension) XXX_Merge(src proto.Message) { + xxx_messageInfo_RequestVerifyVoteExtension.Merge(m, src) +} +func (m *RequestVerifyVoteExtension) XXX_Size() int { + return m.Size() +} +func (m *RequestVerifyVoteExtension) XXX_DiscardUnknown() { + xxx_messageInfo_RequestVerifyVoteExtension.DiscardUnknown(m) +} + +var xxx_messageInfo_RequestVerifyVoteExtension proto.InternalMessageInfo + +func (m *RequestVerifyVoteExtension) GetHash() []byte { + if m != nil { + return m.Hash + } + return nil +} + +func (m *RequestVerifyVoteExtension) GetValidatorAddress() []byte { + if m != nil { + return m.ValidatorAddress + } + return nil +} + +func (m *RequestVerifyVoteExtension) GetHeight() int64 { + if m != nil { + return m.Height + } + return 0 +} + +func (m *RequestVerifyVoteExtension) GetVoteExtension() []byte { + if m != nil { + return m.VoteExtension + } + return nil +} + type RequestFinalizeBlock struct { - Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` - Header types1.Header `protobuf:"bytes,2,opt,name=header,proto3" json:"header"` - Txs [][]byte `protobuf:"bytes,3,rep,name=txs,proto3" json:"txs,omitempty"` - DecidedLastCommit CommitInfo `protobuf:"bytes,4,opt,name=decided_last_commit,json=decidedLastCommit,proto3" json:"decided_last_commit"` - ByzantineValidators []Evidence `protobuf:"bytes,5,rep,name=byzantine_validators,json=byzantineValidators,proto3" json:"byzantine_validators"` + Txs [][]byte `protobuf:"bytes,1,rep,name=txs,proto3" json:"txs,omitempty"` + DecidedLastCommit CommitInfo `protobuf:"bytes,2,opt,name=decided_last_commit,json=decidedLastCommit,proto3" json:"decided_last_commit"` + ByzantineValidators []Misbehavior `protobuf:"bytes,3,rep,name=byzantine_validators,json=byzantineValidators,proto3" json:"byzantine_validators"` + // hash is the merkle root hash of the fields of the proposed block. + Hash []byte `protobuf:"bytes,4,opt,name=hash,proto3" json:"hash,omitempty"` + Height int64 `protobuf:"varint,5,opt,name=height,proto3" json:"height,omitempty"` + Time time.Time `protobuf:"bytes,6,opt,name=time,proto3,stdtime" json:"time"` + NextValidatorsHash []byte `protobuf:"bytes,7,opt,name=next_validators_hash,json=nextValidatorsHash,proto3" json:"next_validators_hash,omitempty"` + // proposer_address is the address of the public key of the original proposer of the block. + ProposerAddress []byte `protobuf:"bytes,8,opt,name=proposer_address,json=proposerAddress,proto3" json:"proposer_address,omitempty"` } func (m *RequestFinalizeBlock) Reset() { *m = RequestFinalizeBlock{} } @@ -1529,20 +1687,6 @@ func (m *RequestFinalizeBlock) XXX_DiscardUnknown() { var xxx_messageInfo_RequestFinalizeBlock proto.InternalMessageInfo -func (m *RequestFinalizeBlock) GetHash() []byte { - if m != nil { - return m.Hash - } - return nil -} - -func (m *RequestFinalizeBlock) GetHeader() types1.Header { - if m != nil { - return m.Header - } - return types1.Header{} -} - func (m *RequestFinalizeBlock) GetTxs() [][]byte { if m != nil { return m.Txs @@ -1557,13 +1701,48 @@ func (m *RequestFinalizeBlock) GetDecidedLastCommit() CommitInfo { return CommitInfo{} } -func (m *RequestFinalizeBlock) GetByzantineValidators() []Evidence { +func (m *RequestFinalizeBlock) GetByzantineValidators() []Misbehavior { if m != nil { return m.ByzantineValidators } return nil } +func (m *RequestFinalizeBlock) GetHash() []byte { + if m != nil { + return m.Hash + } + return nil +} + +func (m *RequestFinalizeBlock) GetHeight() int64 { + if m != nil { + return m.Height + } + return 0 +} + +func (m *RequestFinalizeBlock) GetTime() time.Time { + if m != nil { + return m.Time + } + return time.Time{} +} + +func (m *RequestFinalizeBlock) GetNextValidatorsHash() []byte { + if m != nil { + return m.NextValidatorsHash + } + return nil +} + +func (m *RequestFinalizeBlock) GetProposerAddress() []byte { + if m != nil { + return m.ProposerAddress + } + return nil +} + type Response struct { // Types that are valid to be assigned to Value: // *Response_Exception @@ -2831,103 +3010,19 @@ func (m *ResponseApplySnapshotChunk) GetRejectSenders() []string { return nil } -type ResponseExtendVote struct { - VoteExtension *types1.VoteExtension `protobuf:"bytes,1,opt,name=vote_extension,json=voteExtension,proto3" json:"vote_extension,omitempty"` -} - -func (m *ResponseExtendVote) Reset() { *m = ResponseExtendVote{} } -func (m *ResponseExtendVote) String() string { return proto.CompactTextString(m) } -func (*ResponseExtendVote) ProtoMessage() {} -func (*ResponseExtendVote) Descriptor() ([]byte, []int) { - return fileDescriptor_252557cfdd89a31a, []int{36} -} -func (m *ResponseExtendVote) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *ResponseExtendVote) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_ResponseExtendVote.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *ResponseExtendVote) XXX_Merge(src proto.Message) { - xxx_messageInfo_ResponseExtendVote.Merge(m, src) -} -func (m *ResponseExtendVote) XXX_Size() int { - return m.Size() -} -func (m *ResponseExtendVote) XXX_DiscardUnknown() { - xxx_messageInfo_ResponseExtendVote.DiscardUnknown(m) -} - -var xxx_messageInfo_ResponseExtendVote proto.InternalMessageInfo - -func (m *ResponseExtendVote) GetVoteExtension() *types1.VoteExtension { - if m != nil { - return m.VoteExtension - } - return nil -} - -type ResponseVerifyVoteExtension struct { - Result ResponseVerifyVoteExtension_Result `protobuf:"varint,1,opt,name=result,proto3,enum=tendermint.abci.ResponseVerifyVoteExtension_Result" json:"result,omitempty"` -} - -func (m *ResponseVerifyVoteExtension) Reset() { *m = ResponseVerifyVoteExtension{} } -func (m *ResponseVerifyVoteExtension) String() string { return proto.CompactTextString(m) } -func (*ResponseVerifyVoteExtension) ProtoMessage() {} -func (*ResponseVerifyVoteExtension) Descriptor() ([]byte, []int) { - return fileDescriptor_252557cfdd89a31a, []int{37} -} -func (m *ResponseVerifyVoteExtension) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *ResponseVerifyVoteExtension) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_ResponseVerifyVoteExtension.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *ResponseVerifyVoteExtension) XXX_Merge(src proto.Message) { - xxx_messageInfo_ResponseVerifyVoteExtension.Merge(m, src) -} -func (m *ResponseVerifyVoteExtension) XXX_Size() int { - return m.Size() -} -func (m *ResponseVerifyVoteExtension) XXX_DiscardUnknown() { - xxx_messageInfo_ResponseVerifyVoteExtension.DiscardUnknown(m) -} - -var xxx_messageInfo_ResponseVerifyVoteExtension proto.InternalMessageInfo - -func (m *ResponseVerifyVoteExtension) GetResult() ResponseVerifyVoteExtension_Result { - if m != nil { - return m.Result - } - return ResponseVerifyVoteExtension_UNKNOWN -} - type ResponsePrepareProposal struct { - BlockData [][]byte `protobuf:"bytes,1,rep,name=block_data,json=blockData,proto3" json:"block_data,omitempty"` + TxRecords []*TxRecord `protobuf:"bytes,1,rep,name=tx_records,json=txRecords,proto3" json:"tx_records,omitempty"` + AppHash []byte `protobuf:"bytes,2,opt,name=app_hash,json=appHash,proto3" json:"app_hash,omitempty"` + TxResults []*ExecTxResult `protobuf:"bytes,3,rep,name=tx_results,json=txResults,proto3" json:"tx_results,omitempty"` + ValidatorUpdates []*ValidatorUpdate `protobuf:"bytes,4,rep,name=validator_updates,json=validatorUpdates,proto3" json:"validator_updates,omitempty"` + ConsensusParamUpdates *types1.ConsensusParams `protobuf:"bytes,5,opt,name=consensus_param_updates,json=consensusParamUpdates,proto3" json:"consensus_param_updates,omitempty"` } func (m *ResponsePrepareProposal) Reset() { *m = ResponsePrepareProposal{} } func (m *ResponsePrepareProposal) String() string { return proto.CompactTextString(m) } func (*ResponsePrepareProposal) ProtoMessage() {} func (*ResponsePrepareProposal) Descriptor() ([]byte, []int) { - return fileDescriptor_252557cfdd89a31a, []int{38} + return fileDescriptor_252557cfdd89a31a, []int{36} } func (m *ResponsePrepareProposal) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2956,26 +3051,54 @@ func (m *ResponsePrepareProposal) XXX_DiscardUnknown() { var xxx_messageInfo_ResponsePrepareProposal proto.InternalMessageInfo -func (m *ResponsePrepareProposal) GetBlockData() [][]byte { +func (m *ResponsePrepareProposal) GetTxRecords() []*TxRecord { if m != nil { - return m.BlockData + return m.TxRecords + } + return nil +} + +func (m *ResponsePrepareProposal) GetAppHash() []byte { + if m != nil { + return m.AppHash + } + return nil +} + +func (m *ResponsePrepareProposal) GetTxResults() []*ExecTxResult { + if m != nil { + return m.TxResults + } + return nil +} + +func (m *ResponsePrepareProposal) GetValidatorUpdates() []*ValidatorUpdate { + if m != nil { + return m.ValidatorUpdates + } + return nil +} + +func (m *ResponsePrepareProposal) GetConsensusParamUpdates() *types1.ConsensusParams { + if m != nil { + return m.ConsensusParamUpdates } return nil } type ResponseProcessProposal struct { - Accept bool `protobuf:"varint,1,opt,name=accept,proto3" json:"accept,omitempty"` - AppHash []byte `protobuf:"bytes,2,opt,name=app_hash,json=appHash,proto3" json:"app_hash,omitempty"` - TxResults []*ExecTxResult `protobuf:"bytes,3,rep,name=tx_results,json=txResults,proto3" json:"tx_results,omitempty"` - ValidatorUpdates []*ValidatorUpdate `protobuf:"bytes,4,rep,name=validator_updates,json=validatorUpdates,proto3" json:"validator_updates,omitempty"` - ConsensusParamUpdates *types1.ConsensusParams `protobuf:"bytes,5,opt,name=consensus_param_updates,json=consensusParamUpdates,proto3" json:"consensus_param_updates,omitempty"` + Status ResponseProcessProposal_ProposalStatus `protobuf:"varint,1,opt,name=status,proto3,enum=tendermint.abci.ResponseProcessProposal_ProposalStatus" json:"status,omitempty"` + AppHash []byte `protobuf:"bytes,2,opt,name=app_hash,json=appHash,proto3" json:"app_hash,omitempty"` + TxResults []*ExecTxResult `protobuf:"bytes,3,rep,name=tx_results,json=txResults,proto3" json:"tx_results,omitempty"` + ValidatorUpdates []*ValidatorUpdate `protobuf:"bytes,4,rep,name=validator_updates,json=validatorUpdates,proto3" json:"validator_updates,omitempty"` + ConsensusParamUpdates *types1.ConsensusParams `protobuf:"bytes,5,opt,name=consensus_param_updates,json=consensusParamUpdates,proto3" json:"consensus_param_updates,omitempty"` } func (m *ResponseProcessProposal) Reset() { *m = ResponseProcessProposal{} } func (m *ResponseProcessProposal) String() string { return proto.CompactTextString(m) } func (*ResponseProcessProposal) ProtoMessage() {} func (*ResponseProcessProposal) Descriptor() ([]byte, []int) { - return fileDescriptor_252557cfdd89a31a, []int{39} + return fileDescriptor_252557cfdd89a31a, []int{37} } func (m *ResponseProcessProposal) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3004,11 +3127,11 @@ func (m *ResponseProcessProposal) XXX_DiscardUnknown() { var xxx_messageInfo_ResponseProcessProposal proto.InternalMessageInfo -func (m *ResponseProcessProposal) GetAccept() bool { +func (m *ResponseProcessProposal) GetStatus() ResponseProcessProposal_ProposalStatus { if m != nil { - return m.Accept + return m.Status } - return false + return ResponseProcessProposal_UNKNOWN } func (m *ResponseProcessProposal) GetAppHash() []byte { @@ -3039,6 +3162,94 @@ func (m *ResponseProcessProposal) GetConsensusParamUpdates() *types1.ConsensusPa return nil } +type ResponseExtendVote struct { + VoteExtension []byte `protobuf:"bytes,1,opt,name=vote_extension,json=voteExtension,proto3" json:"vote_extension,omitempty"` +} + +func (m *ResponseExtendVote) Reset() { *m = ResponseExtendVote{} } +func (m *ResponseExtendVote) String() string { return proto.CompactTextString(m) } +func (*ResponseExtendVote) ProtoMessage() {} +func (*ResponseExtendVote) Descriptor() ([]byte, []int) { + return fileDescriptor_252557cfdd89a31a, []int{38} +} +func (m *ResponseExtendVote) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ResponseExtendVote) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ResponseExtendVote.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ResponseExtendVote) XXX_Merge(src proto.Message) { + xxx_messageInfo_ResponseExtendVote.Merge(m, src) +} +func (m *ResponseExtendVote) XXX_Size() int { + return m.Size() +} +func (m *ResponseExtendVote) XXX_DiscardUnknown() { + xxx_messageInfo_ResponseExtendVote.DiscardUnknown(m) +} + +var xxx_messageInfo_ResponseExtendVote proto.InternalMessageInfo + +func (m *ResponseExtendVote) GetVoteExtension() []byte { + if m != nil { + return m.VoteExtension + } + return nil +} + +type ResponseVerifyVoteExtension struct { + Status ResponseVerifyVoteExtension_VerifyStatus `protobuf:"varint,1,opt,name=status,proto3,enum=tendermint.abci.ResponseVerifyVoteExtension_VerifyStatus" json:"status,omitempty"` +} + +func (m *ResponseVerifyVoteExtension) Reset() { *m = ResponseVerifyVoteExtension{} } +func (m *ResponseVerifyVoteExtension) String() string { return proto.CompactTextString(m) } +func (*ResponseVerifyVoteExtension) ProtoMessage() {} +func (*ResponseVerifyVoteExtension) Descriptor() ([]byte, []int) { + return fileDescriptor_252557cfdd89a31a, []int{39} +} +func (m *ResponseVerifyVoteExtension) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ResponseVerifyVoteExtension) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ResponseVerifyVoteExtension.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ResponseVerifyVoteExtension) XXX_Merge(src proto.Message) { + xxx_messageInfo_ResponseVerifyVoteExtension.Merge(m, src) +} +func (m *ResponseVerifyVoteExtension) XXX_Size() int { + return m.Size() +} +func (m *ResponseVerifyVoteExtension) XXX_DiscardUnknown() { + xxx_messageInfo_ResponseVerifyVoteExtension.DiscardUnknown(m) +} + +var xxx_messageInfo_ResponseVerifyVoteExtension proto.InternalMessageInfo + +func (m *ResponseVerifyVoteExtension) GetStatus() ResponseVerifyVoteExtension_VerifyStatus { + if m != nil { + return m.Status + } + return ResponseVerifyVoteExtension_UNKNOWN +} + type ResponseFinalizeBlock struct { Events []Event `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"` TxResults []*ExecTxResult `protobuf:"bytes,2,rep,name=tx_results,json=txResults,proto3" json:"tx_results,omitempty"` @@ -3175,6 +3386,64 @@ func (m *CommitInfo) GetVotes() []VoteInfo { return nil } +// ExtendedCommitInfo is similar to CommitInfo except that it is only used in +// the PrepareProposal request such that Tendermint can provide vote extensions +// to the application. +type ExtendedCommitInfo struct { + // The round at which the block proposer decided in the previous height. + Round int32 `protobuf:"varint,1,opt,name=round,proto3" json:"round,omitempty"` + // List of validators' addresses in the last validator set with their voting + // information, including vote extensions. + Votes []ExtendedVoteInfo `protobuf:"bytes,2,rep,name=votes,proto3" json:"votes"` +} + +func (m *ExtendedCommitInfo) Reset() { *m = ExtendedCommitInfo{} } +func (m *ExtendedCommitInfo) String() string { return proto.CompactTextString(m) } +func (*ExtendedCommitInfo) ProtoMessage() {} +func (*ExtendedCommitInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_252557cfdd89a31a, []int{42} +} +func (m *ExtendedCommitInfo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ExtendedCommitInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ExtendedCommitInfo.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ExtendedCommitInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_ExtendedCommitInfo.Merge(m, src) +} +func (m *ExtendedCommitInfo) XXX_Size() int { + return m.Size() +} +func (m *ExtendedCommitInfo) XXX_DiscardUnknown() { + xxx_messageInfo_ExtendedCommitInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_ExtendedCommitInfo proto.InternalMessageInfo + +func (m *ExtendedCommitInfo) GetRound() int32 { + if m != nil { + return m.Round + } + return 0 +} + +func (m *ExtendedCommitInfo) GetVotes() []ExtendedVoteInfo { + if m != nil { + return m.Votes + } + return nil +} + // Event allows application developers to attach additional information to // ResponseBeginBlock, ResponseEndBlock, ResponseCheckTx and ResponseDeliverTx. // Later, transactions may be queried using these events. @@ -3187,7 +3456,7 @@ func (m *Event) Reset() { *m = Event{} } func (m *Event) String() string { return proto.CompactTextString(m) } func (*Event) ProtoMessage() {} func (*Event) Descriptor() ([]byte, []int) { - return fileDescriptor_252557cfdd89a31a, []int{42} + return fileDescriptor_252557cfdd89a31a, []int{43} } func (m *Event) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3241,7 +3510,7 @@ func (m *EventAttribute) Reset() { *m = EventAttribute{} } func (m *EventAttribute) String() string { return proto.CompactTextString(m) } func (*EventAttribute) ProtoMessage() {} func (*EventAttribute) Descriptor() ([]byte, []int) { - return fileDescriptor_252557cfdd89a31a, []int{43} + return fileDescriptor_252557cfdd89a31a, []int{44} } func (m *EventAttribute) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3309,7 +3578,7 @@ func (m *ExecTxResult) Reset() { *m = ExecTxResult{} } func (m *ExecTxResult) String() string { return proto.CompactTextString(m) } func (*ExecTxResult) ProtoMessage() {} func (*ExecTxResult) Descriptor() ([]byte, []int) { - return fileDescriptor_252557cfdd89a31a, []int{44} + return fileDescriptor_252557cfdd89a31a, []int{45} } func (m *ExecTxResult) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3408,7 +3677,7 @@ func (m *TxResult) Reset() { *m = TxResult{} } func (m *TxResult) String() string { return proto.CompactTextString(m) } func (*TxResult) ProtoMessage() {} func (*TxResult) Descriptor() ([]byte, []int) { - return fileDescriptor_252557cfdd89a31a, []int{45} + return fileDescriptor_252557cfdd89a31a, []int{46} } func (m *TxResult) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3465,6 +3734,58 @@ func (m *TxResult) GetResult() ExecTxResult { return ExecTxResult{} } +type TxRecord struct { + Action TxRecord_TxAction `protobuf:"varint,1,opt,name=action,proto3,enum=tendermint.abci.TxRecord_TxAction" json:"action,omitempty"` + Tx []byte `protobuf:"bytes,2,opt,name=tx,proto3" json:"tx,omitempty"` +} + +func (m *TxRecord) Reset() { *m = TxRecord{} } +func (m *TxRecord) String() string { return proto.CompactTextString(m) } +func (*TxRecord) ProtoMessage() {} +func (*TxRecord) Descriptor() ([]byte, []int) { + return fileDescriptor_252557cfdd89a31a, []int{47} +} +func (m *TxRecord) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TxRecord) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TxRecord.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TxRecord) XXX_Merge(src proto.Message) { + xxx_messageInfo_TxRecord.Merge(m, src) +} +func (m *TxRecord) XXX_Size() int { + return m.Size() +} +func (m *TxRecord) XXX_DiscardUnknown() { + xxx_messageInfo_TxRecord.DiscardUnknown(m) +} + +var xxx_messageInfo_TxRecord proto.InternalMessageInfo + +func (m *TxRecord) GetAction() TxRecord_TxAction { + if m != nil { + return m.Action + } + return TxRecord_UNKNOWN +} + +func (m *TxRecord) GetTx() []byte { + if m != nil { + return m.Tx + } + return nil +} + // Validator type Validator struct { Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` @@ -3476,7 +3797,7 @@ func (m *Validator) Reset() { *m = Validator{} } func (m *Validator) String() string { return proto.CompactTextString(m) } func (*Validator) ProtoMessage() {} func (*Validator) Descriptor() ([]byte, []int) { - return fileDescriptor_252557cfdd89a31a, []int{46} + return fileDescriptor_252557cfdd89a31a, []int{48} } func (m *Validator) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3529,7 +3850,7 @@ func (m *ValidatorUpdate) Reset() { *m = ValidatorUpdate{} } func (m *ValidatorUpdate) String() string { return proto.CompactTextString(m) } func (*ValidatorUpdate) ProtoMessage() {} func (*ValidatorUpdate) Descriptor() ([]byte, []int) { - return fileDescriptor_252557cfdd89a31a, []int{47} + return fileDescriptor_252557cfdd89a31a, []int{49} } func (m *ValidatorUpdate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3582,7 +3903,7 @@ func (m *VoteInfo) Reset() { *m = VoteInfo{} } func (m *VoteInfo) String() string { return proto.CompactTextString(m) } func (*VoteInfo) ProtoMessage() {} func (*VoteInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_252557cfdd89a31a, []int{48} + return fileDescriptor_252557cfdd89a31a, []int{50} } func (m *VoteInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3625,8 +3946,72 @@ func (m *VoteInfo) GetSignedLastBlock() bool { return false } -type Evidence struct { - Type EvidenceType `protobuf:"varint,1,opt,name=type,proto3,enum=tendermint.abci.EvidenceType" json:"type,omitempty"` +// ExtendedVoteInfo +type ExtendedVoteInfo struct { + // The validator that sent the vote. + Validator Validator `protobuf:"bytes,1,opt,name=validator,proto3" json:"validator"` + // Indicates whether the validator signed the last block, allowing for rewards based on validator availability. + SignedLastBlock bool `protobuf:"varint,2,opt,name=signed_last_block,json=signedLastBlock,proto3" json:"signed_last_block,omitempty"` + // Non-deterministic extension provided by the sending validator's application. + VoteExtension []byte `protobuf:"bytes,3,opt,name=vote_extension,json=voteExtension,proto3" json:"vote_extension,omitempty"` +} + +func (m *ExtendedVoteInfo) Reset() { *m = ExtendedVoteInfo{} } +func (m *ExtendedVoteInfo) String() string { return proto.CompactTextString(m) } +func (*ExtendedVoteInfo) ProtoMessage() {} +func (*ExtendedVoteInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_252557cfdd89a31a, []int{51} +} +func (m *ExtendedVoteInfo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ExtendedVoteInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ExtendedVoteInfo.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ExtendedVoteInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_ExtendedVoteInfo.Merge(m, src) +} +func (m *ExtendedVoteInfo) XXX_Size() int { + return m.Size() +} +func (m *ExtendedVoteInfo) XXX_DiscardUnknown() { + xxx_messageInfo_ExtendedVoteInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_ExtendedVoteInfo proto.InternalMessageInfo + +func (m *ExtendedVoteInfo) GetValidator() Validator { + if m != nil { + return m.Validator + } + return Validator{} +} + +func (m *ExtendedVoteInfo) GetSignedLastBlock() bool { + if m != nil { + return m.SignedLastBlock + } + return false +} + +func (m *ExtendedVoteInfo) GetVoteExtension() []byte { + if m != nil { + return m.VoteExtension + } + return nil +} + +type Misbehavior struct { + Type MisbehaviorType `protobuf:"varint,1,opt,name=type,proto3,enum=tendermint.abci.MisbehaviorType" json:"type,omitempty"` // The offending validator Validator Validator `protobuf:"bytes,2,opt,name=validator,proto3" json:"validator"` // The height when the offense occurred @@ -3639,18 +4024,18 @@ type Evidence struct { TotalVotingPower int64 `protobuf:"varint,5,opt,name=total_voting_power,json=totalVotingPower,proto3" json:"total_voting_power,omitempty"` } -func (m *Evidence) Reset() { *m = Evidence{} } -func (m *Evidence) String() string { return proto.CompactTextString(m) } -func (*Evidence) ProtoMessage() {} -func (*Evidence) Descriptor() ([]byte, []int) { - return fileDescriptor_252557cfdd89a31a, []int{49} +func (m *Misbehavior) Reset() { *m = Misbehavior{} } +func (m *Misbehavior) String() string { return proto.CompactTextString(m) } +func (*Misbehavior) ProtoMessage() {} +func (*Misbehavior) Descriptor() ([]byte, []int) { + return fileDescriptor_252557cfdd89a31a, []int{52} } -func (m *Evidence) XXX_Unmarshal(b []byte) error { +func (m *Misbehavior) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *Evidence) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *Misbehavior) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_Evidence.Marshal(b, m, deterministic) + return xxx_messageInfo_Misbehavior.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -3660,47 +4045,47 @@ func (m *Evidence) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return b[:n], nil } } -func (m *Evidence) XXX_Merge(src proto.Message) { - xxx_messageInfo_Evidence.Merge(m, src) +func (m *Misbehavior) XXX_Merge(src proto.Message) { + xxx_messageInfo_Misbehavior.Merge(m, src) } -func (m *Evidence) XXX_Size() int { +func (m *Misbehavior) XXX_Size() int { return m.Size() } -func (m *Evidence) XXX_DiscardUnknown() { - xxx_messageInfo_Evidence.DiscardUnknown(m) +func (m *Misbehavior) XXX_DiscardUnknown() { + xxx_messageInfo_Misbehavior.DiscardUnknown(m) } -var xxx_messageInfo_Evidence proto.InternalMessageInfo +var xxx_messageInfo_Misbehavior proto.InternalMessageInfo -func (m *Evidence) GetType() EvidenceType { +func (m *Misbehavior) GetType() MisbehaviorType { if m != nil { return m.Type } - return EvidenceType_UNKNOWN + return MisbehaviorType_UNKNOWN } -func (m *Evidence) GetValidator() Validator { +func (m *Misbehavior) GetValidator() Validator { if m != nil { return m.Validator } return Validator{} } -func (m *Evidence) GetHeight() int64 { +func (m *Misbehavior) GetHeight() int64 { if m != nil { return m.Height } return 0 } -func (m *Evidence) GetTime() time.Time { +func (m *Misbehavior) GetTime() time.Time { if m != nil { return m.Time } return time.Time{} } -func (m *Evidence) GetTotalVotingPower() int64 { +func (m *Misbehavior) GetTotalVotingPower() int64 { if m != nil { return m.TotalVotingPower } @@ -3719,7 +4104,7 @@ func (m *Snapshot) Reset() { *m = Snapshot{} } func (m *Snapshot) String() string { return proto.CompactTextString(m) } func (*Snapshot) ProtoMessage() {} func (*Snapshot) Descriptor() ([]byte, []int) { - return fileDescriptor_252557cfdd89a31a, []int{50} + return fileDescriptor_252557cfdd89a31a, []int{53} } func (m *Snapshot) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3785,10 +4170,12 @@ func (m *Snapshot) GetMetadata() []byte { func init() { proto.RegisterEnum("tendermint.abci.CheckTxType", CheckTxType_name, CheckTxType_value) - proto.RegisterEnum("tendermint.abci.EvidenceType", EvidenceType_name, EvidenceType_value) + proto.RegisterEnum("tendermint.abci.MisbehaviorType", MisbehaviorType_name, MisbehaviorType_value) proto.RegisterEnum("tendermint.abci.ResponseOfferSnapshot_Result", ResponseOfferSnapshot_Result_name, ResponseOfferSnapshot_Result_value) proto.RegisterEnum("tendermint.abci.ResponseApplySnapshotChunk_Result", ResponseApplySnapshotChunk_Result_name, ResponseApplySnapshotChunk_Result_value) - proto.RegisterEnum("tendermint.abci.ResponseVerifyVoteExtension_Result", ResponseVerifyVoteExtension_Result_name, ResponseVerifyVoteExtension_Result_value) + proto.RegisterEnum("tendermint.abci.ResponseProcessProposal_ProposalStatus", ResponseProcessProposal_ProposalStatus_name, ResponseProcessProposal_ProposalStatus_value) + proto.RegisterEnum("tendermint.abci.ResponseVerifyVoteExtension_VerifyStatus", ResponseVerifyVoteExtension_VerifyStatus_name, ResponseVerifyVoteExtension_VerifyStatus_value) + proto.RegisterEnum("tendermint.abci.TxRecord_TxAction", TxRecord_TxAction_name, TxRecord_TxAction_value) proto.RegisterType((*Request)(nil), "tendermint.abci.Request") proto.RegisterType((*RequestEcho)(nil), "tendermint.abci.RequestEcho") proto.RegisterType((*RequestFlush)(nil), "tendermint.abci.RequestFlush") @@ -3804,10 +4191,10 @@ func init() { proto.RegisterType((*RequestOfferSnapshot)(nil), "tendermint.abci.RequestOfferSnapshot") proto.RegisterType((*RequestLoadSnapshotChunk)(nil), "tendermint.abci.RequestLoadSnapshotChunk") proto.RegisterType((*RequestApplySnapshotChunk)(nil), "tendermint.abci.RequestApplySnapshotChunk") - proto.RegisterType((*RequestExtendVote)(nil), "tendermint.abci.RequestExtendVote") - proto.RegisterType((*RequestVerifyVoteExtension)(nil), "tendermint.abci.RequestVerifyVoteExtension") proto.RegisterType((*RequestPrepareProposal)(nil), "tendermint.abci.RequestPrepareProposal") proto.RegisterType((*RequestProcessProposal)(nil), "tendermint.abci.RequestProcessProposal") + proto.RegisterType((*RequestExtendVote)(nil), "tendermint.abci.RequestExtendVote") + proto.RegisterType((*RequestVerifyVoteExtension)(nil), "tendermint.abci.RequestVerifyVoteExtension") proto.RegisterType((*RequestFinalizeBlock)(nil), "tendermint.abci.RequestFinalizeBlock") proto.RegisterType((*Response)(nil), "tendermint.abci.Response") proto.RegisterType((*ResponseException)(nil), "tendermint.abci.ResponseException") @@ -3825,229 +4212,246 @@ func init() { proto.RegisterType((*ResponseOfferSnapshot)(nil), "tendermint.abci.ResponseOfferSnapshot") proto.RegisterType((*ResponseLoadSnapshotChunk)(nil), "tendermint.abci.ResponseLoadSnapshotChunk") proto.RegisterType((*ResponseApplySnapshotChunk)(nil), "tendermint.abci.ResponseApplySnapshotChunk") - proto.RegisterType((*ResponseExtendVote)(nil), "tendermint.abci.ResponseExtendVote") - proto.RegisterType((*ResponseVerifyVoteExtension)(nil), "tendermint.abci.ResponseVerifyVoteExtension") proto.RegisterType((*ResponsePrepareProposal)(nil), "tendermint.abci.ResponsePrepareProposal") proto.RegisterType((*ResponseProcessProposal)(nil), "tendermint.abci.ResponseProcessProposal") + proto.RegisterType((*ResponseExtendVote)(nil), "tendermint.abci.ResponseExtendVote") + proto.RegisterType((*ResponseVerifyVoteExtension)(nil), "tendermint.abci.ResponseVerifyVoteExtension") proto.RegisterType((*ResponseFinalizeBlock)(nil), "tendermint.abci.ResponseFinalizeBlock") proto.RegisterType((*CommitInfo)(nil), "tendermint.abci.CommitInfo") + proto.RegisterType((*ExtendedCommitInfo)(nil), "tendermint.abci.ExtendedCommitInfo") proto.RegisterType((*Event)(nil), "tendermint.abci.Event") proto.RegisterType((*EventAttribute)(nil), "tendermint.abci.EventAttribute") proto.RegisterType((*ExecTxResult)(nil), "tendermint.abci.ExecTxResult") proto.RegisterType((*TxResult)(nil), "tendermint.abci.TxResult") + proto.RegisterType((*TxRecord)(nil), "tendermint.abci.TxRecord") proto.RegisterType((*Validator)(nil), "tendermint.abci.Validator") proto.RegisterType((*ValidatorUpdate)(nil), "tendermint.abci.ValidatorUpdate") proto.RegisterType((*VoteInfo)(nil), "tendermint.abci.VoteInfo") - proto.RegisterType((*Evidence)(nil), "tendermint.abci.Evidence") + proto.RegisterType((*ExtendedVoteInfo)(nil), "tendermint.abci.ExtendedVoteInfo") + proto.RegisterType((*Misbehavior)(nil), "tendermint.abci.Misbehavior") proto.RegisterType((*Snapshot)(nil), "tendermint.abci.Snapshot") } func init() { proto.RegisterFile("tendermint/abci/types.proto", fileDescriptor_252557cfdd89a31a) } var fileDescriptor_252557cfdd89a31a = []byte{ - // 3225 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x5a, 0xcd, 0x73, 0xdb, 0xd6, - 0x11, 0xe7, 0xb7, 0xc8, 0xe5, 0xa7, 0x9e, 0x14, 0x85, 0x66, 0x6c, 0xc9, 0x81, 0x27, 0x89, 0xe3, - 0x38, 0x52, 0x23, 0x4f, 0x52, 0x67, 0x92, 0xb6, 0x23, 0xd2, 0x54, 0x28, 0x4b, 0x91, 0x14, 0x88, - 0x72, 0x26, 0x6d, 0x6a, 0x04, 0x24, 0x9e, 0x48, 0xc4, 0x24, 0x80, 0x00, 0x20, 0x23, 0xf9, 0xd4, - 0xe9, 0x4c, 0x2e, 0x99, 0x76, 0x9a, 0x63, 0x67, 0x3a, 0x99, 0x5e, 0x7a, 0xe8, 0x1f, 0xd0, 0x43, - 0x4f, 0x3d, 0xf5, 0x90, 0x43, 0x0f, 0xb9, 0xb5, 0xd3, 0x43, 0xda, 0x49, 0x6e, 0xfd, 0x07, 0x7a, - 0xea, 0xc7, 0xbc, 0x0f, 0x7c, 0x91, 0x04, 0x3f, 0x62, 0xbb, 0x97, 0xde, 0xf0, 0x16, 0xbb, 0x8b, - 0xf7, 0x16, 0xef, 0xed, 0xee, 0x6f, 0xf7, 0xc1, 0x33, 0x36, 0xd6, 0x14, 0x6c, 0xf6, 0x55, 0xcd, - 0xde, 0x92, 0x5b, 0x6d, 0x75, 0xcb, 0xbe, 0x30, 0xb0, 0xb5, 0x69, 0x98, 0xba, 0xad, 0xa3, 0xa2, - 0xf7, 0x72, 0x93, 0xbc, 0xac, 0x5c, 0xf1, 0x71, 0xb7, 0xcd, 0x0b, 0xc3, 0xd6, 0xb7, 0x0c, 0x53, - 0xd7, 0xcf, 0x18, 0x7f, 0xe5, 0xb2, 0xef, 0x35, 0xd5, 0xe3, 0xd7, 0x16, 0x78, 0xcb, 0x85, 0x1f, - 0xe0, 0x0b, 0xe7, 0xed, 0x95, 0x31, 0x59, 0x43, 0x36, 0xe5, 0xbe, 0xf3, 0x7a, 0xa3, 0xa3, 0xeb, - 0x9d, 0x1e, 0xde, 0xa2, 0xa3, 0xd6, 0xe0, 0x6c, 0xcb, 0x56, 0xfb, 0xd8, 0xb2, 0xe5, 0xbe, 0xc1, - 0x19, 0x56, 0x3b, 0x7a, 0x47, 0xa7, 0x8f, 0x5b, 0xe4, 0x89, 0x51, 0x85, 0xff, 0x00, 0x2c, 0x89, - 0xf8, 0xa3, 0x01, 0xb6, 0x6c, 0xb4, 0x0d, 0x09, 0xdc, 0xee, 0xea, 0xe5, 0xe8, 0xd5, 0xe8, 0xf5, - 0xec, 0xf6, 0xe5, 0xcd, 0x91, 0xc5, 0x6d, 0x72, 0xbe, 0x7a, 0xbb, 0xab, 0x37, 0x22, 0x22, 0xe5, - 0x45, 0xaf, 0x42, 0xf2, 0xac, 0x37, 0xb0, 0xba, 0xe5, 0x18, 0x15, 0xba, 0x12, 0x26, 0xb4, 0x4b, - 0x98, 0x1a, 0x11, 0x91, 0x71, 0x93, 0x4f, 0xa9, 0xda, 0x99, 0x5e, 0x8e, 0x4f, 0xff, 0xd4, 0x9e, - 0x76, 0x46, 0x3f, 0x45, 0x78, 0x51, 0x15, 0x40, 0xd5, 0x54, 0x5b, 0x6a, 0x77, 0x65, 0x55, 0x2b, - 0x27, 0xa8, 0xe4, 0xb3, 0xe1, 0x92, 0xaa, 0x5d, 0x23, 0x8c, 0x8d, 0x88, 0x98, 0x51, 0x9d, 0x01, - 0x99, 0xee, 0x47, 0x03, 0x6c, 0x5e, 0x94, 0x93, 0xd3, 0xa7, 0xfb, 0x0e, 0x61, 0x22, 0xd3, 0xa5, - 0xdc, 0x68, 0x0f, 0xb2, 0x2d, 0xdc, 0x51, 0x35, 0xa9, 0xd5, 0xd3, 0xdb, 0x0f, 0xca, 0x29, 0x2a, - 0x2c, 0x84, 0x09, 0x57, 0x09, 0x6b, 0x95, 0x70, 0x56, 0x63, 0xe5, 0x68, 0x23, 0x22, 0x42, 0xcb, - 0xa5, 0xa0, 0x37, 0x21, 0xdd, 0xee, 0xe2, 0xf6, 0x03, 0xc9, 0x3e, 0x2f, 0x2f, 0x51, 0x3d, 0x1b, - 0x61, 0x7a, 0x6a, 0x84, 0xaf, 0x79, 0xde, 0x88, 0x88, 0x4b, 0x6d, 0xf6, 0x88, 0x76, 0x01, 0x14, - 0xdc, 0x53, 0x87, 0xd8, 0x24, 0xf2, 0xe9, 0xe9, 0x36, 0xb8, 0xc3, 0x38, 0x9b, 0xe7, 0x7c, 0x1a, - 0x19, 0xc5, 0x21, 0xa0, 0x1a, 0x64, 0xb0, 0xa6, 0xf0, 0xe5, 0x64, 0xa8, 0x9a, 0xab, 0xa1, 0xff, - 0x5b, 0x53, 0xfc, 0x8b, 0x49, 0x63, 0x3e, 0x46, 0xb7, 0x21, 0xd5, 0xd6, 0xfb, 0x7d, 0xd5, 0x2e, - 0x03, 0xd5, 0xb0, 0x1e, 0xba, 0x10, 0xca, 0xd5, 0x88, 0x88, 0x9c, 0x1f, 0x1d, 0x42, 0xa1, 0xa7, - 0x5a, 0xb6, 0x64, 0x69, 0xb2, 0x61, 0x75, 0x75, 0xdb, 0x2a, 0x67, 0xa9, 0x86, 0xe7, 0xc2, 0x34, - 0x1c, 0xa8, 0x96, 0x7d, 0xe2, 0x30, 0x37, 0x22, 0x62, 0xbe, 0xe7, 0x27, 0x10, 0x7d, 0xfa, 0xd9, - 0x19, 0x36, 0x5d, 0x85, 0xe5, 0xdc, 0x74, 0x7d, 0x47, 0x84, 0xdb, 0x91, 0x27, 0xfa, 0x74, 0x3f, - 0x01, 0xfd, 0x08, 0x56, 0x7a, 0xba, 0xac, 0xb8, 0xea, 0xa4, 0x76, 0x77, 0xa0, 0x3d, 0x28, 0xe7, - 0xa9, 0xd2, 0x17, 0x43, 0x27, 0xa9, 0xcb, 0x8a, 0xa3, 0xa2, 0x46, 0x04, 0x1a, 0x11, 0x71, 0xb9, - 0x37, 0x4a, 0x44, 0xf7, 0x61, 0x55, 0x36, 0x8c, 0xde, 0xc5, 0xa8, 0xf6, 0x02, 0xd5, 0x7e, 0x23, - 0x4c, 0xfb, 0x0e, 0x91, 0x19, 0x55, 0x8f, 0xe4, 0x31, 0x2a, 0x6a, 0x42, 0xc9, 0x30, 0xb1, 0x21, - 0x9b, 0x58, 0x32, 0x4c, 0xdd, 0xd0, 0x2d, 0xb9, 0x57, 0x2e, 0x52, 0xdd, 0x2f, 0x84, 0xe9, 0x3e, - 0x66, 0xfc, 0xc7, 0x9c, 0xbd, 0x11, 0x11, 0x8b, 0x46, 0x90, 0xc4, 0xb4, 0xea, 0x6d, 0x6c, 0x59, - 0x9e, 0xd6, 0xd2, 0x2c, 0xad, 0x94, 0x3f, 0xa8, 0x35, 0x40, 0x42, 0x75, 0xc8, 0xe2, 0x73, 0x22, - 0x2e, 0x0d, 0x75, 0x1b, 0x97, 0x97, 0xa7, 0x1f, 0xac, 0x3a, 0x65, 0xbd, 0xa7, 0xdb, 0x98, 0x1c, - 0x2a, 0xec, 0x8e, 0x90, 0x0c, 0x4f, 0x0d, 0xb1, 0xa9, 0x9e, 0x5d, 0x50, 0x35, 0x12, 0x7d, 0x63, - 0xa9, 0xba, 0x56, 0x46, 0x54, 0xe1, 0x4b, 0x61, 0x0a, 0xef, 0x51, 0x21, 0xa2, 0xa2, 0xee, 0x88, - 0x34, 0x22, 0xe2, 0xca, 0x70, 0x9c, 0x4c, 0xb6, 0xd8, 0x99, 0xaa, 0xc9, 0x3d, 0xf5, 0x21, 0xe6, - 0xc7, 0x66, 0x65, 0xfa, 0x16, 0xdb, 0xe5, 0xdc, 0xf4, 0xac, 0x90, 0x2d, 0x76, 0xe6, 0x27, 0x54, - 0x97, 0x20, 0x39, 0x94, 0x7b, 0x03, 0x2c, 0xbc, 0x00, 0x59, 0x9f, 0x63, 0x45, 0x65, 0x58, 0xea, - 0x63, 0xcb, 0x92, 0x3b, 0x98, 0xfa, 0xe1, 0x8c, 0xe8, 0x0c, 0x85, 0x02, 0xe4, 0xfc, 0xce, 0x54, - 0xf8, 0x2c, 0xea, 0x4a, 0x12, 0x3f, 0x49, 0x24, 0x87, 0xd8, 0xa4, 0xcb, 0xe6, 0x92, 0x7c, 0x88, - 0xae, 0x41, 0x9e, 0x4e, 0x59, 0x72, 0xde, 0x13, 0x67, 0x9d, 0x10, 0x73, 0x94, 0x78, 0x8f, 0x33, - 0x6d, 0x40, 0xd6, 0xd8, 0x36, 0x5c, 0x96, 0x38, 0x65, 0x01, 0x63, 0xdb, 0x70, 0x18, 0x9e, 0x85, - 0x1c, 0x59, 0x9f, 0xcb, 0x91, 0xa0, 0x1f, 0xc9, 0x12, 0x1a, 0x67, 0x11, 0xfe, 0x14, 0x83, 0xd2, - 0xa8, 0x03, 0x46, 0xb7, 0x21, 0x41, 0x62, 0x11, 0x0f, 0x2b, 0x95, 0x4d, 0x16, 0xa8, 0x36, 0x9d, - 0x40, 0xb5, 0xd9, 0x74, 0x02, 0x55, 0x35, 0xfd, 0xc5, 0x57, 0x1b, 0x91, 0xcf, 0xfe, 0xb6, 0x11, - 0x15, 0xa9, 0x04, 0xba, 0x44, 0x7c, 0xa5, 0xac, 0x6a, 0x92, 0xaa, 0xd0, 0x29, 0x67, 0x88, 0x23, - 0x94, 0x55, 0x6d, 0x4f, 0x41, 0x07, 0x50, 0x6a, 0xeb, 0x9a, 0x85, 0x35, 0x6b, 0x60, 0x49, 0x2c, - 0x10, 0xf2, 0x60, 0x12, 0x70, 0x87, 0x2c, 0xbc, 0xd6, 0x1c, 0xce, 0x63, 0xca, 0x28, 0x16, 0xdb, - 0x41, 0x02, 0x71, 0xab, 0x43, 0xb9, 0xa7, 0x2a, 0xb2, 0xad, 0x9b, 0x56, 0x39, 0x71, 0x35, 0x3e, - 0xd1, 0x1f, 0xde, 0x73, 0x58, 0x4e, 0x0d, 0x45, 0xb6, 0x71, 0x35, 0x41, 0xa6, 0x2b, 0xfa, 0x24, - 0xd1, 0xf3, 0x50, 0x94, 0x0d, 0x43, 0xb2, 0x6c, 0xd9, 0xc6, 0x52, 0xeb, 0xc2, 0xc6, 0x16, 0x0d, - 0x34, 0x39, 0x31, 0x2f, 0x1b, 0xc6, 0x09, 0xa1, 0x56, 0x09, 0x11, 0x3d, 0x07, 0x05, 0x12, 0x93, - 0x54, 0xb9, 0x27, 0x75, 0xb1, 0xda, 0xe9, 0xda, 0x34, 0xa4, 0xc4, 0xc5, 0x3c, 0xa7, 0x36, 0x28, - 0x51, 0x50, 0xdc, 0x3f, 0x4e, 0xe3, 0x11, 0x42, 0x90, 0x50, 0x64, 0x5b, 0xa6, 0x96, 0xcc, 0x89, - 0xf4, 0x99, 0xd0, 0x0c, 0xd9, 0xee, 0x72, 0xfb, 0xd0, 0x67, 0xb4, 0x06, 0x29, 0xae, 0x36, 0x4e, - 0xd5, 0xf2, 0x11, 0x5a, 0x85, 0xa4, 0x61, 0xea, 0x43, 0x4c, 0x7f, 0x5d, 0x5a, 0x64, 0x03, 0xe1, - 0x27, 0x31, 0x58, 0x1e, 0x8b, 0x5c, 0x44, 0x6f, 0x57, 0xb6, 0xba, 0xce, 0xb7, 0xc8, 0x33, 0x7a, - 0x8d, 0xe8, 0x95, 0x15, 0x6c, 0xf2, 0x68, 0x5f, 0x1e, 0x37, 0x75, 0x83, 0xbe, 0xe7, 0xa6, 0xe1, - 0xdc, 0x68, 0x1f, 0x4a, 0x3d, 0xd9, 0xb2, 0x25, 0xe6, 0xfd, 0x25, 0x5f, 0xe4, 0x7f, 0x66, 0xcc, - 0xc8, 0x2c, 0x56, 0x90, 0x0d, 0xcd, 0x95, 0x14, 0x88, 0xa8, 0x47, 0x45, 0x22, 0xac, 0xb6, 0x2e, - 0x1e, 0xca, 0x9a, 0xad, 0x6a, 0x58, 0x1a, 0xfb, 0x6b, 0x97, 0xc6, 0x14, 0xd6, 0x87, 0xaa, 0x82, - 0xb5, 0xb6, 0xf3, 0xbb, 0x56, 0x5c, 0x61, 0xf7, 0x77, 0x5a, 0x82, 0x08, 0x85, 0x60, 0xcc, 0x45, - 0x05, 0x88, 0xd9, 0xe7, 0x7c, 0xf1, 0x31, 0xfb, 0x1c, 0x7d, 0x07, 0x12, 0x64, 0x81, 0x74, 0xe1, - 0x85, 0x09, 0x09, 0x0b, 0x97, 0x6b, 0x5e, 0x18, 0x58, 0xa4, 0x9c, 0x82, 0xe0, 0x1e, 0x05, 0x37, - 0x0e, 0x8f, 0x6a, 0x15, 0x5e, 0x84, 0xe2, 0x48, 0x90, 0xf5, 0xfd, 0xbb, 0xa8, 0xff, 0xdf, 0x09, - 0x45, 0xc8, 0x07, 0xa2, 0xa9, 0xb0, 0x06, 0xab, 0x93, 0x82, 0xa3, 0xd0, 0x75, 0xe9, 0x81, 0x20, - 0x87, 0x5e, 0x85, 0xb4, 0x1b, 0x1d, 0xd9, 0x51, 0x1c, 0xb7, 0x95, 0xc3, 0x2c, 0xba, 0xac, 0xe4, - 0x0c, 0x92, 0x2d, 0x4d, 0xf7, 0x42, 0x8c, 0x4e, 0x7c, 0x49, 0x36, 0x8c, 0x86, 0x6c, 0x75, 0x85, - 0x0f, 0xa0, 0x1c, 0x16, 0xf9, 0x46, 0x96, 0x91, 0x70, 0xb7, 0xe0, 0x1a, 0xa4, 0xce, 0x74, 0xb3, - 0x2f, 0xdb, 0x54, 0x59, 0x5e, 0xe4, 0x23, 0xb2, 0x35, 0x59, 0x14, 0x8c, 0x53, 0x32, 0x1b, 0x08, - 0x12, 0x5c, 0x0a, 0x8d, 0x7e, 0x44, 0x44, 0xd5, 0x14, 0xcc, 0xec, 0x99, 0x17, 0xd9, 0xc0, 0x53, - 0xc4, 0x26, 0xcb, 0x06, 0xe4, 0xb3, 0x16, 0x5d, 0x2b, 0xd5, 0x9f, 0x11, 0xf9, 0x48, 0xf8, 0x81, - 0xbb, 0xf5, 0xbd, 0xd8, 0x82, 0x6e, 0x40, 0x82, 0x46, 0x23, 0x66, 0xa5, 0xb5, 0xf1, 0x4d, 0x4e, - 0xb8, 0x44, 0xca, 0x23, 0x34, 0xa0, 0x12, 0x1e, 0x4b, 0x16, 0xd2, 0xf4, 0xf3, 0x28, 0xac, 0x4d, - 0x0e, 0xc7, 0xe8, 0x0a, 0x00, 0xf3, 0xdf, 0xfc, 0xf4, 0xc7, 0xaf, 0xe7, 0xc4, 0x0c, 0xa5, 0xdc, - 0x21, 0x2e, 0xe0, 0x79, 0x28, 0x7a, 0xaf, 0x25, 0x4b, 0x7d, 0xc8, 0xb6, 0x69, 0x5c, 0xcc, 0xbb, - 0x3c, 0x27, 0xea, 0x43, 0x8c, 0x6e, 0x42, 0x92, 0x7c, 0x89, 0x38, 0xca, 0xf8, 0x94, 0xe9, 0x30, - 0x26, 0xe1, 0xd7, 0x31, 0xdf, 0x7c, 0x82, 0x51, 0xfb, 0x71, 0xfa, 0x86, 0x12, 0xc4, 0xed, 0x73, - 0x36, 0xa5, 0x9c, 0x48, 0x1e, 0xd1, 0x09, 0xac, 0xb2, 0x0c, 0x03, 0x2b, 0x92, 0xcf, 0x6d, 0xf0, - 0x8c, 0x7f, 0x0e, 0x8f, 0x81, 0x1c, 0xf1, 0x03, 0xd7, 0x73, 0x84, 0x7a, 0x8d, 0xe4, 0x23, 0x78, - 0x8d, 0x5f, 0xc5, 0xdc, 0xa3, 0x16, 0x08, 0xf6, 0x4f, 0xd8, 0x3e, 0xef, 0xc0, 0x8a, 0x82, 0xdb, - 0xaa, 0xf2, 0x6d, 0xcd, 0xb3, 0xcc, 0xa5, 0x9f, 0xb0, 0x75, 0xfe, 0x9c, 0x85, 0xb4, 0x88, 0x2d, - 0x83, 0x84, 0x5a, 0x54, 0x85, 0x0c, 0x3e, 0x6f, 0x63, 0xc3, 0x76, 0xb2, 0x93, 0xc9, 0x59, 0x1e, - 0xe3, 0xae, 0x3b, 0x9c, 0x04, 0xb3, 0xb8, 0x62, 0xe8, 0x16, 0x87, 0xa7, 0xe1, 0x48, 0x93, 0x8b, - 0xfb, 0xf1, 0xe9, 0x6b, 0x0e, 0x3e, 0x8d, 0x87, 0x42, 0x14, 0x26, 0x35, 0x02, 0x50, 0x6f, 0x71, - 0x80, 0x9a, 0x98, 0xf1, 0xb1, 0x00, 0x42, 0xad, 0x05, 0x10, 0x6a, 0x72, 0xc6, 0x32, 0x43, 0x20, - 0xea, 0x6b, 0x0e, 0x44, 0x4d, 0xcd, 0x98, 0xf1, 0x08, 0x46, 0xbd, 0x1b, 0xc4, 0xa8, 0x0c, 0x5b, - 0x5e, 0x0b, 0x95, 0x9e, 0x0a, 0x52, 0xbf, 0xe7, 0x03, 0xa9, 0xe9, 0x50, 0x74, 0xc8, 0x14, 0x4d, - 0x40, 0xa9, 0x6f, 0x05, 0x50, 0x6a, 0x66, 0x86, 0x1d, 0xa6, 0xc0, 0xd4, 0x3b, 0x7e, 0x98, 0x0a, - 0xa1, 0x68, 0x97, 0xff, 0xf7, 0x30, 0x9c, 0xfa, 0xba, 0x8b, 0x53, 0xb3, 0xa1, 0x80, 0x9b, 0xaf, - 0x65, 0x14, 0xa8, 0x1e, 0x8d, 0x01, 0x55, 0x06, 0x2c, 0x9f, 0x0f, 0x55, 0x31, 0x03, 0xa9, 0x1e, - 0x8d, 0x21, 0xd5, 0xfc, 0x0c, 0x85, 0x33, 0xa0, 0xea, 0xfb, 0x93, 0xa1, 0x6a, 0x38, 0x98, 0xe4, - 0xd3, 0x9c, 0x0f, 0xab, 0x4a, 0x21, 0x58, 0xb5, 0x18, 0x8a, 0xab, 0x98, 0xfa, 0xb9, 0xc1, 0xea, - 0xe9, 0x04, 0xb0, 0xca, 0x60, 0xe5, 0xf5, 0x50, 0xe5, 0x73, 0xa0, 0xd5, 0xd3, 0x09, 0x68, 0x75, - 0x79, 0xa6, 0xda, 0x99, 0x70, 0x75, 0x37, 0x08, 0x57, 0xd1, 0x8c, 0x33, 0x16, 0x8a, 0x57, 0x5b, - 0x61, 0x78, 0x95, 0x61, 0xca, 0x9b, 0xa1, 0x1a, 0x17, 0x00, 0xac, 0x47, 0x63, 0x80, 0x75, 0x75, - 0xc6, 0x4e, 0x9b, 0x17, 0xb1, 0xbe, 0x48, 0x92, 0xa6, 0x11, 0x57, 0x4d, 0xf2, 0x2e, 0x6c, 0x9a, - 0xba, 0xc9, 0xb1, 0x27, 0x1b, 0x08, 0xd7, 0x09, 0x82, 0xf1, 0xdc, 0xf2, 0x14, 0x74, 0x4b, 0xf3, - 0x5b, 0x9f, 0x2b, 0x16, 0x7e, 0x1f, 0xf5, 0x64, 0x69, 0xe2, 0xef, 0x47, 0x3f, 0x19, 0x8e, 0x7e, - 0x7c, 0x98, 0x37, 0x16, 0xc4, 0xbc, 0x1b, 0x90, 0x25, 0x79, 0xeb, 0x08, 0x9c, 0x95, 0x0d, 0x17, - 0xce, 0xde, 0x80, 0x65, 0x1a, 0x3e, 0x59, 0xea, 0xc4, 0x93, 0xd5, 0x04, 0xcd, 0x9b, 0x8a, 0xe4, - 0x05, 0xb3, 0x02, 0xcb, 0x5a, 0x5f, 0x86, 0x15, 0x1f, 0xaf, 0x9b, 0x0f, 0x33, 0x6c, 0x57, 0x72, - 0xb9, 0x77, 0x78, 0x62, 0xfc, 0xc7, 0xa8, 0x67, 0x21, 0x0f, 0x07, 0x4f, 0x82, 0xac, 0xd1, 0xc7, - 0x04, 0x59, 0x63, 0xdf, 0x1a, 0xb2, 0xfa, 0xf3, 0xfb, 0x78, 0x30, 0xbf, 0xff, 0x67, 0xd4, 0xfb, - 0x27, 0x2e, 0x00, 0x6d, 0xeb, 0x0a, 0xe6, 0x19, 0x37, 0x7d, 0x26, 0x09, 0x4a, 0x4f, 0xef, 0xf0, - 0xbc, 0x9a, 0x3c, 0x12, 0x2e, 0x37, 0x76, 0x66, 0x78, 0x68, 0x74, 0x93, 0xf5, 0x24, 0xb5, 0x30, - 0x4f, 0xd6, 0x4b, 0x10, 0x7f, 0x80, 0x59, 0xa4, 0xcb, 0x89, 0xe4, 0x91, 0xf0, 0xd1, 0x4d, 0x46, - 0xe3, 0x57, 0x4e, 0x64, 0x03, 0x74, 0x1b, 0x32, 0xb4, 0x8c, 0x2e, 0xe9, 0x86, 0xc5, 0x03, 0x52, - 0x20, 0xd1, 0x61, 0xd5, 0xf2, 0xcd, 0x63, 0xc2, 0x73, 0x64, 0x58, 0x62, 0xda, 0xe0, 0x4f, 0x3e, - 0x1c, 0x92, 0x09, 0x40, 0xe1, 0xcb, 0x90, 0x21, 0xb3, 0xb7, 0x0c, 0xb9, 0x8d, 0x69, 0x64, 0xc9, - 0x88, 0x1e, 0x41, 0xb8, 0x0f, 0x68, 0x3c, 0x4e, 0xa2, 0x06, 0xa4, 0xf0, 0x10, 0x6b, 0xb6, 0x45, - 0x53, 0xf0, 0x91, 0x04, 0x9a, 0xe7, 0x45, 0x58, 0xb3, 0xab, 0x65, 0x62, 0xe4, 0x7f, 0x7c, 0xb5, - 0x51, 0x62, 0xdc, 0x37, 0xf5, 0xbe, 0x6a, 0xe3, 0xbe, 0x61, 0x5f, 0x88, 0x5c, 0x5e, 0xf8, 0x6b, - 0x8c, 0x00, 0xbf, 0x40, 0xfc, 0x9c, 0x68, 0x5b, 0x67, 0xcb, 0xc7, 0x7c, 0x80, 0x7f, 0x3e, 0x7b, - 0x5f, 0x01, 0xe8, 0xc8, 0x96, 0xf4, 0xb1, 0xac, 0xd9, 0x58, 0xe1, 0x46, 0xcf, 0x74, 0x64, 0xeb, - 0x5d, 0x4a, 0x20, 0x7f, 0x9d, 0xbc, 0x1e, 0x58, 0x58, 0xe1, 0xa5, 0x87, 0xa5, 0x8e, 0x6c, 0x9d, - 0x5a, 0x58, 0xf1, 0xad, 0x72, 0xe9, 0xd1, 0x56, 0x19, 0xb4, 0x71, 0x7a, 0xc4, 0xc6, 0x3e, 0x48, - 0x96, 0xf1, 0x43, 0x32, 0x54, 0x81, 0xb4, 0x61, 0xaa, 0xba, 0xa9, 0xda, 0x17, 0xf4, 0xc7, 0xc4, - 0x45, 0x77, 0x8c, 0xae, 0x41, 0xbe, 0x8f, 0xfb, 0x86, 0xae, 0xf7, 0x24, 0xe6, 0x6c, 0xb2, 0x54, - 0x34, 0xc7, 0x89, 0x75, 0xea, 0x73, 0x3e, 0x89, 0x79, 0xa7, 0xcf, 0x83, 0xde, 0x8f, 0xd7, 0xbc, - 0xeb, 0x13, 0xcc, 0xeb, 0xa3, 0x90, 0x45, 0x8c, 0xd8, 0xd7, 0x1d, 0xff, 0xaf, 0x0c, 0x2c, 0xfc, - 0x8c, 0x16, 0xe3, 0x82, 0xb9, 0x11, 0x3a, 0x81, 0x65, 0xf7, 0xf0, 0x4b, 0x03, 0xea, 0x14, 0x9c, - 0xed, 0x3c, 0xaf, 0xf7, 0x28, 0x0d, 0x83, 0x64, 0x0b, 0xbd, 0x07, 0x4f, 0x8f, 0x78, 0x36, 0x57, - 0x75, 0x6c, 0x5e, 0x07, 0xf7, 0x54, 0xd0, 0xc1, 0x39, 0xaa, 0x3d, 0x63, 0xc5, 0x1f, 0xf1, 0xcc, - 0xed, 0x41, 0x21, 0x98, 0xe6, 0x4d, 0xfc, 0xfd, 0xd7, 0x20, 0x6f, 0x62, 0x5b, 0x56, 0x35, 0x29, - 0x50, 0x41, 0xcb, 0x31, 0x22, 0xaf, 0xcb, 0x1d, 0xc3, 0x53, 0x13, 0xd3, 0x3d, 0xf4, 0x5d, 0xc8, - 0x78, 0x99, 0x62, 0x34, 0x04, 0x3c, 0xb9, 0x45, 0x16, 0x8f, 0x57, 0xf8, 0x43, 0xd4, 0x53, 0x19, - 0x2c, 0xdb, 0xd4, 0x21, 0x65, 0x62, 0x6b, 0xd0, 0x63, 0x85, 0x94, 0xc2, 0xf6, 0xcb, 0xf3, 0x25, - 0x8a, 0x84, 0x3a, 0xe8, 0xd9, 0x22, 0x17, 0x16, 0xee, 0x43, 0x8a, 0x51, 0x50, 0x16, 0x96, 0x4e, - 0x0f, 0xf7, 0x0f, 0x8f, 0xde, 0x3d, 0x2c, 0x45, 0x10, 0x40, 0x6a, 0xa7, 0x56, 0xab, 0x1f, 0x37, - 0x4b, 0x51, 0x94, 0x81, 0xe4, 0x4e, 0xf5, 0x48, 0x6c, 0x96, 0x62, 0x84, 0x2c, 0xd6, 0xef, 0xd6, - 0x6b, 0xcd, 0x52, 0x1c, 0x2d, 0x43, 0x9e, 0x3d, 0x4b, 0xbb, 0x47, 0xe2, 0xdb, 0x3b, 0xcd, 0x52, - 0xc2, 0x47, 0x3a, 0xa9, 0x1f, 0xde, 0xa9, 0x8b, 0xa5, 0xa4, 0xf0, 0x0a, 0x5c, 0x0a, 0x4d, 0x2d, - 0xbd, 0x9a, 0x4c, 0xd4, 0x57, 0x93, 0x11, 0x7e, 0x19, 0x83, 0x4a, 0x78, 0xbe, 0x88, 0xee, 0x8e, - 0x2c, 0x7c, 0x7b, 0x81, 0x64, 0x73, 0x64, 0xf5, 0xe8, 0x39, 0x28, 0x98, 0xf8, 0x0c, 0xdb, 0xed, - 0x2e, 0xcb, 0x5f, 0x59, 0xc0, 0xcc, 0x8b, 0x79, 0x4e, 0xa5, 0x42, 0x16, 0x63, 0xfb, 0x10, 0xb7, - 0x6d, 0x89, 0xf9, 0x22, 0xb6, 0xe9, 0x32, 0x84, 0x8d, 0x50, 0x4f, 0x18, 0x51, 0xf8, 0x60, 0x21, - 0x5b, 0x66, 0x20, 0x29, 0xd6, 0x9b, 0xe2, 0x7b, 0xa5, 0x38, 0x42, 0x50, 0xa0, 0x8f, 0xd2, 0xc9, - 0xe1, 0xce, 0xf1, 0x49, 0xe3, 0x88, 0xd8, 0x72, 0x05, 0x8a, 0x8e, 0x2d, 0x1d, 0x62, 0x52, 0x78, - 0xdf, 0x8b, 0x3f, 0xbe, 0xba, 0xd4, 0x2e, 0x14, 0x46, 0xd2, 0xc5, 0xe8, 0x38, 0x9e, 0xf1, 0x0a, - 0x39, 0x6e, 0x2a, 0x28, 0xe6, 0x87, 0xfe, 0xa1, 0xf0, 0x9b, 0x28, 0x3c, 0x33, 0x25, 0xa1, 0x44, - 0xfb, 0x23, 0x96, 0xbf, 0xb5, 0x48, 0x3a, 0x3a, 0xba, 0xf1, 0x6e, 0xcf, 0x65, 0xac, 0x93, 0x83, - 0x9d, 0x93, 0x46, 0x70, 0xe3, 0x09, 0xb7, 0xe1, 0xe9, 0x90, 0x8c, 0x7f, 0x46, 0x41, 0x4c, 0xf8, - 0x5d, 0xcc, 0x2f, 0x1a, 0x4c, 0xe1, 0xd7, 0x20, 0x25, 0xb7, 0x49, 0xd2, 0x4a, 0x17, 0x97, 0x16, - 0xf9, 0x68, 0x4a, 0x9d, 0x13, 0xbd, 0x09, 0x60, 0x9f, 0x4b, 0x6c, 0x3d, 0x8e, 0x1f, 0x1a, 0xaf, - 0x08, 0xd4, 0xcf, 0x71, 0xbb, 0x79, 0xce, 0x57, 0x9f, 0xb1, 0xf9, 0x93, 0x85, 0xde, 0x9e, 0xe4, - 0x71, 0xe7, 0x6c, 0x31, 0x2c, 0xe6, 0x6b, 0x93, 0x8f, 0xe6, 0x6b, 0x85, 0x5f, 0xc4, 0x3d, 0x27, - 0x14, 0x2c, 0x68, 0x3d, 0xb6, 0xcc, 0x67, 0xc4, 0x96, 0xb1, 0x05, 0x6d, 0x39, 0x31, 0x7a, 0xc5, - 0x9f, 0x5c, 0xf4, 0x4a, 0x3c, 0x62, 0xf4, 0xf2, 0x6f, 0xaa, 0x64, 0x70, 0x53, 0x8d, 0x05, 0x9a, - 0xd4, 0x84, 0x40, 0xf3, 0x1e, 0x80, 0xaf, 0xf3, 0xb1, 0x0a, 0x49, 0x53, 0x1f, 0x68, 0x0a, 0xdd, - 0xb9, 0x49, 0x91, 0x0d, 0xd0, 0xab, 0x4e, 0x55, 0x37, 0x16, 0x12, 0x6f, 0xc8, 0xf1, 0xf4, 0x95, - 0xff, 0x78, 0x79, 0xf7, 0x21, 0x24, 0xe9, 0x3f, 0x23, 0x51, 0x90, 0x76, 0x36, 0x38, 0xac, 0x22, - 0xcf, 0xe8, 0xc7, 0x00, 0xb2, 0x6d, 0x9b, 0x6a, 0x6b, 0xe0, 0x29, 0xde, 0x98, 0xfc, 0xcf, 0x77, - 0x1c, 0xbe, 0xea, 0x65, 0xfe, 0xf3, 0x57, 0x3d, 0x51, 0xdf, 0x06, 0xf0, 0x29, 0x14, 0x0e, 0xa1, - 0x10, 0x94, 0x75, 0x80, 0x00, 0x9b, 0x43, 0x10, 0x08, 0x30, 0x5c, 0xc7, 0x81, 0x80, 0x0b, 0x23, - 0xe2, 0xac, 0x83, 0x45, 0x07, 0xc2, 0xbf, 0xa3, 0x90, 0xf3, 0x6f, 0x99, 0xff, 0xb7, 0x5c, 0x5a, - 0xf8, 0x24, 0x0a, 0x69, 0x77, 0xf1, 0x21, 0x1d, 0x24, 0xcf, 0x76, 0x31, 0x7f, 0xbf, 0x84, 0xb5, - 0xa4, 0xe2, 0x6e, 0xa3, 0xeb, 0x0d, 0xd7, 0xf9, 0x87, 0x95, 0x3e, 0xfd, 0x96, 0x76, 0x8a, 0xd5, - 0xdc, 0xd9, 0xbf, 0x01, 0x19, 0xf7, 0xd4, 0x11, 0x6c, 0x2e, 0x2b, 0x8a, 0x89, 0x2d, 0x8b, 0xc7, - 0x7d, 0x67, 0x48, 0xfb, 0x90, 0xfa, 0xc7, 0xbc, 0x19, 0x13, 0x17, 0xd9, 0x40, 0x50, 0xa0, 0x38, - 0x72, 0x64, 0xd1, 0x1b, 0xb0, 0x64, 0x0c, 0x5a, 0x92, 0xb3, 0x35, 0x46, 0x6e, 0x0a, 0x39, 0xa8, - 0x6f, 0xd0, 0xea, 0xa9, 0xed, 0x7d, 0x7c, 0xe1, 0x4c, 0xc6, 0x18, 0xb4, 0xf6, 0xd9, 0x0e, 0x62, - 0x5f, 0x89, 0xf9, 0xbf, 0xf2, 0xd3, 0x28, 0xa4, 0x9d, 0x13, 0x81, 0xbe, 0x0f, 0x19, 0xd7, 0x1d, - 0xb8, 0xfd, 0xe9, 0x50, 0x3f, 0xc2, 0xf5, 0x7b, 0x22, 0xe8, 0x06, 0x2c, 0x5b, 0x6a, 0x47, 0x73, - 0x2a, 0xf1, 0xac, 0xcc, 0x12, 0xa3, 0x5b, 0xb3, 0xc8, 0x5e, 0x1c, 0x38, 0xb5, 0x81, 0xbb, 0x89, - 0x74, 0xbc, 0x94, 0xb8, 0x9b, 0x48, 0x27, 0x4a, 0x49, 0xe1, 0x5f, 0x51, 0x48, 0x3b, 0x35, 0x74, - 0xf4, 0x8a, 0xef, 0x00, 0x16, 0x26, 0xd9, 0x9b, 0x33, 0x7a, 0xbd, 0xc5, 0xe0, 0xbc, 0x63, 0x8b, - 0xcf, 0x3b, 0xac, 0x41, 0xec, 0xb4, 0xea, 0x13, 0x0b, 0xb7, 0xea, 0x6f, 0x02, 0xb2, 0x75, 0x5b, - 0xee, 0x49, 0x43, 0xdd, 0x56, 0xb5, 0x8e, 0xc4, 0x2c, 0xcf, 0xce, 0x4a, 0x89, 0xbe, 0xb9, 0x47, - 0x5f, 0x1c, 0xbb, 0x3f, 0xc1, 0xcd, 0x70, 0x17, 0x6d, 0x15, 0xae, 0x41, 0x8a, 0x27, 0x71, 0xac, - 0x57, 0xc8, 0x47, 0x6e, 0xd7, 0x25, 0xe1, 0xeb, 0xba, 0x54, 0x20, 0xdd, 0xc7, 0xb6, 0x4c, 0x0f, - 0x3e, 0x73, 0xc0, 0xee, 0xf8, 0xc6, 0xeb, 0x90, 0xf5, 0x75, 0x6d, 0x89, 0x2f, 0x38, 0xac, 0xbf, - 0x5b, 0x8a, 0x54, 0x96, 0x3e, 0xfd, 0xfc, 0x6a, 0xfc, 0x10, 0x7f, 0x4c, 0x36, 0xb0, 0x58, 0xaf, - 0x35, 0xea, 0xb5, 0xfd, 0x52, 0xb4, 0x92, 0xfd, 0xf4, 0xf3, 0xab, 0x4b, 0x22, 0xa6, 0x25, 0xee, - 0x1b, 0x0d, 0xc8, 0xf9, 0xff, 0x4a, 0x30, 0xb5, 0x41, 0x50, 0xb8, 0x73, 0x7a, 0x7c, 0xb0, 0x57, - 0xdb, 0x69, 0xd6, 0xa5, 0x7b, 0x47, 0xcd, 0x7a, 0x29, 0x8a, 0x9e, 0x86, 0x95, 0x83, 0xbd, 0xb7, - 0x1a, 0x4d, 0xa9, 0x76, 0xb0, 0x57, 0x3f, 0x6c, 0x4a, 0x3b, 0xcd, 0xe6, 0x4e, 0x6d, 0xbf, 0x14, - 0xdb, 0xfe, 0x6d, 0x16, 0x8a, 0x3b, 0xd5, 0xda, 0x1e, 0xc9, 0x61, 0xd5, 0xb6, 0x4c, 0x4b, 0x69, - 0x35, 0x48, 0xd0, 0x62, 0xd9, 0xd4, 0x1b, 0x78, 0x95, 0xe9, 0x0d, 0x10, 0xb4, 0x0b, 0x49, 0x5a, - 0x47, 0x43, 0xd3, 0xaf, 0xe4, 0x55, 0x66, 0x74, 0x44, 0xc8, 0x64, 0xe8, 0x51, 0x99, 0x7a, 0x47, - 0xaf, 0x32, 0xbd, 0x41, 0x82, 0x0e, 0x60, 0xc9, 0x29, 0x73, 0xcc, 0xba, 0xed, 0x56, 0x99, 0xd9, - 0x69, 0x20, 0x4b, 0x63, 0xe5, 0xa8, 0xe9, 0xd7, 0xf7, 0x2a, 0x33, 0x5a, 0x27, 0x68, 0x0f, 0x52, - 0x1c, 0x09, 0xce, 0xb8, 0xb9, 0x56, 0x99, 0xd5, 0x31, 0x40, 0x22, 0x64, 0xbc, 0x42, 0xdf, 0xec, - 0x4b, 0x89, 0x95, 0x39, 0xba, 0x42, 0xe8, 0x3e, 0xe4, 0x83, 0xe8, 0x72, 0xbe, 0xdb, 0x71, 0x95, - 0x39, 0x7b, 0x13, 0x44, 0x7f, 0x10, 0x6a, 0xce, 0x77, 0x5b, 0xae, 0x32, 0x67, 0xab, 0x02, 0x7d, - 0x08, 0xcb, 0xe3, 0x50, 0x70, 0xfe, 0xcb, 0x73, 0x95, 0x05, 0x9a, 0x17, 0xa8, 0x0f, 0x68, 0x02, - 0x84, 0x5c, 0xe0, 0x2e, 0x5d, 0x65, 0x91, 0x5e, 0x06, 0x52, 0xa0, 0x38, 0x0a, 0x49, 0xe6, 0xbd, - 0x5b, 0x57, 0x99, 0xbb, 0xaf, 0xc1, 0xbe, 0x12, 0x44, 0x2f, 0xf3, 0xde, 0xb5, 0xab, 0xcc, 0xdd, - 0xe6, 0x40, 0xa7, 0x00, 0x3e, 0x8c, 0x39, 0xc7, 0xdd, 0xbb, 0xca, 0x3c, 0x0d, 0x0f, 0x64, 0xc0, - 0xca, 0x24, 0x6c, 0xb9, 0xc8, 0x55, 0xbc, 0xca, 0x42, 0x7d, 0x10, 0xb2, 0x9f, 0x83, 0xa8, 0x65, - 0xbe, 0xab, 0x79, 0x95, 0x39, 0x1b, 0x22, 0xd5, 0xfa, 0x17, 0x5f, 0xaf, 0x47, 0xbf, 0xfc, 0x7a, - 0x3d, 0xfa, 0xf7, 0xaf, 0xd7, 0xa3, 0x9f, 0x7d, 0xb3, 0x1e, 0xf9, 0xf2, 0x9b, 0xf5, 0xc8, 0x5f, - 0xbe, 0x59, 0x8f, 0xfc, 0xf0, 0xa5, 0x8e, 0x6a, 0x77, 0x07, 0xad, 0xcd, 0xb6, 0xde, 0xdf, 0xf2, - 0xdf, 0xd2, 0x9e, 0x74, 0x73, 0xbc, 0x95, 0xa2, 0xd1, 0xf4, 0xd6, 0x7f, 0x03, 0x00, 0x00, 0xff, - 0xff, 0x8d, 0xc9, 0xd7, 0xa0, 0x59, 0x2e, 0x00, 0x00, + // 3451 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x5b, 0xcb, 0x73, 0x23, 0xe5, + 0xb5, 0x97, 0x5a, 0xef, 0x23, 0xeb, 0xe1, 0xcf, 0x66, 0xd0, 0x88, 0x19, 0x7b, 0xe8, 0xa9, 0x81, + 0x99, 0x01, 0x3c, 0x5c, 0xcf, 0x1d, 0x18, 0xee, 0xc0, 0xa5, 0x6c, 0x59, 0x83, 0xcc, 0x78, 0x6c, + 0xd3, 0x96, 0x4d, 0x71, 0x6f, 0x32, 0x4d, 0x4b, 0xfd, 0xd9, 0x6a, 0x46, 0x52, 0x37, 0xdd, 0x2d, + 0x23, 0xb3, 0x0c, 0xc5, 0x86, 0x4a, 0x55, 0xd8, 0xa4, 0x92, 0x54, 0x85, 0x5d, 0x52, 0x95, 0xfc, + 0x07, 0x59, 0x65, 0x95, 0x05, 0x8b, 0x2c, 0x58, 0x25, 0xa9, 0x2c, 0x48, 0x0a, 0x76, 0xf9, 0x07, + 0xb2, 0x4b, 0x52, 0xdf, 0xa3, 0x5f, 0x52, 0xb7, 0x1e, 0x0c, 0x50, 0x95, 0x0a, 0x3b, 0xf5, 0xe9, + 0x73, 0x4e, 0x7f, 0x8f, 0xf3, 0x9d, 0xc7, 0xef, 0x7c, 0x82, 0x27, 0x6c, 0xdc, 0x57, 0xb1, 0xd9, + 0xd3, 0xfa, 0xf6, 0x0d, 0xa5, 0xd5, 0xd6, 0x6e, 0xd8, 0x67, 0x06, 0xb6, 0xd6, 0x0c, 0x53, 0xb7, + 0x75, 0x54, 0xf2, 0x5e, 0xae, 0x91, 0x97, 0xd5, 0x8b, 0x3e, 0xee, 0xb6, 0x79, 0x66, 0xd8, 0xfa, + 0x0d, 0xc3, 0xd4, 0xf5, 0x63, 0xc6, 0x5f, 0xbd, 0xe0, 0x7b, 0x4d, 0xf5, 0xf8, 0xb5, 0x05, 0xde, + 0x72, 0xe1, 0x87, 0xf8, 0xcc, 0x79, 0x7b, 0x71, 0x4c, 0xd6, 0x50, 0x4c, 0xa5, 0xe7, 0xbc, 0x5e, + 0x3d, 0xd1, 0xf5, 0x93, 0x2e, 0xbe, 0x41, 0x9f, 0x5a, 0x83, 0xe3, 0x1b, 0xb6, 0xd6, 0xc3, 0x96, + 0xad, 0xf4, 0x0c, 0xce, 0xb0, 0x7c, 0xa2, 0x9f, 0xe8, 0xf4, 0xe7, 0x0d, 0xf2, 0x8b, 0x51, 0xc5, + 0x7f, 0x02, 0x64, 0x24, 0xfc, 0xee, 0x00, 0x5b, 0x36, 0x5a, 0x87, 0x24, 0x6e, 0x77, 0xf4, 0x4a, + 0xfc, 0x52, 0xfc, 0x6a, 0x7e, 0xfd, 0xc2, 0xda, 0xc8, 0xe4, 0xd6, 0x38, 0x5f, 0xbd, 0xdd, 0xd1, + 0x1b, 0x31, 0x89, 0xf2, 0xa2, 0x5b, 0x90, 0x3a, 0xee, 0x0e, 0xac, 0x4e, 0x45, 0xa0, 0x42, 0x17, + 0xa3, 0x84, 0xee, 0x12, 0xa6, 0x46, 0x4c, 0x62, 0xdc, 0xe4, 0x53, 0x5a, 0xff, 0x58, 0xaf, 0x24, + 0x26, 0x7f, 0x6a, 0xbb, 0x7f, 0x4c, 0x3f, 0x45, 0x78, 0xd1, 0x26, 0x80, 0xd6, 0xd7, 0x6c, 0xb9, + 0xdd, 0x51, 0xb4, 0x7e, 0x25, 0x49, 0x25, 0x9f, 0x8c, 0x96, 0xd4, 0xec, 0x1a, 0x61, 0x6c, 0xc4, + 0xa4, 0x9c, 0xe6, 0x3c, 0x90, 0xe1, 0xbe, 0x3b, 0xc0, 0xe6, 0x59, 0x25, 0x35, 0x79, 0xb8, 0x6f, + 0x10, 0x26, 0x32, 0x5c, 0xca, 0x8d, 0xb6, 0x21, 0xdf, 0xc2, 0x27, 0x5a, 0x5f, 0x6e, 0x75, 0xf5, + 0xf6, 0xc3, 0x4a, 0x9a, 0x0a, 0x8b, 0x51, 0xc2, 0x9b, 0x84, 0x75, 0x93, 0x70, 0x6e, 0x0a, 0x95, + 0x78, 0x23, 0x26, 0x41, 0xcb, 0xa5, 0xa0, 0x97, 0x21, 0xdb, 0xee, 0xe0, 0xf6, 0x43, 0xd9, 0x1e, + 0x56, 0x32, 0x54, 0xcf, 0x6a, 0x94, 0x9e, 0x1a, 0xe1, 0x6b, 0x0e, 0x1b, 0x31, 0x29, 0xd3, 0x66, + 0x3f, 0xd1, 0x5d, 0x00, 0x15, 0x77, 0xb5, 0x53, 0x6c, 0x12, 0xf9, 0xec, 0xe4, 0x35, 0xd8, 0x62, + 0x9c, 0xcd, 0x21, 0x1f, 0x46, 0x4e, 0x75, 0x08, 0xa8, 0x06, 0x39, 0xdc, 0x57, 0xf9, 0x74, 0x72, + 0x54, 0xcd, 0xa5, 0xc8, 0xfd, 0xee, 0xab, 0xfe, 0xc9, 0x64, 0x31, 0x7f, 0x46, 0xb7, 0x21, 0xdd, + 0xd6, 0x7b, 0x3d, 0xcd, 0xae, 0x00, 0xd5, 0xb0, 0x12, 0x39, 0x11, 0xca, 0xd5, 0x88, 0x49, 0x9c, + 0x1f, 0xed, 0x42, 0xb1, 0xab, 0x59, 0xb6, 0x6c, 0xf5, 0x15, 0xc3, 0xea, 0xe8, 0xb6, 0x55, 0xc9, + 0x53, 0x0d, 0x57, 0xa2, 0x34, 0xec, 0x68, 0x96, 0x7d, 0xe0, 0x30, 0x37, 0x62, 0x52, 0xa1, 0xeb, + 0x27, 0x10, 0x7d, 0xfa, 0xf1, 0x31, 0x36, 0x5d, 0x85, 0x95, 0x85, 0xc9, 0xfa, 0xf6, 0x08, 0xb7, + 0x23, 0x4f, 0xf4, 0xe9, 0x7e, 0x02, 0xfa, 0x7f, 0x58, 0xea, 0xea, 0x8a, 0xea, 0xaa, 0x93, 0xdb, + 0x9d, 0x41, 0xff, 0x61, 0xa5, 0x40, 0x95, 0x5e, 0x8b, 0x1c, 0xa4, 0xae, 0xa8, 0x8e, 0x8a, 0x1a, + 0x11, 0x68, 0xc4, 0xa4, 0xc5, 0xee, 0x28, 0x11, 0x3d, 0x80, 0x65, 0xc5, 0x30, 0xba, 0x67, 0xa3, + 0xda, 0x8b, 0x54, 0xfb, 0xf5, 0x28, 0xed, 0x1b, 0x44, 0x66, 0x54, 0x3d, 0x52, 0xc6, 0xa8, 0xa8, + 0x09, 0x65, 0xc3, 0xc4, 0x86, 0x62, 0x62, 0xd9, 0x30, 0x75, 0x43, 0xb7, 0x94, 0x6e, 0xa5, 0x44, + 0x75, 0x3f, 0x1d, 0xa5, 0x7b, 0x9f, 0xf1, 0xef, 0x73, 0xf6, 0x46, 0x4c, 0x2a, 0x19, 0x41, 0x12, + 0xd3, 0xaa, 0xb7, 0xb1, 0x65, 0x79, 0x5a, 0xcb, 0xd3, 0xb4, 0x52, 0xfe, 0xa0, 0xd6, 0x00, 0x09, + 0xd5, 0x21, 0x8f, 0x87, 0x44, 0x5c, 0x3e, 0xd5, 0x6d, 0x5c, 0x59, 0x9c, 0x7c, 0xb0, 0xea, 0x94, + 0xf5, 0x48, 0xb7, 0x31, 0x39, 0x54, 0xd8, 0x7d, 0x42, 0x0a, 0x3c, 0x76, 0x8a, 0x4d, 0xed, 0xf8, + 0x8c, 0xaa, 0x91, 0xe9, 0x1b, 0x4b, 0xd3, 0xfb, 0x15, 0x44, 0x15, 0x3e, 0x13, 0xa5, 0xf0, 0x88, + 0x0a, 0x11, 0x15, 0x75, 0x47, 0xa4, 0x11, 0x93, 0x96, 0x4e, 0xc7, 0xc9, 0xc4, 0xc4, 0x8e, 0xb5, + 0xbe, 0xd2, 0xd5, 0xde, 0xc7, 0xfc, 0xd8, 0x2c, 0x4d, 0x36, 0xb1, 0xbb, 0x9c, 0x9b, 0x9e, 0x15, + 0x62, 0x62, 0xc7, 0x7e, 0xc2, 0x66, 0x06, 0x52, 0xa7, 0x4a, 0x77, 0x80, 0xc5, 0xa7, 0x21, 0xef, + 0x73, 0xac, 0xa8, 0x02, 0x99, 0x1e, 0xb6, 0x2c, 0xe5, 0x04, 0x53, 0x3f, 0x9c, 0x93, 0x9c, 0x47, + 0xb1, 0x08, 0x0b, 0x7e, 0x67, 0x2a, 0x7e, 0x1c, 0x77, 0x25, 0x89, 0x9f, 0x24, 0x92, 0xa7, 0xd8, + 0xa4, 0xd3, 0xe6, 0x92, 0xfc, 0x11, 0x5d, 0x86, 0x02, 0x1d, 0xb2, 0xec, 0xbc, 0x27, 0xce, 0x3a, + 0x29, 0x2d, 0x50, 0xe2, 0x11, 0x67, 0x5a, 0x85, 0xbc, 0xb1, 0x6e, 0xb8, 0x2c, 0x09, 0xca, 0x02, + 0xc6, 0xba, 0xe1, 0x30, 0x3c, 0x09, 0x0b, 0x64, 0x7e, 0x2e, 0x47, 0x92, 0x7e, 0x24, 0x4f, 0x68, + 0x9c, 0x45, 0xfc, 0xbd, 0x00, 0xe5, 0x51, 0x07, 0x8c, 0x6e, 0x43, 0x92, 0xc4, 0x22, 0x1e, 0x56, + 0xaa, 0x6b, 0x2c, 0x50, 0xad, 0x39, 0x81, 0x6a, 0xad, 0xe9, 0x04, 0xaa, 0xcd, 0xec, 0xa7, 0x9f, + 0xaf, 0xc6, 0x3e, 0xfe, 0xcb, 0x6a, 0x5c, 0xa2, 0x12, 0xe8, 0x3c, 0xf1, 0x95, 0x8a, 0xd6, 0x97, + 0x35, 0x95, 0x0e, 0x39, 0x47, 0x1c, 0xa1, 0xa2, 0xf5, 0xb7, 0x55, 0xb4, 0x03, 0xe5, 0xb6, 0xde, + 0xb7, 0x70, 0xdf, 0x1a, 0x58, 0x32, 0x0b, 0x84, 0x3c, 0x98, 0x04, 0xdc, 0x21, 0x0b, 0xaf, 0x35, + 0x87, 0x73, 0x9f, 0x32, 0x4a, 0xa5, 0x76, 0x90, 0x40, 0xdc, 0xea, 0xa9, 0xd2, 0xd5, 0x54, 0xc5, + 0xd6, 0x4d, 0xab, 0x92, 0xbc, 0x94, 0x08, 0xf5, 0x87, 0x47, 0x0e, 0xcb, 0xa1, 0xa1, 0x2a, 0x36, + 0xde, 0x4c, 0x92, 0xe1, 0x4a, 0x3e, 0x49, 0xf4, 0x14, 0x94, 0x14, 0xc3, 0x90, 0x2d, 0x5b, 0xb1, + 0xb1, 0xdc, 0x3a, 0xb3, 0xb1, 0x45, 0x03, 0xcd, 0x82, 0x54, 0x50, 0x0c, 0xe3, 0x80, 0x50, 0x37, + 0x09, 0x11, 0x5d, 0x81, 0x22, 0x89, 0x49, 0x9a, 0xd2, 0x95, 0x3b, 0x58, 0x3b, 0xe9, 0xd8, 0x34, + 0xa4, 0x24, 0xa4, 0x02, 0xa7, 0x36, 0x28, 0x51, 0x54, 0xdd, 0x1d, 0xa7, 0xf1, 0x08, 0x21, 0x48, + 0xaa, 0x8a, 0xad, 0xd0, 0x95, 0x5c, 0x90, 0xe8, 0x6f, 0x42, 0x33, 0x14, 0xbb, 0xc3, 0xd7, 0x87, + 0xfe, 0x46, 0xe7, 0x20, 0xcd, 0xd5, 0x26, 0xa8, 0x5a, 0xfe, 0x84, 0x96, 0x21, 0x65, 0x98, 0xfa, + 0x29, 0xa6, 0x5b, 0x97, 0x95, 0xd8, 0x83, 0xf8, 0x81, 0x00, 0x8b, 0x63, 0x91, 0x8b, 0xe8, 0xed, + 0x28, 0x56, 0xc7, 0xf9, 0x16, 0xf9, 0x8d, 0x5e, 0x20, 0x7a, 0x15, 0x15, 0x9b, 0x3c, 0xda, 0x57, + 0xc6, 0x97, 0xba, 0x41, 0xdf, 0xf3, 0xa5, 0xe1, 0xdc, 0xe8, 0x1e, 0x94, 0xbb, 0x8a, 0x65, 0xcb, + 0xcc, 0xfb, 0xcb, 0xbe, 0xc8, 0xff, 0xc4, 0xd8, 0x22, 0xb3, 0x58, 0x41, 0x0c, 0x9a, 0x2b, 0x29, + 0x12, 0x51, 0x8f, 0x8a, 0x0e, 0x61, 0xb9, 0x75, 0xf6, 0xbe, 0xd2, 0xb7, 0xb5, 0x3e, 0x96, 0xc7, + 0x76, 0x6d, 0x3c, 0x95, 0xb8, 0xaf, 0x59, 0x2d, 0xdc, 0x51, 0x4e, 0x35, 0xdd, 0x19, 0xd6, 0x92, + 0x2b, 0xef, 0xee, 0xa8, 0x25, 0x4a, 0x50, 0x0c, 0x86, 0x5d, 0x54, 0x04, 0xc1, 0x1e, 0xf2, 0xf9, + 0x0b, 0xf6, 0x10, 0x3d, 0x0f, 0x49, 0x32, 0x47, 0x3a, 0xf7, 0x62, 0xc8, 0x87, 0xb8, 0x5c, 0xf3, + 0xcc, 0xc0, 0x12, 0xe5, 0x14, 0x45, 0xf7, 0x34, 0xb8, 0xa1, 0x78, 0x54, 0xab, 0x78, 0x0d, 0x4a, + 0x23, 0x71, 0xd6, 0xb7, 0x7d, 0x71, 0xff, 0xf6, 0x89, 0x25, 0x28, 0x04, 0x02, 0xaa, 0x78, 0x0e, + 0x96, 0xc3, 0xe2, 0xa3, 0xd8, 0x71, 0xe9, 0x81, 0x38, 0x87, 0x6e, 0x41, 0xd6, 0x0d, 0x90, 0xec, + 0x34, 0x9e, 0x1f, 0x9b, 0x85, 0xc3, 0x2c, 0xb9, 0xac, 0xe4, 0x18, 0x12, 0xab, 0xa6, 0xe6, 0x20, + 0xd0, 0x81, 0x67, 0x14, 0xc3, 0x68, 0x28, 0x56, 0x47, 0x7c, 0x1b, 0x2a, 0x51, 0xc1, 0x6f, 0x64, + 0x1a, 0x49, 0xd7, 0x0a, 0xcf, 0x41, 0xfa, 0x58, 0x37, 0x7b, 0x8a, 0x4d, 0x95, 0x15, 0x24, 0xfe, + 0x44, 0xac, 0x93, 0x05, 0xc2, 0x04, 0x25, 0xb3, 0x07, 0x51, 0x86, 0xf3, 0x91, 0x01, 0x90, 0x88, + 0x68, 0x7d, 0x15, 0xb3, 0xf5, 0x2c, 0x48, 0xec, 0xc1, 0x53, 0xc4, 0x06, 0xcb, 0x1e, 0xc8, 0x67, + 0x2d, 0x3a, 0x57, 0xaa, 0x3f, 0x27, 0xf1, 0x27, 0xf1, 0xd7, 0x09, 0x38, 0x17, 0x1e, 0x06, 0xd1, + 0x25, 0x58, 0xe8, 0x29, 0x43, 0xd9, 0x1e, 0xf2, 0xb3, 0xcc, 0xb6, 0x03, 0x7a, 0xca, 0xb0, 0x39, + 0x64, 0x07, 0xb9, 0x0c, 0x09, 0x7b, 0x68, 0x55, 0x84, 0x4b, 0x89, 0xab, 0x0b, 0x12, 0xf9, 0x89, + 0x0e, 0x61, 0xb1, 0xab, 0xb7, 0x95, 0xae, 0xec, 0xb3, 0x78, 0x6e, 0xec, 0x97, 0xc7, 0x16, 0x9b, + 0x05, 0x34, 0xac, 0x8e, 0x19, 0x7d, 0x89, 0xea, 0xd8, 0x71, 0x2d, 0xff, 0x1b, 0xb2, 0x7a, 0xdf, + 0x1e, 0xa5, 0x02, 0x9e, 0xc2, 0xf1, 0xd9, 0xe9, 0xb9, 0x7d, 0xf6, 0xf3, 0xb0, 0xdc, 0xc7, 0x43, + 0xdb, 0x37, 0x46, 0x66, 0x38, 0x19, 0xba, 0x17, 0x88, 0xbc, 0xf3, 0xbe, 0x4f, 0x6c, 0x08, 0x5d, + 0xa3, 0x99, 0x85, 0xa1, 0x5b, 0xd8, 0x94, 0x15, 0x55, 0x35, 0xb1, 0x65, 0xd1, 0xcc, 0x76, 0x81, + 0xa6, 0x0b, 0x94, 0xbe, 0xc1, 0xc8, 0xe2, 0xcf, 0xfc, 0x7b, 0x15, 0xcc, 0x24, 0xf8, 0x4e, 0xc4, + 0xbd, 0x9d, 0x38, 0x80, 0x65, 0x2e, 0xaf, 0x06, 0x36, 0x43, 0x98, 0xd5, 0xf3, 0x20, 0x47, 0x7c, + 0x86, 0x7d, 0x48, 0x3c, 0xda, 0x3e, 0x38, 0xde, 0x36, 0xe9, 0xf3, 0xb6, 0xff, 0x66, 0x7b, 0xf3, + 0xaa, 0x1b, 0x45, 0xbc, 0x34, 0x2d, 0x34, 0x8a, 0x78, 0xf3, 0x12, 0x02, 0xee, 0xed, 0xe7, 0x71, + 0xa8, 0x46, 0xe7, 0x65, 0xa1, 0xaa, 0x9e, 0x81, 0x45, 0x77, 0x2e, 0xee, 0xf8, 0xd8, 0xa9, 0x2f, + 0xbb, 0x2f, 0xf8, 0x00, 0x23, 0xa3, 0xe2, 0x15, 0x28, 0x8e, 0x64, 0x8d, 0x6c, 0x17, 0x0a, 0xa7, + 0xfe, 0xef, 0x8b, 0x3f, 0x4e, 0xb8, 0x5e, 0x35, 0x90, 0xda, 0x85, 0x58, 0xde, 0x1b, 0xb0, 0xa4, + 0xe2, 0xb6, 0xa6, 0x7e, 0x55, 0xc3, 0x5b, 0xe4, 0xd2, 0xdf, 0xd9, 0xdd, 0x0c, 0x76, 0xf7, 0xc7, + 0x3c, 0x64, 0x25, 0x6c, 0x19, 0x24, 0xa5, 0x43, 0x9b, 0x90, 0xc3, 0xc3, 0x36, 0x36, 0x6c, 0x27, + 0x0b, 0x0e, 0xaf, 0x26, 0x18, 0x77, 0xdd, 0xe1, 0x24, 0xb5, 0xb1, 0x2b, 0x86, 0x6e, 0x72, 0x18, + 0x24, 0x1a, 0xd1, 0xe0, 0xe2, 0x7e, 0x1c, 0xe4, 0x05, 0x07, 0x07, 0x49, 0x44, 0x96, 0xc2, 0x4c, + 0x6a, 0x04, 0x08, 0xb9, 0xc9, 0x81, 0x90, 0xe4, 0x94, 0x8f, 0x05, 0x90, 0x90, 0x5a, 0x00, 0x09, + 0x49, 0x4d, 0x99, 0x66, 0x04, 0x14, 0xf2, 0x82, 0x03, 0x85, 0xa4, 0xa7, 0x8c, 0x78, 0x04, 0x0b, + 0x79, 0x3d, 0x88, 0x85, 0x64, 0x22, 0x42, 0x9b, 0x23, 0x3d, 0x11, 0x0c, 0x79, 0xc5, 0x07, 0x86, + 0x64, 0x23, 0x51, 0x08, 0xa6, 0x28, 0x04, 0x0d, 0x79, 0x2d, 0x80, 0x86, 0xe4, 0xa6, 0xac, 0xc3, + 0x04, 0x38, 0x64, 0xcb, 0x0f, 0x87, 0x40, 0x24, 0xaa, 0xc2, 0xf7, 0x3d, 0x0a, 0x0f, 0x79, 0xc9, + 0xc5, 0x43, 0xf2, 0x91, 0xc0, 0x0e, 0x9f, 0xcb, 0x28, 0x20, 0xb2, 0x37, 0x06, 0x88, 0x30, 0x00, + 0xe3, 0xa9, 0x48, 0x15, 0x53, 0x10, 0x91, 0xbd, 0x31, 0x44, 0xa4, 0x30, 0x45, 0xe1, 0x14, 0x48, + 0xe4, 0x7b, 0xe1, 0x90, 0x48, 0x34, 0x68, 0xc1, 0x87, 0x39, 0x1b, 0x26, 0x22, 0x47, 0x60, 0x22, + 0xa5, 0xc8, 0xfa, 0x9d, 0xa9, 0x9f, 0x19, 0x14, 0x39, 0x0c, 0x01, 0x45, 0x18, 0x7c, 0x71, 0x35, + 0x52, 0xf9, 0x0c, 0xa8, 0xc8, 0x61, 0x08, 0x2a, 0xb2, 0x38, 0x55, 0xed, 0x54, 0x58, 0xe4, 0x6e, + 0x10, 0x16, 0x41, 0x53, 0xce, 0x58, 0x24, 0x2e, 0xd2, 0x8a, 0xc2, 0x45, 0x18, 0x76, 0xf1, 0x6c, + 0xa4, 0xc6, 0x39, 0x80, 0x91, 0xbd, 0x31, 0x60, 0x64, 0x79, 0x8a, 0xa5, 0xcd, 0x8a, 0x8c, 0x5c, + 0x23, 0x19, 0xc5, 0x88, 0xab, 0x26, 0xc9, 0x3d, 0x36, 0x4d, 0xdd, 0xe4, 0x18, 0x07, 0x7b, 0x10, + 0xaf, 0x92, 0x4a, 0xd9, 0x73, 0xcb, 0x13, 0x50, 0x14, 0x5a, 0x44, 0xf9, 0x5c, 0xb1, 0xf8, 0x9b, + 0xb8, 0x27, 0x4b, 0x0b, 0x4c, 0x7f, 0x95, 0x9d, 0xe3, 0x55, 0xb6, 0x0f, 0x5b, 0x11, 0x82, 0xd8, + 0xca, 0x2a, 0xe4, 0x49, 0x71, 0x34, 0x02, 0x9b, 0x28, 0x86, 0x0b, 0x9b, 0x5c, 0x87, 0x45, 0x9a, + 0x04, 0x30, 0x04, 0x86, 0x47, 0xd6, 0x24, 0x8d, 0xac, 0x25, 0xf2, 0x82, 0xad, 0x02, 0x0b, 0xb1, + 0xcf, 0xc1, 0x92, 0x8f, 0xd7, 0x2d, 0xba, 0x18, 0x86, 0x50, 0x76, 0xb9, 0x37, 0x78, 0xf5, 0xf5, + 0xbb, 0xb8, 0xb7, 0x42, 0x1e, 0xde, 0x12, 0x06, 0x8d, 0xc4, 0xbf, 0x26, 0x68, 0x44, 0xf8, 0xca, + 0xd0, 0x88, 0xbf, 0x88, 0x4c, 0x04, 0x8b, 0xc8, 0xbf, 0xc7, 0xbd, 0x3d, 0x71, 0x81, 0x8e, 0xb6, + 0xae, 0x62, 0x5e, 0xd6, 0xd1, 0xdf, 0x24, 0xcd, 0xea, 0xea, 0x27, 0xbc, 0x78, 0x23, 0x3f, 0x09, + 0x97, 0x1b, 0x3b, 0x73, 0x3c, 0x34, 0xba, 0x15, 0x21, 0xcb, 0x5d, 0x78, 0x45, 0x58, 0x86, 0xc4, + 0x43, 0xcc, 0x22, 0xdd, 0x82, 0x44, 0x7e, 0x12, 0x3e, 0x6a, 0x64, 0x3c, 0x07, 0x61, 0x0f, 0xe8, + 0x36, 0xe4, 0x68, 0xbb, 0x46, 0xd6, 0x0d, 0x8b, 0x07, 0xa4, 0x40, 0xba, 0xc6, 0xba, 0x32, 0x6b, + 0xfb, 0x84, 0x67, 0xcf, 0xb0, 0xa4, 0xac, 0xc1, 0x7f, 0xf9, 0x92, 0xa6, 0x5c, 0x20, 0x69, 0xba, + 0x00, 0x39, 0x32, 0x7a, 0xcb, 0x50, 0xda, 0x98, 0x46, 0x96, 0x9c, 0xe4, 0x11, 0xc4, 0x07, 0x80, + 0xc6, 0xe3, 0x24, 0x6a, 0x40, 0x1a, 0x9f, 0xe2, 0xbe, 0xcd, 0x72, 0xca, 0xfc, 0xfa, 0xb9, 0xf1, + 0xba, 0x91, 0xbc, 0xde, 0xac, 0x90, 0x45, 0xfe, 0xdb, 0xe7, 0xab, 0x65, 0xc6, 0xfd, 0xac, 0xde, + 0xd3, 0x6c, 0xdc, 0x33, 0xec, 0x33, 0x89, 0xcb, 0x8b, 0x7f, 0x16, 0xa0, 0x34, 0x12, 0x3f, 0x43, + 0xd7, 0xd6, 0x31, 0x79, 0xc1, 0x07, 0x2c, 0xcd, 0xb6, 0xde, 0x17, 0x01, 0x4e, 0x14, 0x4b, 0x7e, + 0x4f, 0xe9, 0xdb, 0x58, 0xe5, 0x8b, 0x9e, 0x3b, 0x51, 0xac, 0x37, 0x29, 0x81, 0xec, 0x3a, 0x79, + 0x3d, 0xb0, 0xb0, 0xca, 0x21, 0xae, 0xcc, 0x89, 0x62, 0x1d, 0x5a, 0x58, 0xf5, 0xcd, 0x32, 0xf3, + 0x68, 0xb3, 0x0c, 0xae, 0x71, 0x76, 0x64, 0x8d, 0x7d, 0x75, 0x7f, 0xce, 0x5f, 0xf7, 0xa3, 0x2a, + 0x64, 0x0d, 0x53, 0xd3, 0x4d, 0xcd, 0x3e, 0xa3, 0x1b, 0x93, 0x90, 0xdc, 0x67, 0x74, 0x19, 0x0a, + 0x3d, 0xdc, 0x33, 0x74, 0xbd, 0x2b, 0x33, 0x67, 0x93, 0xa7, 0xa2, 0x0b, 0x9c, 0x58, 0xa7, 0x3e, + 0xe7, 0x43, 0xc1, 0x3b, 0x7d, 0x1e, 0xbe, 0xf3, 0xf5, 0x2e, 0xef, 0x4a, 0xc8, 0xf2, 0xfa, 0x28, + 0x64, 0x12, 0x23, 0xeb, 0xeb, 0x3e, 0x7f, 0x5b, 0x0b, 0x2c, 0xfe, 0x90, 0x82, 0xbe, 0xc1, 0xdc, + 0x08, 0x1d, 0xf8, 0x2b, 0xb3, 0x01, 0x75, 0x0a, 0x8e, 0x39, 0xcf, 0xea, 0x3d, 0xbc, 0x0a, 0x8e, + 0x91, 0x2d, 0xf4, 0x16, 0x3c, 0x3e, 0xe2, 0xd9, 0x5c, 0xd5, 0xc2, 0xac, 0x0e, 0xee, 0xb1, 0xa0, + 0x83, 0x73, 0x54, 0x7b, 0x8b, 0x95, 0x78, 0xc4, 0x33, 0xb7, 0x0d, 0xc5, 0x60, 0x9a, 0x17, 0xba, + 0xfd, 0x97, 0xa1, 0x60, 0x62, 0x5b, 0xd1, 0xfa, 0x72, 0xa0, 0x26, 0x5d, 0x60, 0x44, 0x8e, 0xff, + 0xee, 0xc3, 0x63, 0xa1, 0xe9, 0x1e, 0x7a, 0x11, 0x72, 0x5e, 0xa6, 0xc8, 0x56, 0x75, 0x02, 0x92, + 0xe7, 0xf1, 0x8a, 0xbf, 0x8d, 0x7b, 0x2a, 0x83, 0xd8, 0x60, 0x1d, 0xd2, 0x26, 0xb6, 0x06, 0x5d, + 0x86, 0xd6, 0x15, 0xd7, 0x9f, 0x9b, 0x2d, 0x51, 0x24, 0xd4, 0x41, 0xd7, 0x96, 0xb8, 0xb0, 0xf8, + 0x00, 0xd2, 0x8c, 0x82, 0xf2, 0x90, 0x39, 0xdc, 0xbd, 0xb7, 0xbb, 0xf7, 0xe6, 0x6e, 0x39, 0x86, + 0x00, 0xd2, 0x1b, 0xb5, 0x5a, 0x7d, 0xbf, 0x59, 0x8e, 0xa3, 0x1c, 0xa4, 0x36, 0x36, 0xf7, 0xa4, + 0x66, 0x59, 0x20, 0x64, 0xa9, 0xfe, 0x7a, 0xbd, 0xd6, 0x2c, 0x27, 0xd0, 0x22, 0x14, 0xd8, 0x6f, + 0xf9, 0xee, 0x9e, 0x74, 0x7f, 0xa3, 0x59, 0x4e, 0xfa, 0x48, 0x07, 0xf5, 0xdd, 0xad, 0xba, 0x54, + 0x4e, 0x89, 0xff, 0x05, 0xe7, 0x23, 0x53, 0x4b, 0x0f, 0xf8, 0x8b, 0xfb, 0x80, 0x3f, 0xf1, 0xa7, + 0x02, 0x54, 0xa3, 0xf3, 0x45, 0xf4, 0xfa, 0xc8, 0xc4, 0xd7, 0xe7, 0x48, 0x36, 0x47, 0x66, 0x8f, + 0xae, 0x40, 0xd1, 0xc4, 0xc7, 0xd8, 0x6e, 0x77, 0x58, 0xfe, 0xca, 0x02, 0x66, 0x41, 0x2a, 0x70, + 0x2a, 0x15, 0xb2, 0x18, 0xdb, 0x3b, 0xb8, 0x6d, 0xcb, 0xcc, 0x17, 0x31, 0xa3, 0xcb, 0x11, 0x36, + 0x42, 0x3d, 0x60, 0x44, 0xf1, 0xed, 0xb9, 0xd6, 0x32, 0x07, 0x29, 0xa9, 0xde, 0x94, 0xde, 0x2a, + 0x27, 0x10, 0x82, 0x22, 0xfd, 0x29, 0x1f, 0xec, 0x6e, 0xec, 0x1f, 0x34, 0xf6, 0xc8, 0x5a, 0x2e, + 0x41, 0xc9, 0x59, 0x4b, 0x87, 0x98, 0x12, 0xff, 0x20, 0xc0, 0xe3, 0x11, 0xd9, 0x2e, 0xba, 0x0d, + 0x60, 0x0f, 0x65, 0x13, 0xb7, 0x75, 0x53, 0x8d, 0x36, 0xb2, 0xe6, 0x50, 0xa2, 0x1c, 0x52, 0xce, + 0xe6, 0xbf, 0xac, 0x09, 0x78, 0x31, 0x7a, 0x99, 0x2b, 0x25, 0xb3, 0x72, 0x8e, 0xda, 0xc5, 0x10, + 0x58, 0x14, 0xb7, 0x89, 0x62, 0xba, 0xb6, 0x54, 0x31, 0xe5, 0x47, 0xf7, 0xc3, 0x9c, 0xca, 0x8c, + 0xdd, 0x9a, 0xf9, 0xdc, 0x49, 0xea, 0xd1, 0xdc, 0x89, 0xf8, 0x8b, 0x84, 0x7f, 0x61, 0x83, 0xc9, + 0xfd, 0x1e, 0xa4, 0x2d, 0x5b, 0xb1, 0x07, 0x16, 0x37, 0xb8, 0x17, 0x67, 0xad, 0x14, 0xd6, 0x9c, + 0x1f, 0x07, 0x54, 0x5c, 0xe2, 0x6a, 0xbe, 0x5b, 0x6f, 0x4b, 0xbc, 0x05, 0xc5, 0xe0, 0xe2, 0x44, + 0x1f, 0x19, 0xcf, 0xe7, 0x08, 0xe2, 0x1d, 0x2f, 0xff, 0xf2, 0x81, 0x96, 0xe3, 0x80, 0x60, 0x3c, + 0x0c, 0x10, 0xfc, 0x65, 0x1c, 0x9e, 0x98, 0x50, 0x2f, 0xa1, 0x37, 0x46, 0xf6, 0xf9, 0xa5, 0x79, + 0xaa, 0xad, 0x35, 0x46, 0x0b, 0xee, 0xb4, 0x78, 0x13, 0x16, 0xfc, 0xf4, 0xd9, 0x26, 0xf9, 0xa3, + 0x84, 0xe7, 0xf3, 0x83, 0xc8, 0xe5, 0xd7, 0x96, 0x68, 0x8e, 0xd8, 0x99, 0x30, 0xa7, 0x9d, 0x85, + 0x26, 0x0b, 0x89, 0x6f, 0x2e, 0x59, 0x48, 0x3e, 0x62, 0xb2, 0xe0, 0x3f, 0x70, 0xa9, 0xe0, 0x81, + 0x1b, 0x8b, 0xeb, 0xe9, 0x90, 0xb8, 0xfe, 0x16, 0x80, 0xaf, 0xa1, 0xb9, 0x0c, 0x29, 0x53, 0x1f, + 0xf4, 0x55, 0x6a, 0x26, 0x29, 0x89, 0x3d, 0xa0, 0x5b, 0x90, 0x22, 0xe6, 0xe6, 0x2c, 0xe6, 0xb8, + 0xe7, 0x25, 0xe6, 0xe2, 0xc3, 0x8c, 0x19, 0xb7, 0xa8, 0x01, 0x1a, 0x6f, 0x2a, 0x45, 0x7c, 0xe2, + 0x95, 0xe0, 0x27, 0x9e, 0x8c, 0x6c, 0x4f, 0x85, 0x7f, 0xea, 0x7d, 0x48, 0x51, 0xf3, 0x20, 0xf9, + 0x0d, 0x6d, 0x8c, 0xf2, 0x82, 0x99, 0xfc, 0x46, 0xdf, 0x07, 0x50, 0x6c, 0xdb, 0xd4, 0x5a, 0x03, + 0xef, 0x03, 0xab, 0xe1, 0xe6, 0xb5, 0xe1, 0xf0, 0x6d, 0x5e, 0xe0, 0x76, 0xb6, 0xec, 0x89, 0xfa, + 0x6c, 0xcd, 0xa7, 0x50, 0xdc, 0x85, 0x62, 0x50, 0xd6, 0x29, 0xf1, 0xd8, 0x18, 0x82, 0x25, 0x1e, + 0xab, 0xd8, 0x79, 0x89, 0xe7, 0x16, 0x88, 0x09, 0xd6, 0x03, 0xa7, 0x0f, 0xe2, 0x3f, 0xe2, 0xb0, + 0xe0, 0xb7, 0xce, 0xff, 0xb4, 0x2a, 0x49, 0xfc, 0x30, 0x0e, 0x59, 0x77, 0xf2, 0x11, 0x0d, 0x68, + 0x6f, 0xed, 0x04, 0x7f, 0xbb, 0x95, 0x75, 0xb4, 0x13, 0x6e, 0x9f, 0xfc, 0x8e, 0x9b, 0x50, 0x45, + 0x81, 0xda, 0xfe, 0x95, 0x76, 0xae, 0x0a, 0xf0, 0xfc, 0xf1, 0x27, 0x7c, 0x1c, 0x24, 0x93, 0x40, + 0xff, 0x03, 0x69, 0xa5, 0xed, 0x42, 0xf9, 0xc5, 0x10, 0x6c, 0xd7, 0x61, 0x5d, 0x6b, 0x0e, 0x37, + 0x28, 0xa7, 0xc4, 0x25, 0xf8, 0xa8, 0x04, 0xb7, 0xcf, 0xfe, 0x2a, 0xd1, 0xcb, 0x78, 0x82, 0x6e, + 0xb3, 0x08, 0x70, 0xb8, 0x7b, 0x7f, 0x6f, 0x6b, 0xfb, 0xee, 0x76, 0x7d, 0x8b, 0xa7, 0x54, 0x5b, + 0x5b, 0xf5, 0xad, 0xb2, 0x40, 0xf8, 0xa4, 0xfa, 0xfd, 0xbd, 0xa3, 0xfa, 0x56, 0x39, 0x21, 0xde, + 0x81, 0x9c, 0xeb, 0x7a, 0x50, 0x05, 0x32, 0x4e, 0x5b, 0x22, 0xce, 0x1d, 0x00, 0xef, 0x32, 0x2d, + 0x43, 0xca, 0xd0, 0xdf, 0xe3, 0x5d, 0xe6, 0x84, 0xc4, 0x1e, 0x44, 0x15, 0x4a, 0x23, 0x7e, 0x0b, + 0xdd, 0x81, 0x8c, 0x31, 0x68, 0xc9, 0x8e, 0xd1, 0x8e, 0x34, 0x71, 0x1c, 0xa4, 0x61, 0xd0, 0xea, + 0x6a, 0xed, 0x7b, 0xf8, 0xcc, 0x59, 0x26, 0x63, 0xd0, 0xba, 0xc7, 0x6c, 0x9b, 0x7d, 0x45, 0xf0, + 0x7f, 0xe5, 0x14, 0xb2, 0xce, 0x51, 0x45, 0xff, 0x0b, 0x39, 0xd7, 0x25, 0xba, 0x57, 0x6f, 0x22, + 0x7d, 0x29, 0x57, 0xef, 0x89, 0xa0, 0xeb, 0xb0, 0x68, 0x69, 0x27, 0x7d, 0xa7, 0x85, 0xc5, 0x90, + 0x3d, 0x81, 0x9e, 0x99, 0x12, 0x7b, 0xb1, 0xe3, 0xc0, 0x51, 0x24, 0x12, 0x96, 0x47, 0x7d, 0xc5, + 0xb7, 0x39, 0x80, 0x90, 0x88, 0x9d, 0x08, 0x8b, 0xd8, 0x1f, 0x08, 0x90, 0xf7, 0x35, 0xc6, 0xd0, + 0x7f, 0xfb, 0x1c, 0x57, 0x31, 0x24, 0xd4, 0xf8, 0x78, 0xbd, 0x5b, 0x1d, 0xc1, 0x89, 0x09, 0xf3, + 0x4f, 0x2c, 0xaa, 0x0f, 0xe9, 0xf4, 0xd7, 0x92, 0x73, 0xf7, 0xd7, 0x9e, 0x05, 0x64, 0xeb, 0xb6, + 0xd2, 0x95, 0x4f, 0x75, 0x5b, 0xeb, 0x9f, 0xc8, 0xcc, 0x34, 0x98, 0x9b, 0x29, 0xd3, 0x37, 0x47, + 0xf4, 0xc5, 0x3e, 0xb5, 0x92, 0x1f, 0xc4, 0x21, 0xeb, 0x96, 0x7d, 0xf3, 0x5e, 0xd2, 0x38, 0x07, + 0x69, 0x5e, 0xd9, 0xb0, 0x5b, 0x1a, 0xfc, 0x29, 0xb4, 0x91, 0x58, 0x85, 0x6c, 0x0f, 0xdb, 0x0a, + 0xf5, 0x99, 0x2c, 0x4c, 0xba, 0xcf, 0xd7, 0x5f, 0x82, 0xbc, 0xef, 0xbe, 0x0c, 0x71, 0xa3, 0xbb, + 0xf5, 0x37, 0xcb, 0xb1, 0x6a, 0xe6, 0xa3, 0x4f, 0x2e, 0x25, 0x76, 0xf1, 0x7b, 0xe4, 0x84, 0x49, + 0xf5, 0x5a, 0xa3, 0x5e, 0xbb, 0x57, 0x8e, 0x57, 0xf3, 0x1f, 0x7d, 0x72, 0x29, 0x23, 0x61, 0xda, + 0xf7, 0xb9, 0x7e, 0x0f, 0x4a, 0x23, 0x1b, 0x13, 0x3c, 0xd0, 0x08, 0x8a, 0x5b, 0x87, 0xfb, 0x3b, + 0xdb, 0xb5, 0x8d, 0x66, 0x5d, 0x3e, 0xda, 0x6b, 0xd6, 0xcb, 0x71, 0xf4, 0x38, 0x2c, 0xed, 0x6c, + 0xbf, 0xd6, 0x68, 0xca, 0xb5, 0x9d, 0xed, 0xfa, 0x6e, 0x53, 0xde, 0x68, 0x36, 0x37, 0x6a, 0xf7, + 0xca, 0xc2, 0xfa, 0xaf, 0xf2, 0x50, 0xda, 0xd8, 0xac, 0x6d, 0x93, 0xda, 0x4e, 0x6b, 0x2b, 0xd4, + 0x3d, 0xd4, 0x20, 0x49, 0x41, 0xe4, 0x89, 0x37, 0xa0, 0xab, 0x93, 0x1b, 0x83, 0xe8, 0x2e, 0xa4, + 0x28, 0xbe, 0x8c, 0x26, 0x5f, 0x89, 0xae, 0x4e, 0xe9, 0x14, 0x92, 0xc1, 0xd0, 0xe3, 0x34, 0xf1, + 0x8e, 0x74, 0x75, 0x72, 0xe3, 0x10, 0xed, 0x40, 0xc6, 0x81, 0xff, 0xa6, 0xdd, 0x36, 0xae, 0x4e, + 0xed, 0xc0, 0x91, 0xa9, 0x31, 0x98, 0x76, 0xf2, 0xf5, 0xe9, 0xea, 0x94, 0x96, 0x22, 0xda, 0x86, + 0x34, 0x47, 0x48, 0xa6, 0xdc, 0x1c, 0xae, 0x4e, 0xeb, 0xa4, 0x21, 0x09, 0x72, 0x1e, 0x00, 0x3e, + 0xfd, 0x52, 0x78, 0x75, 0x86, 0x6e, 0x29, 0x7a, 0x00, 0x85, 0x20, 0xea, 0x32, 0xdb, 0xed, 0xe4, + 0xea, 0x8c, 0x3d, 0x3b, 0xa2, 0x3f, 0x08, 0xc1, 0xcc, 0x76, 0x5b, 0xb9, 0x3a, 0x63, 0x0b, 0x0f, + 0xbd, 0x03, 0x8b, 0xe3, 0x10, 0xc9, 0xec, 0x97, 0x97, 0xab, 0x73, 0x34, 0xf5, 0x50, 0x0f, 0x50, + 0x08, 0xb4, 0x32, 0xc7, 0x5d, 0xe6, 0xea, 0x3c, 0x3d, 0x3e, 0xa4, 0x42, 0x69, 0x14, 0xae, 0x98, + 0xf5, 0x6e, 0x73, 0x75, 0xe6, 0x7e, 0x1f, 0xfb, 0x4a, 0xb0, 0x76, 0x9f, 0xf5, 0xae, 0x73, 0x75, + 0xe6, 0xf6, 0x1f, 0x3a, 0x04, 0xf0, 0xd5, 0x9e, 0x33, 0xdc, 0x7d, 0xae, 0xce, 0xd2, 0x08, 0x44, + 0x06, 0x2c, 0x85, 0x15, 0xa5, 0xf3, 0x5c, 0x85, 0xae, 0xce, 0xd5, 0x1f, 0x24, 0xf6, 0x1c, 0x2c, + 0x2f, 0x67, 0xbb, 0x1a, 0x5d, 0x9d, 0xb1, 0x51, 0xb8, 0x59, 0xff, 0xf4, 0x8b, 0x95, 0xf8, 0x67, + 0x5f, 0xac, 0xc4, 0xff, 0xfa, 0xc5, 0x4a, 0xfc, 0xe3, 0x2f, 0x57, 0x62, 0x9f, 0x7d, 0xb9, 0x12, + 0xfb, 0xd3, 0x97, 0x2b, 0xb1, 0xff, 0x7b, 0xe6, 0x44, 0xb3, 0x3b, 0x83, 0xd6, 0x5a, 0x5b, 0xef, + 0xdd, 0xf0, 0xff, 0x4b, 0x26, 0xec, 0x9f, 0x3b, 0xad, 0x34, 0x0d, 0xa8, 0x37, 0xff, 0x15, 0x00, + 0x00, 0xff, 0xff, 0xa5, 0x05, 0x49, 0xaf, 0xd9, 0x33, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -5662,6 +6066,184 @@ func (m *RequestApplySnapshotChunk) MarshalToSizedBuffer(dAtA []byte) (int, erro return len(dAtA) - i, nil } +func (m *RequestPrepareProposal) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RequestPrepareProposal) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RequestPrepareProposal) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ProposerAddress) > 0 { + i -= len(m.ProposerAddress) + copy(dAtA[i:], m.ProposerAddress) + i = encodeVarintTypes(dAtA, i, uint64(len(m.ProposerAddress))) + i-- + dAtA[i] = 0x42 + } + if len(m.NextValidatorsHash) > 0 { + i -= len(m.NextValidatorsHash) + copy(dAtA[i:], m.NextValidatorsHash) + i = encodeVarintTypes(dAtA, i, uint64(len(m.NextValidatorsHash))) + i-- + dAtA[i] = 0x3a + } + n25, err25 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Time):]) + if err25 != nil { + return 0, err25 + } + i -= n25 + i = encodeVarintTypes(dAtA, i, uint64(n25)) + i-- + dAtA[i] = 0x32 + if m.Height != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x28 + } + if len(m.ByzantineValidators) > 0 { + for iNdEx := len(m.ByzantineValidators) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ByzantineValidators[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } + { + size, err := m.LocalLastCommit.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Txs) > 0 { + for iNdEx := len(m.Txs) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Txs[iNdEx]) + copy(dAtA[i:], m.Txs[iNdEx]) + i = encodeVarintTypes(dAtA, i, uint64(len(m.Txs[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if m.MaxTxBytes != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.MaxTxBytes)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *RequestProcessProposal) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RequestProcessProposal) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RequestProcessProposal) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ProposerAddress) > 0 { + i -= len(m.ProposerAddress) + copy(dAtA[i:], m.ProposerAddress) + i = encodeVarintTypes(dAtA, i, uint64(len(m.ProposerAddress))) + i-- + dAtA[i] = 0x42 + } + if len(m.NextValidatorsHash) > 0 { + i -= len(m.NextValidatorsHash) + copy(dAtA[i:], m.NextValidatorsHash) + i = encodeVarintTypes(dAtA, i, uint64(len(m.NextValidatorsHash))) + i-- + dAtA[i] = 0x3a + } + n27, err27 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Time):]) + if err27 != nil { + return 0, err27 + } + i -= n27 + i = encodeVarintTypes(dAtA, i, uint64(n27)) + i-- + dAtA[i] = 0x32 + if m.Height != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x28 + } + if len(m.Hash) > 0 { + i -= len(m.Hash) + copy(dAtA[i:], m.Hash) + i = encodeVarintTypes(dAtA, i, uint64(len(m.Hash))) + i-- + dAtA[i] = 0x22 + } + if len(m.ByzantineValidators) > 0 { + for iNdEx := len(m.ByzantineValidators) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ByzantineValidators[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + { + size, err := m.ProposedLastCommit.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Txs) > 0 { + for iNdEx := len(m.Txs) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Txs[iNdEx]) + copy(dAtA[i:], m.Txs[iNdEx]) + i = encodeVarintTypes(dAtA, i, uint64(len(m.Txs[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + func (m *RequestExtendVote) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -5682,15 +6264,15 @@ func (m *RequestExtendVote) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.Vote != nil { - { - size, err := m.Vote.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) - } + if m.Height != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x10 + } + if len(m.Hash) > 0 { + i -= len(m.Hash) + copy(dAtA[i:], m.Hash) + i = encodeVarintTypes(dAtA, i, uint64(len(m.Hash))) i-- dAtA[i] = 0xa } @@ -5717,135 +6299,25 @@ func (m *RequestVerifyVoteExtension) MarshalToSizedBuffer(dAtA []byte) (int, err _ = i var l int _ = l - if m.Vote != nil { - { - size, err := m.Vote.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) - } + if len(m.VoteExtension) > 0 { + i -= len(m.VoteExtension) + copy(dAtA[i:], m.VoteExtension) + i = encodeVarintTypes(dAtA, i, uint64(len(m.VoteExtension))) i-- - dAtA[i] = 0xa + dAtA[i] = 0x22 } - return len(dAtA) - i, nil -} - -func (m *RequestPrepareProposal) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *RequestPrepareProposal) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *RequestPrepareProposal) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Votes) > 0 { - for iNdEx := len(m.Votes) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Votes[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - } - if m.BlockDataSize != 0 { - i = encodeVarintTypes(dAtA, i, uint64(m.BlockDataSize)) + if m.Height != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.Height)) i-- - dAtA[i] = 0x10 + dAtA[i] = 0x18 } - if len(m.BlockData) > 0 { - for iNdEx := len(m.BlockData) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.BlockData[iNdEx]) - copy(dAtA[i:], m.BlockData[iNdEx]) - i = encodeVarintTypes(dAtA, i, uint64(len(m.BlockData[iNdEx]))) - i-- - dAtA[i] = 0xa - } + if len(m.ValidatorAddress) > 0 { + i -= len(m.ValidatorAddress) + copy(dAtA[i:], m.ValidatorAddress) + i = encodeVarintTypes(dAtA, i, uint64(len(m.ValidatorAddress))) + i-- + dAtA[i] = 0x12 } - return len(dAtA) - i, nil -} - -func (m *RequestProcessProposal) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *RequestProcessProposal) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *RequestProcessProposal) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.ByzantineValidators) > 0 { - for iNdEx := len(m.ByzantineValidators) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.ByzantineValidators[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x2a - } - } - { - size, err := m.ProposedLastCommit.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x22 - if len(m.Txs) > 0 { - for iNdEx := len(m.Txs) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Txs[iNdEx]) - copy(dAtA[i:], m.Txs[iNdEx]) - i = encodeVarintTypes(dAtA, i, uint64(len(m.Txs[iNdEx]))) - i-- - dAtA[i] = 0x1a - } - } - { - size, err := m.Header.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 if len(m.Hash) > 0 { i -= len(m.Hash) copy(dAtA[i:], m.Hash) @@ -5876,6 +6348,40 @@ func (m *RequestFinalizeBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.ProposerAddress) > 0 { + i -= len(m.ProposerAddress) + copy(dAtA[i:], m.ProposerAddress) + i = encodeVarintTypes(dAtA, i, uint64(len(m.ProposerAddress))) + i-- + dAtA[i] = 0x42 + } + if len(m.NextValidatorsHash) > 0 { + i -= len(m.NextValidatorsHash) + copy(dAtA[i:], m.NextValidatorsHash) + i = encodeVarintTypes(dAtA, i, uint64(len(m.NextValidatorsHash))) + i-- + dAtA[i] = 0x3a + } + n29, err29 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Time):]) + if err29 != nil { + return 0, err29 + } + i -= n29 + i = encodeVarintTypes(dAtA, i, uint64(n29)) + i-- + dAtA[i] = 0x32 + if m.Height != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x28 + } + if len(m.Hash) > 0 { + i -= len(m.Hash) + copy(dAtA[i:], m.Hash) + i = encodeVarintTypes(dAtA, i, uint64(len(m.Hash))) + i-- + dAtA[i] = 0x22 + } if len(m.ByzantineValidators) > 0 { for iNdEx := len(m.ByzantineValidators) - 1; iNdEx >= 0; iNdEx-- { { @@ -5887,7 +6393,7 @@ func (m *RequestFinalizeBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintTypes(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x2a + dAtA[i] = 0x1a } } { @@ -5899,33 +6405,16 @@ func (m *RequestFinalizeBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintTypes(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x22 + dAtA[i] = 0x12 if len(m.Txs) > 0 { for iNdEx := len(m.Txs) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.Txs[iNdEx]) copy(dAtA[i:], m.Txs[iNdEx]) i = encodeVarintTypes(dAtA, i, uint64(len(m.Txs[iNdEx]))) i-- - dAtA[i] = 0x1a + dAtA[i] = 0xa } } - { - size, err := m.Header.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - if len(m.Hash) > 0 { - i -= len(m.Hash) - copy(dAtA[i:], m.Hash) - i = encodeVarintTypes(dAtA, i, uint64(len(m.Hash))) - i-- - dAtA[i] = 0xa - } return len(dAtA) - i, nil } @@ -7133,69 +7622,6 @@ func (m *ResponseApplySnapshotChunk) MarshalToSizedBuffer(dAtA []byte) (int, err return len(dAtA) - i, nil } -func (m *ResponseExtendVote) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ResponseExtendVote) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ResponseExtendVote) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.VoteExtension != nil { - { - size, err := m.VoteExtension.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *ResponseVerifyVoteExtension) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ResponseVerifyVoteExtension) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ResponseVerifyVoteExtension) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Result != 0 { - i = encodeVarintTypes(dAtA, i, uint64(m.Result)) - i-- - dAtA[i] = 0x8 - } - return len(dAtA) - i, nil -} - func (m *ResponsePrepareProposal) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -7216,11 +7642,63 @@ func (m *ResponsePrepareProposal) MarshalToSizedBuffer(dAtA []byte) (int, error) _ = i var l int _ = l - if len(m.BlockData) > 0 { - for iNdEx := len(m.BlockData) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.BlockData[iNdEx]) - copy(dAtA[i:], m.BlockData[iNdEx]) - i = encodeVarintTypes(dAtA, i, uint64(len(m.BlockData[iNdEx]))) + if m.ConsensusParamUpdates != nil { + { + size, err := m.ConsensusParamUpdates.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + if len(m.ValidatorUpdates) > 0 { + for iNdEx := len(m.ValidatorUpdates) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ValidatorUpdates[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } + if len(m.TxResults) > 0 { + for iNdEx := len(m.TxResults) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.TxResults[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if len(m.AppHash) > 0 { + i -= len(m.AppHash) + copy(dAtA[i:], m.AppHash) + i = encodeVarintTypes(dAtA, i, uint64(len(m.AppHash))) + i-- + dAtA[i] = 0x12 + } + if len(m.TxRecords) > 0 { + for iNdEx := len(m.TxRecords) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.TxRecords[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } i-- dAtA[i] = 0xa } @@ -7295,13 +7773,66 @@ func (m *ResponseProcessProposal) MarshalToSizedBuffer(dAtA []byte) (int, error) i-- dAtA[i] = 0x12 } - if m.Accept { + if m.Status != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.Status)) i-- - if m.Accept { - dAtA[i] = 1 - } else { - dAtA[i] = 0 - } + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *ResponseExtendVote) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ResponseExtendVote) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ResponseExtendVote) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.VoteExtension) > 0 { + i -= len(m.VoteExtension) + copy(dAtA[i:], m.VoteExtension) + i = encodeVarintTypes(dAtA, i, uint64(len(m.VoteExtension))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ResponseVerifyVoteExtension) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ResponseVerifyVoteExtension) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ResponseVerifyVoteExtension) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Status != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.Status)) i-- dAtA[i] = 0x8 } @@ -7439,6 +7970,48 @@ func (m *CommitInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *ExtendedCommitInfo) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ExtendedCommitInfo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ExtendedCommitInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Votes) > 0 { + for iNdEx := len(m.Votes) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Votes[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if m.Round != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.Round)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *Event) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -7660,6 +8233,41 @@ func (m *TxResult) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *TxRecord) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TxRecord) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TxRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Tx) > 0 { + i -= len(m.Tx) + copy(dAtA[i:], m.Tx) + i = encodeVarintTypes(dAtA, i, uint64(len(m.Tx))) + i-- + dAtA[i] = 0x12 + } + if m.Action != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.Action)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *Validator) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -7776,7 +8384,7 @@ func (m *VoteInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *Evidence) Marshal() (dAtA []byte, err error) { +func (m *ExtendedVoteInfo) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -7786,12 +8394,62 @@ func (m *Evidence) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *Evidence) MarshalTo(dAtA []byte) (int, error) { +func (m *ExtendedVoteInfo) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *Evidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *ExtendedVoteInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.VoteExtension) > 0 { + i -= len(m.VoteExtension) + copy(dAtA[i:], m.VoteExtension) + i = encodeVarintTypes(dAtA, i, uint64(len(m.VoteExtension))) + i-- + dAtA[i] = 0x1a + } + if m.SignedLastBlock { + i-- + if m.SignedLastBlock { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x10 + } + { + size, err := m.Validator.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *Misbehavior) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Misbehavior) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Misbehavior) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -7801,12 +8459,12 @@ func (m *Evidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x28 } - n62, err62 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Time):]) - if err62 != nil { - return 0, err62 + n63, err63 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Time):]) + if err63 != nil { + return 0, err63 } - i -= n62 - i = encodeVarintTypes(dAtA, i, uint64(n62)) + i -= n63 + i = encodeVarintTypes(dAtA, i, uint64(n63)) i-- dAtA[i] = 0x22 if m.Height != 0 { @@ -8372,53 +9030,42 @@ func (m *RequestApplySnapshotChunk) Size() (n int) { return n } -func (m *RequestExtendVote) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Vote != nil { - l = m.Vote.Size() - n += 1 + l + sovTypes(uint64(l)) - } - return n -} - -func (m *RequestVerifyVoteExtension) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Vote != nil { - l = m.Vote.Size() - n += 1 + l + sovTypes(uint64(l)) - } - return n -} - func (m *RequestPrepareProposal) Size() (n int) { if m == nil { return 0 } var l int _ = l - if len(m.BlockData) > 0 { - for _, b := range m.BlockData { + if m.MaxTxBytes != 0 { + n += 1 + sovTypes(uint64(m.MaxTxBytes)) + } + if len(m.Txs) > 0 { + for _, b := range m.Txs { l = len(b) n += 1 + l + sovTypes(uint64(l)) } } - if m.BlockDataSize != 0 { - n += 1 + sovTypes(uint64(m.BlockDataSize)) - } - if len(m.Votes) > 0 { - for _, e := range m.Votes { + l = m.LocalLastCommit.Size() + n += 1 + l + sovTypes(uint64(l)) + if len(m.ByzantineValidators) > 0 { + for _, e := range m.ByzantineValidators { l = e.Size() n += 1 + l + sovTypes(uint64(l)) } } + if m.Height != 0 { + n += 1 + sovTypes(uint64(m.Height)) + } + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.Time) + n += 1 + l + sovTypes(uint64(l)) + l = len(m.NextValidatorsHash) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + l = len(m.ProposerAddress) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } return n } @@ -8428,12 +9075,6 @@ func (m *RequestProcessProposal) Size() (n int) { } var l int _ = l - l = len(m.Hash) - if l > 0 { - n += 1 + l + sovTypes(uint64(l)) - } - l = m.Header.Size() - n += 1 + l + sovTypes(uint64(l)) if len(m.Txs) > 0 { for _, b := range m.Txs { l = len(b) @@ -8448,10 +9089,27 @@ func (m *RequestProcessProposal) Size() (n int) { n += 1 + l + sovTypes(uint64(l)) } } + l = len(m.Hash) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + if m.Height != 0 { + n += 1 + sovTypes(uint64(m.Height)) + } + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.Time) + n += 1 + l + sovTypes(uint64(l)) + l = len(m.NextValidatorsHash) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + l = len(m.ProposerAddress) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } return n } -func (m *RequestFinalizeBlock) Size() (n int) { +func (m *RequestExtendVote) Size() (n int) { if m == nil { return 0 } @@ -8461,8 +9119,42 @@ func (m *RequestFinalizeBlock) Size() (n int) { if l > 0 { n += 1 + l + sovTypes(uint64(l)) } - l = m.Header.Size() - n += 1 + l + sovTypes(uint64(l)) + if m.Height != 0 { + n += 1 + sovTypes(uint64(m.Height)) + } + return n +} + +func (m *RequestVerifyVoteExtension) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Hash) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + l = len(m.ValidatorAddress) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + if m.Height != 0 { + n += 1 + sovTypes(uint64(m.Height)) + } + l = len(m.VoteExtension) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + return n +} + +func (m *RequestFinalizeBlock) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l if len(m.Txs) > 0 { for _, b := range m.Txs { l = len(b) @@ -8477,6 +9169,23 @@ func (m *RequestFinalizeBlock) Size() (n int) { n += 1 + l + sovTypes(uint64(l)) } } + l = len(m.Hash) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + if m.Height != 0 { + n += 1 + sovTypes(uint64(m.Height)) + } + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.Time) + n += 1 + l + sovTypes(uint64(l)) + l = len(m.NextValidatorsHash) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + l = len(m.ProposerAddress) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } return n } @@ -9071,55 +9780,18 @@ func (m *ResponseApplySnapshotChunk) Size() (n int) { return n } -func (m *ResponseExtendVote) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.VoteExtension != nil { - l = m.VoteExtension.Size() - n += 1 + l + sovTypes(uint64(l)) - } - return n -} - -func (m *ResponseVerifyVoteExtension) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Result != 0 { - n += 1 + sovTypes(uint64(m.Result)) - } - return n -} - func (m *ResponsePrepareProposal) Size() (n int) { if m == nil { return 0 } var l int _ = l - if len(m.BlockData) > 0 { - for _, b := range m.BlockData { - l = len(b) + if len(m.TxRecords) > 0 { + for _, e := range m.TxRecords { + l = e.Size() n += 1 + l + sovTypes(uint64(l)) } } - return n -} - -func (m *ResponseProcessProposal) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Accept { - n += 2 - } l = len(m.AppHash) if l > 0 { n += 1 + l + sovTypes(uint64(l)) @@ -9143,6 +9815,63 @@ func (m *ResponseProcessProposal) Size() (n int) { return n } +func (m *ResponseProcessProposal) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Status != 0 { + n += 1 + sovTypes(uint64(m.Status)) + } + l = len(m.AppHash) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + if len(m.TxResults) > 0 { + for _, e := range m.TxResults { + l = e.Size() + n += 1 + l + sovTypes(uint64(l)) + } + } + if len(m.ValidatorUpdates) > 0 { + for _, e := range m.ValidatorUpdates { + l = e.Size() + n += 1 + l + sovTypes(uint64(l)) + } + } + if m.ConsensusParamUpdates != nil { + l = m.ConsensusParamUpdates.Size() + n += 1 + l + sovTypes(uint64(l)) + } + return n +} + +func (m *ResponseExtendVote) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.VoteExtension) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + return n +} + +func (m *ResponseVerifyVoteExtension) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Status != 0 { + n += 1 + sovTypes(uint64(m.Status)) + } + return n +} + func (m *ResponseFinalizeBlock) Size() (n int) { if m == nil { return 0 @@ -9199,6 +9928,24 @@ func (m *CommitInfo) Size() (n int) { return n } +func (m *ExtendedCommitInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Round != 0 { + n += 1 + sovTypes(uint64(m.Round)) + } + if len(m.Votes) > 0 { + for _, e := range m.Votes { + l = e.Size() + n += 1 + l + sovTypes(uint64(l)) + } + } + return n +} + func (m *Event) Size() (n int) { if m == nil { return 0 @@ -9299,6 +10046,22 @@ func (m *TxResult) Size() (n int) { return n } +func (m *TxRecord) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Action != 0 { + n += 1 + sovTypes(uint64(m.Action)) + } + l = len(m.Tx) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + return n +} + func (m *Validator) Size() (n int) { if m == nil { return 0 @@ -9343,7 +10106,25 @@ func (m *VoteInfo) Size() (n int) { return n } -func (m *Evidence) Size() (n int) { +func (m *ExtendedVoteInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Validator.Size() + n += 1 + l + sovTypes(uint64(l)) + if m.SignedLastBlock { + n += 2 + } + l = len(m.VoteExtension) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + return n +} + +func (m *Misbehavior) Size() (n int) { if m == nil { return 0 } @@ -10947,7 +11728,7 @@ func (m *RequestBeginBlock) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ByzantineValidators = append(m.ByzantineValidators, Evidence{}) + m.ByzantineValidators = append(m.ByzantineValidators, Misbehavior{}) if err := m.ByzantineValidators[len(m.ByzantineValidators)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } @@ -11691,178 +12472,6 @@ func (m *RequestApplySnapshotChunk) Unmarshal(dAtA []byte) error { } return nil } -func (m *RequestExtendVote) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: RequestExtendVote: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: RequestExtendVote: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Vote", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Vote == nil { - m.Vote = &types1.Vote{} - } - if err := m.Vote.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTypes(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTypes - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *RequestVerifyVoteExtension) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: RequestVerifyVoteExtension: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: RequestVerifyVoteExtension: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Vote", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Vote == nil { - m.Vote = &types1.Vote{} - } - if err := m.Vote.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTypes(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTypes - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *RequestPrepareProposal) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -11893,8 +12502,27 @@ func (m *RequestPrepareProposal) Unmarshal(dAtA []byte) error { } switch fieldNum { case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxTxBytes", wireType) + } + m.MaxTxBytes = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxTxBytes |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BlockData", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Txs", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -11921,31 +12549,12 @@ func (m *RequestPrepareProposal) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.BlockData = append(m.BlockData, make([]byte, postIndex-iNdEx)) - copy(m.BlockData[len(m.BlockData)-1], dAtA[iNdEx:postIndex]) + m.Txs = append(m.Txs, make([]byte, postIndex-iNdEx)) + copy(m.Txs[len(m.Txs)-1], dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field BlockDataSize", wireType) - } - m.BlockDataSize = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.BlockDataSize |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Votes", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field LocalLastCommit", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -11972,11 +12581,164 @@ func (m *RequestPrepareProposal) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Votes = append(m.Votes, &types1.Vote{}) - if err := m.Votes[len(m.Votes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.LocalLastCommit.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ByzantineValidators", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ByzantineValidators = append(m.ByzantineValidators, Misbehavior{}) + if err := m.ByzantineValidators[len(m.ByzantineValidators)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.Time, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NextValidatorsHash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NextValidatorsHash = append(m.NextValidatorsHash[:0], dAtA[iNdEx:postIndex]...) + if m.NextValidatorsHash == nil { + m.NextValidatorsHash = []byte{} + } + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProposerAddress", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProposerAddress = append(m.ProposerAddress[:0], dAtA[iNdEx:postIndex]...) + if m.ProposerAddress == nil { + m.ProposerAddress = []byte{} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) @@ -12027,6 +12789,412 @@ func (m *RequestProcessProposal) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: RequestProcessProposal: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Txs", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Txs = append(m.Txs, make([]byte, postIndex-iNdEx)) + copy(m.Txs[len(m.Txs)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProposedLastCommit", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProposedLastCommit.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ByzantineValidators", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ByzantineValidators = append(m.ByzantineValidators, Misbehavior{}) + if err := m.ByzantineValidators[len(m.ByzantineValidators)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Hash = append(m.Hash[:0], dAtA[iNdEx:postIndex]...) + if m.Hash == nil { + m.Hash = []byte{} + } + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.Time, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NextValidatorsHash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NextValidatorsHash = append(m.NextValidatorsHash[:0], dAtA[iNdEx:postIndex]...) + if m.NextValidatorsHash == nil { + m.NextValidatorsHash = []byte{} + } + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProposerAddress", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProposerAddress = append(m.ProposerAddress[:0], dAtA[iNdEx:postIndex]...) + if m.ProposerAddress == nil { + m.ProposerAddress = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RequestExtendVote) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RequestExtendVote: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RequestExtendVote: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Hash = append(m.Hash[:0], dAtA[iNdEx:postIndex]...) + if m.Hash == nil { + m.Hash = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RequestVerifyVoteExtension) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RequestVerifyVoteExtension: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RequestVerifyVoteExtension: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) @@ -12063,40 +13231,7 @@ func (m *RequestProcessProposal) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Header", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Header.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Txs", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorAddress", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -12123,14 +13258,35 @@ func (m *RequestProcessProposal) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Txs = append(m.Txs, make([]byte, postIndex-iNdEx)) - copy(m.Txs[len(m.Txs)-1], dAtA[iNdEx:postIndex]) + m.ValidatorAddress = append(m.ValidatorAddress[:0], dAtA[iNdEx:postIndex]...) + if m.ValidatorAddress == nil { + m.ValidatorAddress = []byte{} + } iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ProposedLastCommit", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field VoteExtension", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTypes @@ -12140,57 +13296,24 @@ func (m *RequestProcessProposal) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthTypes } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthTypes } if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.ProposedLastCommit.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ByzantineValidators", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.ByzantineValidators = append(m.ByzantineValidators, Evidence{}) - if err := m.ByzantineValidators[len(m.ByzantineValidators)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err + m.VoteExtension = append(m.VoteExtension[:0], dAtA[iNdEx:postIndex]...) + if m.VoteExtension == nil { + m.VoteExtension = []byte{} } iNdEx = postIndex default: @@ -12244,73 +13367,6 @@ func (m *RequestFinalizeBlock) Unmarshal(dAtA []byte) error { } switch fieldNum { case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Hash = append(m.Hash[:0], dAtA[iNdEx:postIndex]...) - if m.Hash == nil { - m.Hash = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Header", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Header.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Txs", wireType) } @@ -12342,7 +13398,7 @@ func (m *RequestFinalizeBlock) Unmarshal(dAtA []byte) error { m.Txs = append(m.Txs, make([]byte, postIndex-iNdEx)) copy(m.Txs[len(m.Txs)-1], dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 4: + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DecidedLastCommit", wireType) } @@ -12375,7 +13431,7 @@ func (m *RequestFinalizeBlock) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 5: + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ByzantineValidators", wireType) } @@ -12404,11 +13460,165 @@ func (m *RequestFinalizeBlock) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ByzantineValidators = append(m.ByzantineValidators, Evidence{}) + m.ByzantineValidators = append(m.ByzantineValidators, Misbehavior{}) if err := m.ByzantineValidators[len(m.ByzantineValidators)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Hash = append(m.Hash[:0], dAtA[iNdEx:postIndex]...) + if m.Hash == nil { + m.Hash = []byte{} + } + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.Time, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NextValidatorsHash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NextValidatorsHash = append(m.NextValidatorsHash[:0], dAtA[iNdEx:postIndex]...) + if m.NextValidatorsHash == nil { + m.NextValidatorsHash = []byte{} + } + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProposerAddress", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProposerAddress = append(m.ProposerAddress[:0], dAtA[iNdEx:postIndex]...) + if m.ProposerAddress == nil { + m.ProposerAddress = []byte{} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) @@ -15421,161 +16631,6 @@ func (m *ResponseApplySnapshotChunk) Unmarshal(dAtA []byte) error { } return nil } -func (m *ResponseExtendVote) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ResponseExtendVote: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ResponseExtendVote: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field VoteExtension", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.VoteExtension == nil { - m.VoteExtension = &types1.VoteExtension{} - } - if err := m.VoteExtension.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTypes(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTypes - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ResponseVerifyVoteExtension) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ResponseVerifyVoteExtension: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ResponseVerifyVoteExtension: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Result", wireType) - } - m.Result = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Result |= ResponseVerifyVoteExtension_Result(b&0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := skipTypes(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTypes - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *ResponsePrepareProposal) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -15607,9 +16662,9 @@ func (m *ResponsePrepareProposal) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BlockData", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field TxRecords", wireType) } - var byteLen int + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTypes @@ -15619,94 +16674,26 @@ func (m *ResponsePrepareProposal) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - byteLen |= int(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { + if msglen < 0 { return ErrInvalidLengthTypes } - postIndex := iNdEx + byteLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthTypes } if postIndex > l { return io.ErrUnexpectedEOF } - m.BlockData = append(m.BlockData, make([]byte, postIndex-iNdEx)) - copy(m.BlockData[len(m.BlockData)-1], dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTypes(dAtA[iNdEx:]) - if err != nil { + m.TxRecords = append(m.TxRecords, &TxRecord{}) + if err := m.TxRecords[len(m.TxRecords)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTypes - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ResponseProcessProposal) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ResponseProcessProposal: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ResponseProcessProposal: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Accept", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.Accept = bool(v != 0) + iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field AppHash", wireType) @@ -15866,6 +16853,366 @@ func (m *ResponseProcessProposal) Unmarshal(dAtA []byte) error { } return nil } +func (m *ResponseProcessProposal) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ResponseProcessProposal: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ResponseProcessProposal: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + m.Status = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Status |= ResponseProcessProposal_ProposalStatus(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppHash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppHash = append(m.AppHash[:0], dAtA[iNdEx:postIndex]...) + if m.AppHash == nil { + m.AppHash = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TxResults", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.TxResults = append(m.TxResults, &ExecTxResult{}) + if err := m.TxResults[len(m.TxResults)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorUpdates", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ValidatorUpdates = append(m.ValidatorUpdates, &ValidatorUpdate{}) + if err := m.ValidatorUpdates[len(m.ValidatorUpdates)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConsensusParamUpdates", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ConsensusParamUpdates == nil { + m.ConsensusParamUpdates = &types1.ConsensusParams{} + } + if err := m.ConsensusParamUpdates.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ResponseExtendVote) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ResponseExtendVote: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ResponseExtendVote: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VoteExtension", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.VoteExtension = append(m.VoteExtension[:0], dAtA[iNdEx:postIndex]...) + if m.VoteExtension == nil { + m.VoteExtension = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ResponseVerifyVoteExtension) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ResponseVerifyVoteExtension: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ResponseVerifyVoteExtension: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + m.Status = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Status |= ResponseVerifyVoteExtension_VerifyStatus(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ResponseFinalizeBlock) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -16210,6 +17557,109 @@ func (m *CommitInfo) Unmarshal(dAtA []byte) error { } return nil } +func (m *ExtendedCommitInfo) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ExtendedCommitInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ExtendedCommitInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Round", wireType) + } + m.Round = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Round |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Votes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Votes = append(m.Votes, ExtendedVoteInfo{}) + if err := m.Votes[len(m.Votes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *Event) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -16886,6 +18336,109 @@ func (m *TxResult) Unmarshal(dAtA []byte) error { } return nil } +func (m *TxRecord) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TxRecord: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TxRecord: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Action", wireType) + } + m.Action = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Action |= TxRecord_TxAction(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Tx", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Tx = append(m.Tx[:0], dAtA[iNdEx:postIndex]...) + if m.Tx == nil { + m.Tx = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *Validator) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -17194,7 +18747,7 @@ func (m *VoteInfo) Unmarshal(dAtA []byte) error { } return nil } -func (m *Evidence) Unmarshal(dAtA []byte) error { +func (m *ExtendedVoteInfo) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -17217,10 +18770,147 @@ func (m *Evidence) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: Evidence: wiretype end group for non-group") + return fmt.Errorf("proto: ExtendedVoteInfo: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: Evidence: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ExtendedVoteInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Validator", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Validator.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SignedLastBlock", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.SignedLastBlock = bool(v != 0) + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VoteExtension", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.VoteExtension = append(m.VoteExtension[:0], dAtA[iNdEx:postIndex]...) + if m.VoteExtension == nil { + m.VoteExtension = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Misbehavior) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Misbehavior: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Misbehavior: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -17237,7 +18927,7 @@ func (m *Evidence) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.Type |= EvidenceType(b&0x7F) << shift + m.Type |= MisbehaviorType(b&0x7F) << shift if b < 0x80 { break } diff --git a/abci/types/types_test.go b/abci/types/types_test.go new file mode 100644 index 000000000..f79a24454 --- /dev/null +++ b/abci/types/types_test.go @@ -0,0 +1,74 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/merkle" +) + +func TestHashAndProveResults(t *testing.T) { + trs := []*abci.ExecTxResult{ + // Note, these tests rely on the first two entries being in this order. + {Code: 0, Data: nil}, + {Code: 0, Data: []byte{}}, + + {Code: 0, Data: []byte("one")}, + {Code: 14, Data: nil}, + {Code: 14, Data: []byte("foo")}, + {Code: 14, Data: []byte("bar")}, + } + + // Nil and []byte{} should produce the same bytes + bz0, err := trs[0].Marshal() + require.NoError(t, err) + bz1, err := trs[1].Marshal() + require.NoError(t, err) + require.Equal(t, bz0, bz1) + + // Make sure that we can get a root hash from results and verify proofs. + rs, err := abci.MarshalTxResults(trs) + require.NoError(t, err) + root := merkle.HashFromByteSlices(rs) + assert.NotEmpty(t, root) + + _, proofs := merkle.ProofsFromByteSlices(rs) + for i, tr := range trs { + bz, err := tr.Marshal() + require.NoError(t, err) + + valid := proofs[i].Verify(root, bz) + assert.NoError(t, valid, "%d", i) + } +} + +func TestHashDeterministicFieldsOnly(t *testing.T) { + tr1 := abci.ExecTxResult{ + Code: 1, + Data: []byte("transaction"), + Log: "nondeterministic data: abc", + Info: "nondeterministic data: abc", + GasWanted: 1000, + GasUsed: 1000, + Events: []abci.Event{}, + Codespace: "nondeterministic.data.abc", + } + tr2 := abci.ExecTxResult{ + Code: 1, + Data: []byte("transaction"), + Log: "nondeterministic data: def", + Info: "nondeterministic data: def", + GasWanted: 1000, + GasUsed: 1000, + Events: []abci.Event{}, + Codespace: "nondeterministic.data.def", + } + r1, err := abci.MarshalTxResults([]*abci.ExecTxResult{&tr1}) + require.NoError(t, err) + r2, err := abci.MarshalTxResults([]*abci.ExecTxResult{&tr2}) + require.NoError(t, err) + require.Equal(t, merkle.HashFromByteSlices(r1), merkle.HashFromByteSlices(r2)) +} diff --git a/buf.gen.yaml b/buf.gen.yaml index 335e25241..d972360bb 100644 --- a/buf.gen.yaml +++ b/buf.gen.yaml @@ -1,14 +1,9 @@ -# The version of the generation template (required). -# The only currently-valid value is v1beta1. -version: v1beta1 - -# The plugins to run. +version: v1 plugins: - # The name of the plugin. - name: gogofaster - # The directory where the generated proto output will be written. - # The directory is relative to where the generation tool was run. - out: proto - # Set options to assign import paths to the well-known types - # and to enable service generation. - opt: Mgoogle/protobuf/timestamp.proto=github.com/gogo/protobuf/types,Mgoogle/protobuf/duration.proto=github.com/golang/protobuf/ptypes/duration,plugins=grpc,paths=source_relative + out: ./proto/ + opt: + - Mgoogle/protobuf/timestamp.proto=github.com/gogo/protobuf/types + - Mgoogle/protobuf/duration.proto=github.com/golang/protobuf/ptypes/duration + - plugins=grpc + - paths=source_relative diff --git a/buf.work.yaml b/buf.work.yaml new file mode 100644 index 000000000..1878b341b --- /dev/null +++ b/buf.work.yaml @@ -0,0 +1,3 @@ +version: v1 +directories: + - proto diff --git a/cmd/priv_val_server/main.go b/cmd/priv_val_server/main.go index e34236acc..901422145 100644 --- a/cmd/priv_val_server/main.go +++ b/cmd/priv_val_server/main.go @@ -113,7 +113,7 @@ func main() { // add prometheus metrics for unary RPC calls opts = append(opts, grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor)) - ss := grpcprivval.NewSignerServer(*chainID, pv, logger) + ss := grpcprivval.NewSignerServer(logger, *chainID, pv) protocol, address := tmnet.ProtocolAndAddress(*addr) diff --git a/cmd/tendermint/commands/debug/debug.go b/cmd/tendermint/commands/debug/debug.go index 478a03d55..7fd5b030f 100644 --- a/cmd/tendermint/commands/debug/debug.go +++ b/cmd/tendermint/commands/debug/debug.go @@ -2,6 +2,7 @@ package debug import ( "github.com/spf13/cobra" + "github.com/tendermint/tendermint/libs/log" ) diff --git a/cmd/tendermint/commands/key_migrate.go b/cmd/tendermint/commands/key_migrate.go index 928821586..5866be341 100644 --- a/cmd/tendermint/commands/key_migrate.go +++ b/cmd/tendermint/commands/key_migrate.go @@ -9,6 +9,7 @@ import ( cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/scripts/keymigrate" + "github.com/tendermint/tendermint/scripts/scmigrate" ) func MakeKeyMigrateCommand(conf *cfg.Config, logger log.Logger) *cobra.Command { @@ -51,6 +52,13 @@ func MakeKeyMigrateCommand(conf *cfg.Config, logger log.Logger) *cobra.Command { return fmt.Errorf("running migration for context %q: %w", dbctx, err) } + + if dbctx == "blockstore" { + if err := scmigrate.Migrate(ctx, db); err != nil { + return fmt.Errorf("running seen commit migration: %w", err) + + } + } } logger.Info("completed database migration successfully") diff --git a/cmd/tendermint/commands/light.go b/cmd/tendermint/commands/light.go index fbb6e00db..8e39d7900 100644 --- a/cmd/tendermint/commands/light.go +++ b/cmd/tendermint/commands/light.go @@ -180,7 +180,7 @@ for applications built w/ Cosmos SDK). return err } - ctx, cancel := signal.NotifyContext(cmd.Context(), syscall.SIGTERM) + ctx, cancel := signal.NotifyContext(cmd.Context(), os.Interrupt, syscall.SIGTERM) defer cancel() go func() { diff --git a/cmd/tendermint/commands/reset.go b/cmd/tendermint/commands/reset.go new file mode 100644 index 000000000..332ed2a4d --- /dev/null +++ b/cmd/tendermint/commands/reset.go @@ -0,0 +1,182 @@ +package commands + +import ( + "os" + "path/filepath" + + "github.com/spf13/cobra" + + "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/libs/log" + tmos "github.com/tendermint/tendermint/libs/os" + "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/types" +) + +// MakeResetCommand constructs a command that removes the database of +// the specified Tendermint core instance. +func MakeResetCommand(conf *config.Config, logger log.Logger) *cobra.Command { + var keyType string + + resetCmd := &cobra.Command{ + Use: "reset", + Short: "Set of commands to conveniently reset tendermint related data", + } + + resetBlocksCmd := &cobra.Command{ + Use: "blockchain", + Short: "Removes all blocks, state, transactions and evidence stored by the tendermint node", + RunE: func(cmd *cobra.Command, args []string) error { + return ResetState(conf.DBDir(), logger) + }, + } + + resetPeersCmd := &cobra.Command{ + Use: "peers", + Short: "Removes all peer addresses", + RunE: func(cmd *cobra.Command, args []string) error { + return ResetPeerStore(conf.DBDir()) + }, + } + + resetSignerCmd := &cobra.Command{ + Use: "unsafe-signer", + Short: "esets private validator signer state", + Long: `Resets private validator signer state. +Only use in testing. This can cause the node to double sign`, + RunE: func(cmd *cobra.Command, args []string) error { + return ResetFilePV(conf.PrivValidator.KeyFile(), conf.PrivValidator.StateFile(), logger, keyType) + }, + } + + resetAllCmd := &cobra.Command{ + Use: "unsafe-all", + Short: "Removes all tendermint data including signing state", + Long: `Removes all tendermint data including signing state. +Only use in testing. This can cause the node to double sign`, + RunE: func(cmd *cobra.Command, args []string) error { + return ResetAll(conf.DBDir(), conf.PrivValidator.KeyFile(), + conf.PrivValidator.StateFile(), logger, keyType) + }, + } + + resetSignerCmd.Flags().StringVar(&keyType, "key", types.ABCIPubKeyTypeEd25519, + "Signer key type. Options: ed25519, secp256k1") + + resetAllCmd.Flags().StringVar(&keyType, "key", types.ABCIPubKeyTypeEd25519, + "Signer key type. Options: ed25519, secp256k1") + + resetCmd.AddCommand(resetBlocksCmd) + resetCmd.AddCommand(resetPeersCmd) + resetCmd.AddCommand(resetSignerCmd) + resetCmd.AddCommand(resetAllCmd) + + return resetCmd +} + +// ResetAll removes address book files plus all data, and resets the privValdiator data. +// Exported for extenal CLI usage +// XXX: this is unsafe and should only suitable for testnets. +func ResetAll(dbDir, privValKeyFile, privValStateFile string, logger log.Logger, keyType string) error { + if err := os.RemoveAll(dbDir); err == nil { + logger.Info("Removed all blockchain history", "dir", dbDir) + } else { + logger.Error("error removing all blockchain history", "dir", dbDir, "err", err) + } + + if err := tmos.EnsureDir(dbDir, 0700); err != nil { + logger.Error("unable to recreate dbDir", "err", err) + } + + // recreate the dbDir since the privVal state needs to live there + return ResetFilePV(privValKeyFile, privValStateFile, logger, keyType) +} + +// ResetState removes all blocks, tendermint state, indexed transactions and evidence. +func ResetState(dbDir string, logger log.Logger) error { + blockdb := filepath.Join(dbDir, "blockstore.db") + state := filepath.Join(dbDir, "state.db") + wal := filepath.Join(dbDir, "cs.wal") + evidence := filepath.Join(dbDir, "evidence.db") + txIndex := filepath.Join(dbDir, "tx_index.db") + + if tmos.FileExists(blockdb) { + if err := os.RemoveAll(blockdb); err == nil { + logger.Info("Removed all blockstore.db", "dir", blockdb) + } else { + logger.Error("error removing all blockstore.db", "dir", blockdb, "err", err) + } + } + + if tmos.FileExists(state) { + if err := os.RemoveAll(state); err == nil { + logger.Info("Removed all state.db", "dir", state) + } else { + logger.Error("error removing all state.db", "dir", state, "err", err) + } + } + + if tmos.FileExists(wal) { + if err := os.RemoveAll(wal); err == nil { + logger.Info("Removed all cs.wal", "dir", wal) + } else { + logger.Error("error removing all cs.wal", "dir", wal, "err", err) + } + } + + if tmos.FileExists(evidence) { + if err := os.RemoveAll(evidence); err == nil { + logger.Info("Removed all evidence.db", "dir", evidence) + } else { + logger.Error("error removing all evidence.db", "dir", evidence, "err", err) + } + } + + if tmos.FileExists(txIndex) { + if err := os.RemoveAll(txIndex); err == nil { + logger.Info("Removed tx_index.db", "dir", txIndex) + } else { + logger.Error("error removing tx_index.db", "dir", txIndex, "err", err) + } + } + + return tmos.EnsureDir(dbDir, 0700) +} + +// ResetFilePV loads the file private validator and resets the watermark to 0. If used on an existing network, +// this can cause the node to double sign. +// XXX: this is unsafe and should only suitable for testnets. +func ResetFilePV(privValKeyFile, privValStateFile string, logger log.Logger, keyType string) error { + if _, err := os.Stat(privValKeyFile); err == nil { + pv, err := privval.LoadFilePVEmptyState(privValKeyFile, privValStateFile) + if err != nil { + return err + } + if err := pv.Reset(); err != nil { + return err + } + logger.Info("Reset private validator file to genesis state", "keyFile", privValKeyFile, + "stateFile", privValStateFile) + } else { + pv, err := privval.GenFilePV(privValKeyFile, privValStateFile, keyType) + if err != nil { + return err + } + if err := pv.Save(); err != nil { + return err + } + logger.Info("Generated private validator file", "keyFile", privValKeyFile, + "stateFile", privValStateFile) + } + return nil +} + +// ResetPeerStore removes the peer store containing all information used by the tendermint networking layer +// In the case of a reset, new peers will need to be set either via the config or through the discovery mechanism +func ResetPeerStore(dbDir string) error { + peerstore := filepath.Join(dbDir, "peerstore.db") + if tmos.FileExists(peerstore) { + return os.RemoveAll(peerstore) + } + return nil +} diff --git a/cmd/tendermint/commands/reset_priv_validator.go b/cmd/tendermint/commands/reset_priv_validator.go deleted file mode 100644 index ce0798e45..000000000 --- a/cmd/tendermint/commands/reset_priv_validator.go +++ /dev/null @@ -1,95 +0,0 @@ -package commands - -import ( - "os" - - "github.com/spf13/cobra" - - "github.com/tendermint/tendermint/config" - "github.com/tendermint/tendermint/libs/log" - tmos "github.com/tendermint/tendermint/libs/os" - "github.com/tendermint/tendermint/privval" - "github.com/tendermint/tendermint/types" -) - -// MakeResetAllCommand constructs a command that removes the database of -// the specified Tendermint core instance. -func MakeResetAllCommand(conf *config.Config, logger log.Logger) *cobra.Command { - var keyType string - - cmd := &cobra.Command{ - Use: "unsafe-reset-all", - Short: "(unsafe) Remove all the data and WAL, reset this node's validator to genesis state", - RunE: func(cmd *cobra.Command, args []string) error { - return resetAll(conf.DBDir(), conf.PrivValidator.KeyFile(), - conf.PrivValidator.StateFile(), logger, keyType) - }, - } - cmd.Flags().StringVar(&keyType, "key", types.ABCIPubKeyTypeEd25519, - "Key type to generate privval file with. Options: ed25519, secp256k1") - - return cmd -} - -func MakeResetPrivateValidatorCommand(conf *config.Config, logger log.Logger) *cobra.Command { - var keyType string - - cmd := &cobra.Command{ - Use: "unsafe-reset-priv-validator", - Short: "(unsafe) Reset this node's validator to genesis state", - RunE: func(cmd *cobra.Command, args []string) error { - return resetFilePV(conf.PrivValidator.KeyFile(), conf.PrivValidator.StateFile(), logger, keyType) - }, - } - - cmd.Flags().StringVar(&keyType, "key", types.ABCIPubKeyTypeEd25519, - "Key type to generate privval file with. Options: ed25519, secp256k1") - return cmd - -} - -// XXX: this is totally unsafe. -// it's only suitable for testnets. - -// XXX: this is totally unsafe. -// it's only suitable for testnets. - -// resetAll removes address book files plus all data, and resets the privValdiator data. -// Exported so other CLI tools can use it. -func resetAll(dbDir, privValKeyFile, privValStateFile string, logger log.Logger, keyType string) error { - if err := os.RemoveAll(dbDir); err == nil { - logger.Info("Removed all blockchain history", "dir", dbDir) - } else { - logger.Error("error removing all blockchain history", "dir", dbDir, "err", err) - } - // recreate the dbDir since the privVal state needs to live there - if err := tmos.EnsureDir(dbDir, 0700); err != nil { - logger.Error("unable to recreate dbDir", "err", err) - } - return resetFilePV(privValKeyFile, privValStateFile, logger, keyType) -} - -func resetFilePV(privValKeyFile, privValStateFile string, logger log.Logger, keyType string) error { - if _, err := os.Stat(privValKeyFile); err == nil { - pv, err := privval.LoadFilePVEmptyState(privValKeyFile, privValStateFile) - if err != nil { - return err - } - if err := pv.Reset(); err != nil { - return err - } - logger.Info("Reset private validator file to genesis state", "keyFile", privValKeyFile, - "stateFile", privValStateFile) - } else { - pv, err := privval.GenFilePV(privValKeyFile, privValStateFile, keyType) - if err != nil { - return err - } - if err := pv.Save(); err != nil { - return err - } - logger.Info("Generated private validator file", "keyFile", privValKeyFile, - "stateFile", privValStateFile) - } - return nil -} diff --git a/cmd/tendermint/commands/reset_test.go b/cmd/tendermint/commands/reset_test.go new file mode 100644 index 000000000..7abdfa688 --- /dev/null +++ b/cmd/tendermint/commands/reset_test.go @@ -0,0 +1,62 @@ +package commands + +import ( + "context" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/types" +) + +func Test_ResetAll(t *testing.T) { + config := cfg.TestConfig() + dir := t.TempDir() + config.SetRoot(dir) + logger := log.NewNopLogger() + cfg.EnsureRoot(dir) + require.NoError(t, initFilesWithConfig(context.Background(), config, logger, types.ABCIPubKeyTypeEd25519)) + pv, err := privval.LoadFilePV(config.PrivValidator.KeyFile(), config.PrivValidator.StateFile()) + require.NoError(t, err) + pv.LastSignState.Height = 10 + require.NoError(t, pv.Save()) + require.NoError(t, ResetAll(config.DBDir(), config.PrivValidator.KeyFile(), + config.PrivValidator.StateFile(), logger, types.ABCIPubKeyTypeEd25519)) + require.DirExists(t, config.DBDir()) + require.NoFileExists(t, filepath.Join(config.DBDir(), "block.db")) + require.NoFileExists(t, filepath.Join(config.DBDir(), "state.db")) + require.NoFileExists(t, filepath.Join(config.DBDir(), "evidence.db")) + require.NoFileExists(t, filepath.Join(config.DBDir(), "tx_index.db")) + require.FileExists(t, config.PrivValidator.StateFile()) + pv, err = privval.LoadFilePV(config.PrivValidator.KeyFile(), config.PrivValidator.StateFile()) + require.NoError(t, err) + require.Equal(t, int64(0), pv.LastSignState.Height) +} + +func Test_ResetState(t *testing.T) { + config := cfg.TestConfig() + dir := t.TempDir() + config.SetRoot(dir) + logger := log.NewNopLogger() + cfg.EnsureRoot(dir) + require.NoError(t, initFilesWithConfig(context.Background(), config, logger, types.ABCIPubKeyTypeEd25519)) + pv, err := privval.LoadFilePV(config.PrivValidator.KeyFile(), config.PrivValidator.StateFile()) + require.NoError(t, err) + pv.LastSignState.Height = 10 + require.NoError(t, pv.Save()) + require.NoError(t, ResetState(config.DBDir(), logger)) + require.DirExists(t, config.DBDir()) + require.NoFileExists(t, filepath.Join(config.DBDir(), "block.db")) + require.NoFileExists(t, filepath.Join(config.DBDir(), "state.db")) + require.NoFileExists(t, filepath.Join(config.DBDir(), "evidence.db")) + require.NoFileExists(t, filepath.Join(config.DBDir(), "tx_index.db")) + require.FileExists(t, config.PrivValidator.StateFile()) + pv, err = privval.LoadFilePV(config.PrivValidator.KeyFile(), config.PrivValidator.StateFile()) + require.NoError(t, err) + // private validator state should still be in tact. + require.Equal(t, int64(10), pv.LastSignState.Height) +} diff --git a/cmd/tendermint/commands/rollback_test.go b/cmd/tendermint/commands/rollback_test.go index 760dbf0ec..6d1ba818f 100644 --- a/cmd/tendermint/commands/rollback_test.go +++ b/cmd/tendermint/commands/rollback_test.go @@ -53,6 +53,7 @@ func TestRollbackIntegration(t *testing.T) { defer cancel() node2, _, err2 := rpctest.StartTendermint(ctx, cfg, app, rpctest.SuppressStdout) require.NoError(t, err2) + t.Cleanup(node2.Wait) logger := log.NewNopLogger() diff --git a/cmd/tendermint/commands/root.go b/cmd/tendermint/commands/root.go index 3c9b7d049..fdee638bc 100644 --- a/cmd/tendermint/commands/root.go +++ b/cmd/tendermint/commands/root.go @@ -51,10 +51,12 @@ func RootCommand(conf *config.Config, logger log.Logger) *cobra.Command { } *conf = *pconf config.EnsureRoot(conf.RootDir) - if err := log.OverrideWithNewLogger(logger, conf.LogFormat, conf.LogLevel); err != nil { return err } + if warning := pconf.DeprecatedFieldWarning(); warning != nil { + logger.Info("WARNING", "deprecated field warning", warning) + } return nil }, diff --git a/cmd/tendermint/commands/run_node.go b/cmd/tendermint/commands/run_node.go index 5f39fb21e..347a04034 100644 --- a/cmd/tendermint/commands/run_node.go +++ b/cmd/tendermint/commands/run_node.go @@ -105,7 +105,7 @@ func NewRunNodeCmd(nodeProvider cfg.ServiceProvider, conf *cfg.Config, logger lo return err } - ctx, cancel := signal.NotifyContext(cmd.Context(), syscall.SIGTERM) + ctx, cancel := signal.NotifyContext(cmd.Context(), os.Interrupt, syscall.SIGTERM) defer cancel() n, err := nodeProvider(ctx, conf, logger) diff --git a/cmd/tendermint/main.go b/cmd/tendermint/main.go index 90146fecd..715e8e1d4 100644 --- a/cmd/tendermint/main.go +++ b/cmd/tendermint/main.go @@ -33,8 +33,7 @@ func main() { commands.MakeLightCommand(conf, logger), commands.MakeReplayCommand(conf, logger), commands.MakeReplayConsoleCommand(conf, logger), - commands.MakeResetAllCommand(conf, logger), - commands.MakeResetPrivateValidatorCommand(conf, logger), + commands.MakeResetCommand(conf, logger), commands.MakeShowValidatorCommand(conf, logger), commands.MakeTestnetFilesCommand(conf, logger), commands.MakeShowNodeIDCommand(conf), diff --git a/config/config.go b/config/config.go index 959e37912..500e3f7d6 100644 --- a/config/config.go +++ b/config/config.go @@ -8,6 +8,7 @@ import ( "net/http" "os" "path/filepath" + "strings" "time" "github.com/tendermint/tendermint/libs/log" @@ -145,6 +146,10 @@ func (cfg *Config) ValidateBasic() error { return nil } +func (cfg *Config) DeprecatedFieldWarning() error { + return cfg.Consensus.DeprecatedFieldWarning() +} + //----------------------------------------------------------------------------- // BaseConfig @@ -956,27 +961,6 @@ type ConsensusConfig struct { WalPath string `mapstructure:"wal-file"` walFile string // overrides WalPath if set - // TODO: remove timeout configs, these should be global not local - // How long we wait for a proposal block before prevoting nil - TimeoutPropose time.Duration `mapstructure:"timeout-propose"` - // How much timeout-propose increases with each round - TimeoutProposeDelta time.Duration `mapstructure:"timeout-propose-delta"` - // How long we wait after receiving +2/3 prevotes for “anything” (ie. not a single block or nil) - TimeoutPrevote time.Duration `mapstructure:"timeout-prevote"` - // How much the timeout-prevote increases with each round - TimeoutPrevoteDelta time.Duration `mapstructure:"timeout-prevote-delta"` - // How long we wait after receiving +2/3 precommits for “anything” (ie. not a single block or nil) - TimeoutPrecommit time.Duration `mapstructure:"timeout-precommit"` - // How much the timeout-precommit increases with each round - TimeoutPrecommitDelta time.Duration `mapstructure:"timeout-precommit-delta"` - // How long we wait after committing a block, before starting on the new - // height (this gives us a chance to receive some more precommits, even - // though we already have +2/3). - TimeoutCommit time.Duration `mapstructure:"timeout-commit"` - - // Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) - SkipTimeoutCommit bool `mapstructure:"skip-timeout-commit"` - // EmptyBlocks mode and possible interval between empty blocks CreateEmptyBlocks bool `mapstructure:"create-empty-blocks"` CreateEmptyBlocksInterval time.Duration `mapstructure:"create-empty-blocks-interval"` @@ -986,20 +970,59 @@ type ConsensusConfig struct { PeerQueryMaj23SleepDuration time.Duration `mapstructure:"peer-query-maj23-sleep-duration"` DoubleSignCheckHeight int64 `mapstructure:"double-sign-check-height"` + + // TODO: The following fields are all temporary overrides that should exist only + // for the duration of the v0.36 release. The below fields should be completely + // removed in the v0.37 release of Tendermint. + // See: https://github.com/tendermint/tendermint/issues/8188 + + // UnsafeProposeTimeoutOverride provides an unsafe override of the Propose + // timeout consensus parameter. It configures how long the consensus engine + // will wait to receive a proposal block before prevoting nil. + UnsafeProposeTimeoutOverride time.Duration `mapstructure:"unsafe-propose-timeout-override"` + // UnsafeProposeTimeoutDeltaOverride provides an unsafe override of the + // ProposeDelta timeout consensus parameter. It configures how much the + // propose timeout increases with each round. + UnsafeProposeTimeoutDeltaOverride time.Duration `mapstructure:"unsafe-propose-timeout-delta-override"` + // UnsafeVoteTimeoutOverride provides an unsafe override of the Vote timeout + // consensus parameter. It configures how long the consensus engine will wait + // to gather additional votes after receiving +2/3 votes in a round. + UnsafeVoteTimeoutOverride time.Duration `mapstructure:"unsafe-vote-timeout-override"` + // UnsafeVoteTimeoutDeltaOverride provides an unsafe override of the VoteDelta + // timeout consensus parameter. It configures how much the vote timeout + // increases with each round. + UnsafeVoteTimeoutDeltaOverride time.Duration `mapstructure:"unsafe-vote-timeout-delta-override"` + // UnsafeCommitTimeoutOverride provides an unsafe override of the Commit timeout + // consensus parameter. It configures how long the consensus engine will wait + // after receiving +2/3 precommits before beginning the next height. + UnsafeCommitTimeoutOverride time.Duration `mapstructure:"unsafe-commit-timeout-override"` + + // UnsafeBypassCommitTimeoutOverride provides an unsafe override of the + // BypassCommitTimeout consensus parameter. It configures if the consensus + // engine will wait for the full Commit timeout before proceeding to the next height. + // If it is set to true, the consensus engine will proceed to the next height + // as soon as the node has gathered votes from all of the validators on the network. + UnsafeBypassCommitTimeoutOverride *bool `mapstructure:"unsafe-bypass-commit-timeout-override"` + + // Deprecated timeout parameters. These parameters are present in this struct + // so that they can be parsed so that validation can check if they have erroneously + // been included and provide a helpful error message. + // These fields should be completely removed in v0.37. + // See: https://github.com/tendermint/tendermint/issues/8188 + DeprecatedTimeoutPropose *interface{} `mapstructure:"timeout-propose"` + DeprecatedTimeoutProposeDelta *interface{} `mapstructure:"timeout-propose-delta"` + DeprecatedTimeoutPrevote *interface{} `mapstructure:"timeout-prevote"` + DeprecatedTimeoutPrevoteDelta *interface{} `mapstructure:"timeout-prevote-delta"` + DeprecatedTimeoutPrecommit *interface{} `mapstructure:"timeout-precommit"` + DeprecatedTimeoutPrecommitDelta *interface{} `mapstructure:"timeout-precommit-delta"` + DeprecatedTimeoutCommit *interface{} `mapstructure:"timeout-commit"` + DeprecatedSkipTimeoutCommit *interface{} `mapstructure:"skip-timeout-commit"` } // DefaultConsensusConfig returns a default configuration for the consensus service func DefaultConsensusConfig() *ConsensusConfig { return &ConsensusConfig{ WalPath: filepath.Join(defaultDataDir, "cs.wal", "wal"), - TimeoutPropose: 3000 * time.Millisecond, - TimeoutProposeDelta: 500 * time.Millisecond, - TimeoutPrevote: 1000 * time.Millisecond, - TimeoutPrevoteDelta: 500 * time.Millisecond, - TimeoutPrecommit: 1000 * time.Millisecond, - TimeoutPrecommitDelta: 500 * time.Millisecond, - TimeoutCommit: 1000 * time.Millisecond, - SkipTimeoutCommit: false, CreateEmptyBlocks: true, CreateEmptyBlocksInterval: 0 * time.Second, PeerGossipSleepDuration: 100 * time.Millisecond, @@ -1011,14 +1034,6 @@ func DefaultConsensusConfig() *ConsensusConfig { // TestConsensusConfig returns a configuration for testing the consensus service func TestConsensusConfig() *ConsensusConfig { cfg := DefaultConsensusConfig() - cfg.TimeoutPropose = 40 * time.Millisecond - cfg.TimeoutProposeDelta = 1 * time.Millisecond - cfg.TimeoutPrevote = 10 * time.Millisecond - cfg.TimeoutPrevoteDelta = 1 * time.Millisecond - cfg.TimeoutPrecommit = 10 * time.Millisecond - cfg.TimeoutPrecommitDelta = 1 * time.Millisecond - cfg.TimeoutCommit = 10 * time.Millisecond - cfg.SkipTimeoutCommit = true cfg.PeerGossipSleepDuration = 5 * time.Millisecond cfg.PeerQueryMaj23SleepDuration = 250 * time.Millisecond cfg.DoubleSignCheckHeight = int64(0) @@ -1030,33 +1045,6 @@ func (cfg *ConsensusConfig) WaitForTxs() bool { return !cfg.CreateEmptyBlocks || cfg.CreateEmptyBlocksInterval > 0 } -// Propose returns the amount of time to wait for a proposal -func (cfg *ConsensusConfig) Propose(round int32) time.Duration { - return time.Duration( - cfg.TimeoutPropose.Nanoseconds()+cfg.TimeoutProposeDelta.Nanoseconds()*int64(round), - ) * time.Nanosecond -} - -// Prevote returns the amount of time to wait for straggler votes after receiving any +2/3 prevotes -func (cfg *ConsensusConfig) Prevote(round int32) time.Duration { - return time.Duration( - cfg.TimeoutPrevote.Nanoseconds()+cfg.TimeoutPrevoteDelta.Nanoseconds()*int64(round), - ) * time.Nanosecond -} - -// Precommit returns the amount of time to wait for straggler votes after receiving any +2/3 precommits -func (cfg *ConsensusConfig) Precommit(round int32) time.Duration { - return time.Duration( - cfg.TimeoutPrecommit.Nanoseconds()+cfg.TimeoutPrecommitDelta.Nanoseconds()*int64(round), - ) * time.Nanosecond -} - -// Commit returns the amount of time to wait for straggler votes after receiving +2/3 precommits -// for a single block (ie. a commit). -func (cfg *ConsensusConfig) Commit(t time.Time) time.Time { - return t.Add(cfg.TimeoutCommit) -} - // WalFile returns the full path to the write-ahead log file func (cfg *ConsensusConfig) WalFile() string { if cfg.walFile != "" { @@ -1073,26 +1061,20 @@ func (cfg *ConsensusConfig) SetWalFile(walFile string) { // ValidateBasic performs basic validation (checking param bounds, etc.) and // returns an error if any check fails. func (cfg *ConsensusConfig) ValidateBasic() error { - if cfg.TimeoutPropose < 0 { - return errors.New("timeout-propose can't be negative") + if cfg.UnsafeProposeTimeoutOverride < 0 { + return errors.New("unsafe-propose-timeout-override can't be negative") } - if cfg.TimeoutProposeDelta < 0 { - return errors.New("timeout-propose-delta can't be negative") + if cfg.UnsafeProposeTimeoutDeltaOverride < 0 { + return errors.New("unsafe-propose-timeout-delta-override can't be negative") } - if cfg.TimeoutPrevote < 0 { - return errors.New("timeout-prevote can't be negative") + if cfg.UnsafeVoteTimeoutOverride < 0 { + return errors.New("unsafe-vote-timeout-override can't be negative") } - if cfg.TimeoutPrevoteDelta < 0 { - return errors.New("timeout-prevote-delta can't be negative") + if cfg.UnsafeVoteTimeoutDeltaOverride < 0 { + return errors.New("unsafe-vote-timeout-delta-override can't be negative") } - if cfg.TimeoutPrecommit < 0 { - return errors.New("timeout-precommit can't be negative") - } - if cfg.TimeoutPrecommitDelta < 0 { - return errors.New("timeout-precommit-delta can't be negative") - } - if cfg.TimeoutCommit < 0 { - return errors.New("timeout-commit can't be negative") + if cfg.UnsafeCommitTimeoutOverride < 0 { + return errors.New("unsafe-commit-timeout-override can't be negative") } if cfg.CreateEmptyBlocksInterval < 0 { return errors.New("create-empty-blocks-interval can't be negative") @@ -1109,6 +1091,44 @@ func (cfg *ConsensusConfig) ValidateBasic() error { return nil } +func (cfg *ConsensusConfig) DeprecatedFieldWarning() error { + var fields []string + if cfg.DeprecatedSkipTimeoutCommit != nil { + fields = append(fields, "skip-timeout-commit") + } + if cfg.DeprecatedTimeoutPropose != nil { + fields = append(fields, "timeout-propose") + } + if cfg.DeprecatedTimeoutProposeDelta != nil { + fields = append(fields, "timeout-propose-delta") + } + if cfg.DeprecatedTimeoutPrevote != nil { + fields = append(fields, "timeout-prevote") + } + if cfg.DeprecatedTimeoutPrevoteDelta != nil { + fields = append(fields, "timeout-prevote-delta") + } + if cfg.DeprecatedTimeoutPrecommit != nil { + fields = append(fields, "timeout-precommit") + } + if cfg.DeprecatedTimeoutPrecommitDelta != nil { + fields = append(fields, "timeout-precommit-delta") + } + if cfg.DeprecatedTimeoutCommit != nil { + fields = append(fields, "timeout-commit") + } + if cfg.DeprecatedSkipTimeoutCommit != nil { + fields = append(fields, "skip-timeout-commit") + } + if len(fields) != 0 { + return fmt.Errorf("the following deprecated fields were set in the "+ + "configuration file: %s. These fields were removed in v0.36. Timeout "+ + "configuration has been moved to the ConsensusParams. For more information see "+ + "https://tinyurl.com/adr074", strings.Join(fields, ", ")) + } + return nil +} + //----------------------------------------------------------------------------- // TxIndexConfig // Remember that Event has the following structure: @@ -1125,9 +1145,8 @@ type TxIndexConfig struct { // If list contains `null`, meaning no indexer service will be used. // // Options: - // 1) "null" - no indexer services. - // 2) "kv" (default) - the simplest possible indexer, - // backed by key-value storage (defaults to levelDB; see DBBackend). + // 1) "null" (default) - no indexer services. + // 2) "kv" - a simple indexer backed by key-value storage (see DBBackend) // 3) "psql" - the indexer services backed by PostgreSQL. Indexer []string `mapstructure:"indexer"` @@ -1138,14 +1157,12 @@ type TxIndexConfig struct { // DefaultTxIndexConfig returns a default configuration for the transaction indexer. func DefaultTxIndexConfig() *TxIndexConfig { - return &TxIndexConfig{ - Indexer: []string{"kv"}, - } + return &TxIndexConfig{Indexer: []string{"null"}} } // TestTxIndexConfig returns a default configuration for the transaction indexer. func TestTxIndexConfig() *TxIndexConfig { - return DefaultTxIndexConfig() + return &TxIndexConfig{Indexer: []string{"kv"}} } //----------------------------------------------------------------------------- diff --git a/config/config_test.go b/config/config_test.go index d768a1702..a86ab8463 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -29,8 +29,8 @@ func TestConfigValidateBasic(t *testing.T) { cfg := DefaultConfig() assert.NoError(t, cfg.ValidateBasic()) - // tamper with timeout_propose - cfg.Consensus.TimeoutPropose = -10 * time.Second + // tamper with unsafe-propose-timeout-override + cfg.Consensus.UnsafeProposeTimeoutOverride = -10 * time.Second assert.Error(t, cfg.ValidateBasic()) } @@ -106,25 +106,21 @@ func TestConsensusConfig_ValidateBasic(t *testing.T) { modify func(*ConsensusConfig) expectErr bool }{ - "TimeoutPropose": {func(c *ConsensusConfig) { c.TimeoutPropose = time.Second }, false}, - "TimeoutPropose negative": {func(c *ConsensusConfig) { c.TimeoutPropose = -1 }, true}, - "TimeoutProposeDelta": {func(c *ConsensusConfig) { c.TimeoutProposeDelta = time.Second }, false}, - "TimeoutProposeDelta negative": {func(c *ConsensusConfig) { c.TimeoutProposeDelta = -1 }, true}, - "TimeoutPrevote": {func(c *ConsensusConfig) { c.TimeoutPrevote = time.Second }, false}, - "TimeoutPrevote negative": {func(c *ConsensusConfig) { c.TimeoutPrevote = -1 }, true}, - "TimeoutPrevoteDelta": {func(c *ConsensusConfig) { c.TimeoutPrevoteDelta = time.Second }, false}, - "TimeoutPrevoteDelta negative": {func(c *ConsensusConfig) { c.TimeoutPrevoteDelta = -1 }, true}, - "TimeoutPrecommit": {func(c *ConsensusConfig) { c.TimeoutPrecommit = time.Second }, false}, - "TimeoutPrecommit negative": {func(c *ConsensusConfig) { c.TimeoutPrecommit = -1 }, true}, - "TimeoutPrecommitDelta": {func(c *ConsensusConfig) { c.TimeoutPrecommitDelta = time.Second }, false}, - "TimeoutPrecommitDelta negative": {func(c *ConsensusConfig) { c.TimeoutPrecommitDelta = -1 }, true}, - "TimeoutCommit": {func(c *ConsensusConfig) { c.TimeoutCommit = time.Second }, false}, - "TimeoutCommit negative": {func(c *ConsensusConfig) { c.TimeoutCommit = -1 }, true}, - "PeerGossipSleepDuration": {func(c *ConsensusConfig) { c.PeerGossipSleepDuration = time.Second }, false}, - "PeerGossipSleepDuration negative": {func(c *ConsensusConfig) { c.PeerGossipSleepDuration = -1 }, true}, - "PeerQueryMaj23SleepDuration": {func(c *ConsensusConfig) { c.PeerQueryMaj23SleepDuration = time.Second }, false}, - "PeerQueryMaj23SleepDuration negative": {func(c *ConsensusConfig) { c.PeerQueryMaj23SleepDuration = -1 }, true}, - "DoubleSignCheckHeight negative": {func(c *ConsensusConfig) { c.DoubleSignCheckHeight = -1 }, true}, + "UnsafeProposeTimeoutOverride": {func(c *ConsensusConfig) { c.UnsafeProposeTimeoutOverride = time.Second }, false}, + "UnsafeProposeTimeoutOverride negative": {func(c *ConsensusConfig) { c.UnsafeProposeTimeoutOverride = -1 }, true}, + "UnsafeProposeTimeoutDeltaOverride": {func(c *ConsensusConfig) { c.UnsafeProposeTimeoutDeltaOverride = time.Second }, false}, + "UnsafeProposeTimeoutDeltaOverride negative": {func(c *ConsensusConfig) { c.UnsafeProposeTimeoutDeltaOverride = -1 }, true}, + "UnsafePrevoteTimeoutOverride": {func(c *ConsensusConfig) { c.UnsafeVoteTimeoutOverride = time.Second }, false}, + "UnsafePrevoteTimeoutOverride negative": {func(c *ConsensusConfig) { c.UnsafeVoteTimeoutOverride = -1 }, true}, + "UnsafePrevoteTimeoutDeltaOverride": {func(c *ConsensusConfig) { c.UnsafeVoteTimeoutDeltaOverride = time.Second }, false}, + "UnsafePrevoteTimeoutDeltaOverride negative": {func(c *ConsensusConfig) { c.UnsafeVoteTimeoutDeltaOverride = -1 }, true}, + "UnsafeCommitTimeoutOverride": {func(c *ConsensusConfig) { c.UnsafeCommitTimeoutOverride = time.Second }, false}, + "UnsafeCommitTimeoutOverride negative": {func(c *ConsensusConfig) { c.UnsafeCommitTimeoutOverride = -1 }, true}, + "PeerGossipSleepDuration": {func(c *ConsensusConfig) { c.PeerGossipSleepDuration = time.Second }, false}, + "PeerGossipSleepDuration negative": {func(c *ConsensusConfig) { c.PeerGossipSleepDuration = -1 }, true}, + "PeerQueryMaj23SleepDuration": {func(c *ConsensusConfig) { c.PeerQueryMaj23SleepDuration = time.Second }, false}, + "PeerQueryMaj23SleepDuration negative": {func(c *ConsensusConfig) { c.PeerQueryMaj23SleepDuration = -1 }, true}, + "DoubleSignCheckHeight negative": {func(c *ConsensusConfig) { c.DoubleSignCheckHeight = -1 }, true}, } for desc, tc := range testcases { tc := tc // appease linter diff --git a/config/toml.go b/config/toml.go index 0508f9e74..578718ca5 100644 --- a/config/toml.go +++ b/config/toml.go @@ -12,8 +12,8 @@ import ( tmrand "github.com/tendermint/tendermint/libs/rand" ) -// DefaultDirPerm is the default permissions used when creating directories. -const DefaultDirPerm = 0700 +// defaultDirPerm is the default permissions used when creating directories. +const defaultDirPerm = 0700 var configTemplate *template.Template @@ -32,13 +32,13 @@ func init() { // EnsureRoot creates the root, config, and data directories if they don't exist, // and panics if it fails. func EnsureRoot(rootDir string) { - if err := tmos.EnsureDir(rootDir, DefaultDirPerm); err != nil { + if err := tmos.EnsureDir(rootDir, defaultDirPerm); err != nil { panic(err.Error()) } - if err := tmos.EnsureDir(filepath.Join(rootDir, defaultConfigDir), DefaultDirPerm); err != nil { + if err := tmos.EnsureDir(filepath.Join(rootDir, defaultConfigDir), defaultDirPerm); err != nil { panic(err.Error()) } - if err := tmos.EnsureDir(filepath.Join(rootDir, defaultDataDir), DefaultDirPerm); err != nil { + if err := tmos.EnsureDir(filepath.Join(rootDir, defaultDataDir), defaultDirPerm); err != nil { panic(err.Error()) } } @@ -450,32 +450,12 @@ fetchers = "{{ .StateSync.Fetchers }}" wal-file = "{{ js .Consensus.WalPath }}" -# How long we wait for a proposal block before prevoting nil -timeout-propose = "{{ .Consensus.TimeoutPropose }}" -# How much timeout-propose increases with each round -timeout-propose-delta = "{{ .Consensus.TimeoutProposeDelta }}" -# How long we wait after receiving +2/3 prevotes for “anything” (ie. not a single block or nil) -timeout-prevote = "{{ .Consensus.TimeoutPrevote }}" -# How much the timeout-prevote increases with each round -timeout-prevote-delta = "{{ .Consensus.TimeoutPrevoteDelta }}" -# How long we wait after receiving +2/3 precommits for “anything” (ie. not a single block or nil) -timeout-precommit = "{{ .Consensus.TimeoutPrecommit }}" -# How much the timeout-precommit increases with each round -timeout-precommit-delta = "{{ .Consensus.TimeoutPrecommitDelta }}" -# How long we wait after committing a block, before starting on the new -# height (this gives us a chance to receive some more precommits, even -# though we already have +2/3). -timeout-commit = "{{ .Consensus.TimeoutCommit }}" - # How many blocks to look back to check existence of the node's consensus votes before joining consensus # When non-zero, the node will panic upon restart # if the same consensus key was used to sign {double-sign-check-height} last blocks. # So, validators should stop the state machine, wait for some blocks, and then restart the state machine to avoid panic. double-sign-check-height = {{ .Consensus.DoubleSignCheckHeight }} -# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) -skip-timeout-commit = {{ .Consensus.SkipTimeoutCommit }} - # EmptyBlocks mode and possible interval between empty blocks create-empty-blocks = {{ .Consensus.CreateEmptyBlocks }} create-empty-blocks-interval = "{{ .Consensus.CreateEmptyBlocksInterval }}" @@ -484,6 +464,50 @@ create-empty-blocks-interval = "{{ .Consensus.CreateEmptyBlocksInterval }}" peer-gossip-sleep-duration = "{{ .Consensus.PeerGossipSleepDuration }}" peer-query-maj23-sleep-duration = "{{ .Consensus.PeerQueryMaj23SleepDuration }}" +### Unsafe Timeout Overrides ### + +# These fields provide temporary overrides for the Timeout consensus parameters. +# Use of these parameters is strongly discouraged. Using these parameters may have serious +# liveness implications for the validator and for the chain. +# +# These fields will be removed from the configuration file in the v0.37 release of Tendermint. +# For additional information, see ADR-74: +# https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-074-timeout-params.md + +# This field provides an unsafe override of the Propose timeout consensus parameter. +# This field configures how long the consensus engine will wait for a proposal block before prevoting nil. +# If this field is set to a value greater than 0, it will take effect. +# unsafe-propose-timeout-override = {{ .Consensus.UnsafeProposeTimeoutOverride }} + +# This field provides an unsafe override of the ProposeDelta timeout consensus parameter. +# This field configures how much the propose timeout increases with each round. +# If this field is set to a value greater than 0, it will take effect. +# unsafe-propose-timeout-delta-override = {{ .Consensus.UnsafeProposeTimeoutDeltaOverride }} + +# This field provides an unsafe override of the Vote timeout consensus parameter. +# This field configures how long the consensus engine will wait after +# receiving +2/3 votes in a round. +# If this field is set to a value greater than 0, it will take effect. +# unsafe-vote-timeout-override = {{ .Consensus.UnsafeVoteTimeoutOverride }} + +# This field provides an unsafe override of the VoteDelta timeout consensus parameter. +# This field configures how much the vote timeout increases with each round. +# If this field is set to a value greater than 0, it will take effect. +# unsafe-vote-timeout-delta-override = {{ .Consensus.UnsafeVoteTimeoutDeltaOverride }} + +# This field provides an unsafe override of the Commit timeout consensus parameter. +# This field configures how long the consensus engine will wait after receiving +# +2/3 precommits before beginning the next height. +# If this field is set to a value greater than 0, it will take effect. +# unsafe-commit-timeout-override = {{ .Consensus.UnsafeCommitTimeoutOverride }} + +# This field provides an unsafe override of the BypassCommitTimeout consensus parameter. +# This field configures if the consensus engine will wait for the full Commit timeout +# before proceeding to the next height. +# If this field is set to true, the consensus engine will proceed to the next height +# as soon as the node has gathered votes from all of the validators on the network. +# unsafe-bypass-commit-timeout-override = + ####################################################### ### Transaction Indexer Configuration Options ### ####################################################### @@ -496,8 +520,8 @@ peer-query-maj23-sleep-duration = "{{ .Consensus.PeerQueryMaj23SleepDuration }}" # to decide which txs to index based on configuration set in the application. # # Options: -# 1) "null" -# 2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). +# 1) "null" (default) - no indexer services. +# 2) "kv" - a simple indexer backed by key-value storage (see DBBackend) # 3) "psql" - the indexer services backed by PostgreSQL. # When "kv" or "psql" is chosen "tx.height" and "tx.hash" will always be indexed. indexer = [{{ range $i, $e := .TxIndex.Indexer }}{{if $i}}, {{end}}{{ printf "%q" $e}}{{end}}] @@ -542,10 +566,10 @@ func ResetTestRootWithChainID(dir, testName string, chainID string) (*Config, er return nil, err } // ensure config and data subdirs are created - if err := tmos.EnsureDir(filepath.Join(rootDir, defaultConfigDir), DefaultDirPerm); err != nil { + if err := tmos.EnsureDir(filepath.Join(rootDir, defaultConfigDir), defaultDirPerm); err != nil { return nil, err } - if err := tmos.EnsureDir(filepath.Join(rootDir, defaultDataDir), DefaultDirPerm); err != nil { + if err := tmos.EnsureDir(filepath.Join(rootDir, defaultDataDir), defaultDirPerm); err != nil { return nil, err } @@ -588,7 +612,7 @@ func writeFile(filePath string, contents []byte, mode os.FileMode) error { return nil } -var testGenesisFmt = `{ +const testGenesisFmt = `{ "genesis_time": "2018-10-10T08:20:13.695936996Z", "chain_id": "%s", "initial_height": "1", @@ -602,6 +626,14 @@ var testGenesisFmt = `{ "message_delay": "500000000", "precision": "10000000" }, + "timeout": { + "propose": "30000000", + "propose_delta": "50000", + "vote": "30000000", + "vote_delta": "50000", + "commit": "10000000", + "bypass_timeout_commit": true + }, "evidence": { "max_age_num_blocks": "100000", "max_age_duration": "172800000000000", @@ -627,7 +659,7 @@ var testGenesisFmt = `{ "app_hash": "" }` -var testPrivValidatorKey = `{ +const testPrivValidatorKey = `{ "address": "A3258DCBF45DCA0DF052981870F2D1441A36D145", "pub_key": { "type": "tendermint/PubKeyEd25519", @@ -639,7 +671,7 @@ var testPrivValidatorKey = `{ } }` -var testPrivValidatorState = `{ +const testPrivValidatorState = `{ "height": "0", "round": 0, "step": 0 diff --git a/crypto/README.md b/crypto/README.md index 20346d715..d60628d97 100644 --- a/crypto/README.md +++ b/crypto/README.md @@ -12,7 +12,7 @@ For any specific algorithm, use its specific module e.g. ## Binary encoding -For Binary encoding, please refer to the [Tendermint encoding specification](https://docs.tendermint.com/master/spec/blockchain/encoding.html). +For Binary encoding, please refer to the [Tendermint encoding specification](https://docs.tendermint.com/master/spec/core/encoding.html). ## JSON Encoding diff --git a/crypto/crypto.go b/crypto/crypto.go index 4f0dc05e7..ea24af243 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -1,14 +1,18 @@ package crypto import ( - "github.com/tendermint/tendermint/crypto/tmhash" + "crypto/sha256" + "github.com/tendermint/tendermint/internal/jsontypes" "github.com/tendermint/tendermint/libs/bytes" ) const ( + // HashSize is the size in bytes of an AddressHash. + HashSize = sha256.Size + // AddressSize is the size of a pubkey address. - AddressSize = tmhash.TruncatedSize + AddressSize = 20 ) // An address is a []byte, but hex-encoded even in JSON. @@ -16,8 +20,19 @@ const ( // Use an alias so Unmarshal methods (with ptr receivers) are available too. type Address = bytes.HexBytes +// AddressHash computes a truncated SHA-256 hash of bz for use as +// a peer address. +// +// See: https://docs.tendermint.com/master/spec/core/data_structures.html#address func AddressHash(bz []byte) Address { - return Address(tmhash.SumTruncated(bz)) + h := sha256.Sum256(bz) + return Address(h[:AddressSize]) +} + +// Checksum returns the SHA256 of the bz. +func Checksum(bz []byte) []byte { + h := sha256.Sum256(bz) + return h[:] } type PubKey interface { diff --git a/crypto/ed25519/ed25519.go b/crypto/ed25519/ed25519.go index ffd4a3ed1..ca425d111 100644 --- a/crypto/ed25519/ed25519.go +++ b/crypto/ed25519/ed25519.go @@ -2,6 +2,8 @@ package ed25519 import ( "bytes" + "crypto/rand" + "crypto/sha256" "crypto/subtle" "errors" "fmt" @@ -11,7 +13,6 @@ import ( "github.com/oasisprotocol/curve25519-voi/primitives/ed25519/extra/cache" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/jsontypes" ) @@ -124,7 +125,7 @@ func (privKey PrivKey) Type() string { // It uses OS randomness in conjunction with the current global random seed // in tendermint/libs/common to generate the private key. func GenPrivKey() PrivKey { - return genPrivKey(crypto.CReader()) + return genPrivKey(rand.Reader) } // genPrivKey generates a new ed25519 private key using the provided reader. @@ -142,9 +143,8 @@ func genPrivKey(rand io.Reader) PrivKey { // NOTE: secret should be the output of a KDF like bcrypt, // if it's derived from user input. func GenPrivKeyFromSecret(secret []byte) PrivKey { - seed := crypto.Sha256(secret) // Not Ripemd160 because we want 32 bytes. - - return PrivKey(ed25519.NewKeyFromSeed(seed)) + seed := sha256.Sum256(secret) + return PrivKey(ed25519.NewKeyFromSeed(seed[:])) } //------------------------------------- @@ -162,7 +162,7 @@ func (pubKey PubKey) Address() crypto.Address { if len(pubKey) != PubKeySize { panic("pubkey is incorrect size") } - return crypto.Address(tmhash.SumTruncated(pubKey)) + return crypto.AddressHash(pubKey) } // Bytes returns the PubKey byte format. @@ -229,5 +229,5 @@ func (b *BatchVerifier) Add(key crypto.PubKey, msg, signature []byte) error { } func (b *BatchVerifier) Verify() (bool, []bool) { - return b.BatchVerifier.Verify(crypto.CReader()) + return b.BatchVerifier.Verify(rand.Reader) } diff --git a/crypto/example_test.go b/crypto/example_test.go deleted file mode 100644 index f1d0013d4..000000000 --- a/crypto/example_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2017 Tendermint. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package crypto_test - -import ( - "fmt" - - "github.com/tendermint/tendermint/crypto" -) - -func ExampleSha256() { - sum := crypto.Sha256([]byte("This is Tendermint")) - fmt.Printf("%x\n", sum) - // Output: - // f91afb642f3d1c87c17eb01aae5cb65c242dfdbe7cf1066cc260f4ce5d33b94e -} diff --git a/crypto/hash.go b/crypto/hash.go deleted file mode 100644 index e1d22523f..000000000 --- a/crypto/hash.go +++ /dev/null @@ -1,11 +0,0 @@ -package crypto - -import ( - "crypto/sha256" -) - -func Sha256(bytes []byte) []byte { - hasher := sha256.New() - hasher.Write(bytes) - return hasher.Sum(nil) -} diff --git a/crypto/merkle/hash.go b/crypto/merkle/hash.go index 9c6df1786..0bb5448d7 100644 --- a/crypto/merkle/hash.go +++ b/crypto/merkle/hash.go @@ -3,7 +3,7 @@ package merkle import ( "hash" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" ) // TODO: make these have a large predefined capacity @@ -14,12 +14,12 @@ var ( // returns tmhash() func emptyHash() []byte { - return tmhash.Sum([]byte{}) + return crypto.Checksum([]byte{}) } // returns tmhash(0x00 || leaf) func leafHash(leaf []byte) []byte { - return tmhash.Sum(append(leafPrefix, leaf...)) + return crypto.Checksum(append(leafPrefix, leaf...)) } // returns tmhash(0x00 || leaf) @@ -36,7 +36,7 @@ func innerHash(left []byte, right []byte) []byte { n := copy(data, innerPrefix) n += copy(data[n:], left) copy(data[n:], right) - return tmhash.Sum(data) + return crypto.Checksum(data)[:] } func innerHashOpt(s hash.Hash, left []byte, right []byte) []byte { diff --git a/crypto/merkle/proof.go b/crypto/merkle/proof.go index 4f09e4414..8b98d1b21 100644 --- a/crypto/merkle/proof.go +++ b/crypto/merkle/proof.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" tmcrypto "github.com/tendermint/tendermint/proto/tendermint/crypto" ) @@ -102,15 +102,15 @@ func (sp *Proof) ValidateBasic() error { if sp.Index < 0 { return errors.New("negative Index") } - if len(sp.LeafHash) != tmhash.Size { - return fmt.Errorf("expected LeafHash size to be %d, got %d", tmhash.Size, len(sp.LeafHash)) + if len(sp.LeafHash) != crypto.HashSize { + return fmt.Errorf("expected LeafHash size to be %d, got %d", crypto.HashSize, len(sp.LeafHash)) } if len(sp.Aunts) > MaxAunts { return fmt.Errorf("expected no more than %d aunts, got %d", MaxAunts, len(sp.Aunts)) } for i, auntHash := range sp.Aunts { - if len(auntHash) != tmhash.Size { - return fmt.Errorf("expected Aunts#%d size to be %d, got %d", i, tmhash.Size, len(auntHash)) + if len(auntHash) != crypto.HashSize { + return fmt.Errorf("expected Aunts#%d size to be %d, got %d", i, crypto.HashSize, len(auntHash)) } } return nil diff --git a/crypto/merkle/proof_value.go b/crypto/merkle/proof_value.go index 842dc8201..0f4f2eb3d 100644 --- a/crypto/merkle/proof_value.go +++ b/crypto/merkle/proof_value.go @@ -2,9 +2,9 @@ package merkle import ( "bytes" + "crypto/sha256" "fmt" - "github.com/tendermint/tendermint/crypto/tmhash" tmcrypto "github.com/tendermint/tendermint/proto/tendermint/crypto" ) @@ -79,14 +79,13 @@ func (op ValueOp) Run(args [][]byte) ([][]byte, error) { return nil, fmt.Errorf("expected 1 arg, got %v", len(args)) } value := args[0] - hasher := tmhash.New() - hasher.Write(value) - vhash := hasher.Sum(nil) + + vhash := sha256.Sum256(value) bz := new(bytes.Buffer) // Wrap to hash the KVPair. - encodeByteSlice(bz, op.key) //nolint: errcheck // does not error - encodeByteSlice(bz, vhash) //nolint: errcheck // does not error + encodeByteSlice(bz, op.key) //nolint: errcheck // does not error + encodeByteSlice(bz, vhash[:]) //nolint: errcheck // does not error kvhash := leafHash(bz.Bytes()) if !bytes.Equal(kvhash, op.Proof.LeafHash) { diff --git a/crypto/merkle/rfc6962_test.go b/crypto/merkle/rfc6962_test.go index c762cda56..7a70dbb91 100644 --- a/crypto/merkle/rfc6962_test.go +++ b/crypto/merkle/rfc6962_test.go @@ -20,7 +20,7 @@ import ( "encoding/hex" "testing" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" ) func TestRFC6962Hasher(t *testing.T) { @@ -39,7 +39,7 @@ func TestRFC6962Hasher(t *testing.T) { // echo -n '' | sha256sum { desc: "RFC6962 Empty Tree", - want: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"[:tmhash.Size*2], + want: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"[:crypto.HashSize*2], got: emptyTreeHash, }, @@ -47,19 +47,19 @@ func TestRFC6962Hasher(t *testing.T) { // echo -n 00 | xxd -r -p | sha256sum { desc: "RFC6962 Empty Leaf", - want: "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d"[:tmhash.Size*2], + want: "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d"[:crypto.HashSize*2], got: emptyLeafHash, }, // echo -n 004C313233343536 | xxd -r -p | sha256sum { desc: "RFC6962 Leaf", - want: "395aa064aa4c29f7010acfe3f25db9485bbd4b91897b6ad7ad547639252b4d56"[:tmhash.Size*2], + want: "395aa064aa4c29f7010acfe3f25db9485bbd4b91897b6ad7ad547639252b4d56"[:crypto.HashSize*2], got: leafHash, }, // echo -n 014E3132334E343536 | xxd -r -p | sha256sum { desc: "RFC6962 Node", - want: "aa217fe888e47007fa15edab33c2b492a722cb106c64667fc2b044444de66bbb"[:tmhash.Size*2], + want: "aa217fe888e47007fa15edab33c2b492a722cb106c64667fc2b044444de66bbb"[:crypto.HashSize*2], got: innerHash([]byte("N123"), []byte("N456")), }, } { diff --git a/crypto/merkle/tree_test.go b/crypto/merkle/tree_test.go index 641c46b76..72b260178 100644 --- a/crypto/merkle/tree_test.go +++ b/crypto/merkle/tree_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" ctest "github.com/tendermint/tendermint/internal/libs/test" tmrand "github.com/tendermint/tendermint/libs/rand" ) @@ -53,7 +53,7 @@ func TestProof(t *testing.T) { items := make([][]byte, total) for i := 0; i < total; i++ { - items[i] = testItem(tmrand.Bytes(tmhash.Size)) + items[i] = testItem(tmrand.Bytes(crypto.HashSize)) } rootHash = HashFromByteSlices(items) @@ -106,7 +106,7 @@ func TestHashAlternatives(t *testing.T) { items := make([][]byte, total) for i := 0; i < total; i++ { - items[i] = testItem(tmrand.Bytes(tmhash.Size)) + items[i] = testItem(tmrand.Bytes(crypto.HashSize)) } rootHash1 := HashFromByteSlicesIterative(items) @@ -119,7 +119,7 @@ func BenchmarkHashAlternatives(b *testing.B) { items := make([][]byte, total) for i := 0; i < total; i++ { - items[i] = testItem(tmrand.Bytes(tmhash.Size)) + items[i] = testItem(tmrand.Bytes(crypto.HashSize)) } b.ResetTimer() diff --git a/crypto/random.go b/crypto/random.go index 275fb1044..d3e66801c 100644 --- a/crypto/random.go +++ b/crypto/random.go @@ -1,35 +1,15 @@ package crypto import ( - crand "crypto/rand" - "encoding/hex" - "io" + "crypto/rand" ) // This only uses the OS's randomness -func randBytes(numBytes int) []byte { +func CRandBytes(numBytes int) []byte { b := make([]byte, numBytes) - _, err := crand.Read(b) + _, err := rand.Read(b) if err != nil { panic(err) } return b } - -// This only uses the OS's randomness -func CRandBytes(numBytes int) []byte { - return randBytes(numBytes) -} - -// CRandHex returns a hex encoded string that's floor(numDigits/2) * 2 long. -// -// Note: CRandHex(24) gives 96 bits of randomness that -// are usually strong enough for most purposes. -func CRandHex(numDigits int) string { - return hex.EncodeToString(CRandBytes(numDigits / 2)) -} - -// Returns a crand.Reader. -func CReader() io.Reader { - return crand.Reader -} diff --git a/crypto/secp256k1/secp256k1.go b/crypto/secp256k1/secp256k1.go index 7892cfbb1..d0626456c 100644 --- a/crypto/secp256k1/secp256k1.go +++ b/crypto/secp256k1/secp256k1.go @@ -2,6 +2,7 @@ package secp256k1 import ( "bytes" + "crypto/rand" "crypto/sha256" "crypto/subtle" "fmt" @@ -70,7 +71,7 @@ func (privKey PrivKey) Type() string { // GenPrivKey generates a new ECDSA private key on curve secp256k1 private key. // It uses OS randomness to generate the private key. func GenPrivKey() PrivKey { - return genPrivKey(crypto.CReader()) + return genPrivKey(rand.Reader) } // genPrivKey generates a new secp256k1 private key using the provided reader. @@ -190,8 +191,8 @@ var secp256k1halfN = new(big.Int).Rsh(secp256k1.S256().N, 1) // The returned signature will be of the form R || S (in lower-S form). func (privKey PrivKey) Sign(msg []byte) ([]byte, error) { priv, _ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey) - - sig, err := priv.Sign(crypto.Sha256(msg)) + seed := sha256.Sum256(msg) + sig, err := priv.Sign(seed[:]) if err != nil { return nil, err } @@ -220,7 +221,8 @@ func (pubKey PubKey) VerifySignature(msg []byte, sigStr []byte) bool { return false } - return signature.Verify(crypto.Sha256(msg), pub) + seed := sha256.Sum256(msg) + return signature.Verify(seed[:], pub) } // Read Signature struct from R || S. Caller needs to ensure diff --git a/crypto/sr25519/batch.go b/crypto/sr25519/batch.go index 462728598..3e959fbbf 100644 --- a/crypto/sr25519/batch.go +++ b/crypto/sr25519/batch.go @@ -1,6 +1,7 @@ package sr25519 import ( + "crypto/rand" "fmt" "github.com/oasisprotocol/curve25519-voi/primitives/sr25519" @@ -42,5 +43,5 @@ func (b *BatchVerifier) Add(key crypto.PubKey, msg, signature []byte) error { } func (b *BatchVerifier) Verify() (bool, []bool) { - return b.BatchVerifier.Verify(crypto.CReader()) + return b.BatchVerifier.Verify(rand.Reader) } diff --git a/crypto/sr25519/privkey.go b/crypto/sr25519/privkey.go index 4e9cc995f..c8da94d5d 100644 --- a/crypto/sr25519/privkey.go +++ b/crypto/sr25519/privkey.go @@ -1,6 +1,8 @@ package sr25519 import ( + "crypto/rand" + "crypto/sha256" "encoding/json" "fmt" "io" @@ -48,7 +50,7 @@ func (privKey PrivKey) Sign(msg []byte) ([]byte, error) { st := signingCtx.NewTranscriptBytes(msg) - sig, err := privKey.kp.Sign(crypto.CReader(), st) + sig, err := privKey.kp.Sign(rand.Reader, st) if err != nil { return nil, fmt.Errorf("sr25519: failed to sign message: %w", err) } @@ -132,7 +134,7 @@ func (privKey *PrivKey) UnmarshalJSON(data []byte) error { // It uses OS randomness in conjunction with the current global random seed // in tendermint/libs/common to generate the private key. func GenPrivKey() PrivKey { - return genPrivKey(crypto.CReader()) + return genPrivKey(rand.Reader) } func genPrivKey(rng io.Reader) PrivKey { @@ -154,10 +156,9 @@ func genPrivKey(rng io.Reader) PrivKey { // NOTE: secret should be the output of a KDF like bcrypt, // if it's derived from user input. func GenPrivKeyFromSecret(secret []byte) PrivKey { - seed := crypto.Sha256(secret) // Not Ripemd160 because we want 32 bytes. - + seed := sha256.Sum256(secret) var privKey PrivKey - if err := privKey.msk.UnmarshalBinary(seed); err != nil { + if err := privKey.msk.UnmarshalBinary(seed[:]); err != nil { panic("sr25519: failed to deserialize MiniSecretKey: " + err.Error()) } diff --git a/crypto/sr25519/pubkey.go b/crypto/sr25519/pubkey.go index 717f25c8c..a2c6bb920 100644 --- a/crypto/sr25519/pubkey.go +++ b/crypto/sr25519/pubkey.go @@ -7,7 +7,6 @@ import ( "github.com/oasisprotocol/curve25519-voi/primitives/sr25519" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" ) var _ crypto.PubKey = PubKey{} @@ -31,7 +30,7 @@ func (pubKey PubKey) Address() crypto.Address { if len(pubKey) != PubKeySize { panic("pubkey is incorrect size") } - return crypto.Address(tmhash.SumTruncated(pubKey)) + return crypto.AddressHash(pubKey) } // Bytes returns the PubKey byte format. diff --git a/crypto/tmhash/hash.go b/crypto/tmhash/hash.go deleted file mode 100644 index f9b958242..000000000 --- a/crypto/tmhash/hash.go +++ /dev/null @@ -1,65 +0,0 @@ -package tmhash - -import ( - "crypto/sha256" - "hash" -) - -const ( - Size = sha256.Size - BlockSize = sha256.BlockSize -) - -// New returns a new hash.Hash. -func New() hash.Hash { - return sha256.New() -} - -// Sum returns the SHA256 of the bz. -func Sum(bz []byte) []byte { - h := sha256.Sum256(bz) - return h[:] -} - -//------------------------------------------------------------- - -const ( - TruncatedSize = 20 -) - -type sha256trunc struct { - sha256 hash.Hash -} - -func (h sha256trunc) Write(p []byte) (n int, err error) { - return h.sha256.Write(p) -} -func (h sha256trunc) Sum(b []byte) []byte { - shasum := h.sha256.Sum(b) - return shasum[:TruncatedSize] -} - -func (h sha256trunc) Reset() { - h.sha256.Reset() -} - -func (h sha256trunc) Size() int { - return TruncatedSize -} - -func (h sha256trunc) BlockSize() int { - return h.sha256.BlockSize() -} - -// NewTruncated returns a new hash.Hash. -func NewTruncated() hash.Hash { - return sha256trunc{ - sha256: sha256.New(), - } -} - -// SumTruncated returns the first 20 bytes of SHA256 of the bz. -func SumTruncated(bz []byte) []byte { - hash := sha256.Sum256(bz) - return hash[:TruncatedSize] -} diff --git a/crypto/tmhash/hash_test.go b/crypto/tmhash/hash_test.go deleted file mode 100644 index cf9991b3b..000000000 --- a/crypto/tmhash/hash_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package tmhash_test - -import ( - "crypto/sha256" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/tendermint/tendermint/crypto/tmhash" -) - -func TestHash(t *testing.T) { - testVector := []byte("abc") - hasher := tmhash.New() - _, err := hasher.Write(testVector) - require.NoError(t, err) - bz := hasher.Sum(nil) - - bz2 := tmhash.Sum(testVector) - - hasher = sha256.New() - _, err = hasher.Write(testVector) - require.NoError(t, err) - bz3 := hasher.Sum(nil) - - assert.Equal(t, bz, bz2) - assert.Equal(t, bz, bz3) -} - -func TestHashTruncated(t *testing.T) { - testVector := []byte("abc") - hasher := tmhash.NewTruncated() - _, err := hasher.Write(testVector) - require.NoError(t, err) - bz := hasher.Sum(nil) - - bz2 := tmhash.SumTruncated(testVector) - - hasher = sha256.New() - _, err = hasher.Write(testVector) - require.NoError(t, err) - bz3 := hasher.Sum(nil) - bz3 = bz3[:tmhash.TruncatedSize] - - assert.Equal(t, bz, bz2) - assert.Equal(t, bz, bz3) -} diff --git a/crypto/version.go b/crypto/version.go deleted file mode 100644 index 77c0bed8a..000000000 --- a/crypto/version.go +++ /dev/null @@ -1,3 +0,0 @@ -package crypto - -const Version = "0.9.0-dev" diff --git a/docs/app-dev/getting-started.md b/docs/app-dev/getting-started.md index cffaec501..a480137ca 100644 --- a/docs/app-dev/getting-started.md +++ b/docs/app-dev/getting-started.md @@ -182,7 +182,7 @@ node example/counter.js In another window, reset and start `tendermint`: ```sh -tendermint unsafe-reset-all +tendermint reset unsafe-all tendermint start ``` diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 86b54fc51..e75896f38 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -85,6 +85,7 @@ Note the context/background should be written in the present tense. - [ADR-067: Mempool Refactor](./adr-067-mempool-refactor.md) - [ADR-075: RPC Event Subscription Interface](./adr-075-rpc-subscription.md) - [ADR-076: Combine Spec and Tendermint Repositories](./adr-076-combine-spec-repo.md) +- [ADR-081: Protocol Buffers Management](./adr-081-protobuf-mgmt.md) ### Deprecated @@ -103,12 +104,17 @@ None - [ADR-013: Symmetric-Crypto](./adr-013-symmetric-crypto.md) - [ADR-022: ABCI-Errors](./adr-022-abci-errors.md) - [ADR-030: Consensus-Refactor](./adr-030-consensus-refactor.md) +- [ADR-036: Empty Blocks via ABCI](./adr-036-empty-blocks-abci.md) - [ADR-037: Deliver-Block](./adr-037-deliver-block.md) - [ADR-038: Non-Zero-Start-Height](./adr-038-non-zero-start-height.md) +- [ADR-040: Blockchain Reactor Refactor](./adr-040-blockchain-reactor-refactor.md) - [ADR-041: Proposer-Selection-via-ABCI](./adr-041-proposer-selection-via-abci.md) +- [ADR-042: State Sync Design](./adr-042-state-sync.md) - [ADR-045: ABCI-Evidence](./adr-045-abci-evidence.md) +- [ADR-050: Improved Trusted Peering](./adr-050-improved-trusted-peering.md) - [ADR-057: RPC](./adr-057-RPC.md) +- [ADR-064: Batch Verification](./adr-064-batch-verification.md) - [ADR-069: Node Initialization](./adr-069-flexible-node-initialization.md) -- [ADR-071: Proposer-Based Timestamps](adr-071-proposer-based-timestamps.md) +- [ADR-071: Proposer-Based Timestamps](./adr-071-proposer-based-timestamps.md) +- [ADR-073: Adopt LibP2P](./adr-073-libp2p.md) - [ADR-074: Migrate Timeout Parameters to Consensus Parameters](./adr-074-timeout-params.md) - diff --git a/docs/architecture/adr-074-timeout-params.md b/docs/architecture/adr-074-timeout-params.md index c2869f5a3..22fd784bd 100644 --- a/docs/architecture/adr-074-timeout-params.md +++ b/docs/architecture/adr-074-timeout-params.md @@ -67,7 +67,7 @@ The 8 timeout parameters will be consolidated down to 6. These will be as follow parameters. * `TimeoutCommit` * Same as current `TimeoutCommit`. -* `EnableTimeoutCommitBypass` +* `BypassCommitTimeout` * Same as current `SkipTimeoutCommit`, renamed for clarity. A safe default will be provided by Tendermint for each of these parameters and @@ -149,7 +149,7 @@ message TimeoutParams { google.protobuf.Duration vote = 3; google.protobuf.Duration vote_delta = 4; google.protobuf.Duration commit = 5; - bool enable_commit_timeout_bypass = 6; + bool bypass_commit_timeout = 6; } ``` diff --git a/docs/architecture/adr-081-protobuf-mgmt.md b/docs/architecture/adr-081-protobuf-mgmt.md new file mode 100644 index 000000000..1199cff1b --- /dev/null +++ b/docs/architecture/adr-081-protobuf-mgmt.md @@ -0,0 +1,201 @@ +# ADR 081: Protocol Buffers Management + +## Changelog + +- 2022-02-28: First draft + +## Status + +Accepted + +[Tracking issue](https://github.com/tendermint/tendermint/issues/8121) + +## Context + +At present, we manage the [Protocol Buffers] schema files ("protos") that define +our wire-level data formats within the Tendermint repository itself (see the +[`proto`](../../proto/) directory). Recently, we have been making use of [Buf], +both locally and in CI, in order to generate Go stubs, and lint and check +`.proto` files for breaking changes. + +The version of Buf used at the time of this decision was `v1beta1`, and it was +discussed in [\#7975] and in weekly calls as to whether we should upgrade to +`v1` and harmonize our approach with that used by the Cosmos SDK. The team +managing the Cosmos SDK was primarily interested in having our protos versioned +and easily accessible from the [Buf] registry. + +The three main sets of stakeholders for the `.proto` files and their needs, as +currently understood, are as follows. + +1. Tendermint needs Go code generated from `.proto` files. +2. Consumers of Tendermint's `.proto` files, specifically projects that want to + interoperate with Tendermint and need to generate code for their own + programming language, want to be able to access these files in a reliable and + efficient way. +3. The Tendermint Core team wants to provide stable interfaces that are as easy + as possible to maintain, on which consumers can depend, and to be able to + notify those consumers promptly when those interfaces change. To this end, we + want to: + 1. Prevent any breaking changes from being introduced in minor/patch releases + of Tendermint. Only major version updates should be able to contain + breaking interface changes. + 2. Prevent generated code from diverging from the Protobuf schema files. + +There was also discussion surrounding the notion of automated documentation +generation and hosting, but it is not clear at this time whether this would be +that valuable to any of our stakeholders. What will, of course, be valuable at +minimum would be better documentation (in comments) of the `.proto` files +themselves. + +## Alternative Approaches + +### Meeting stakeholders' needs + +1. Go stub generation from protos. We could use: + 1. [Buf]. This approach has been rather cumbersome up to this point, and it + is not clear what Buf really provides beyond that which `protoc` provides + to justify the additional complexity in configuring Buf for stub + generation. + 2. [protoc] - the Protocol Buffers compiler. +2. Notification of breaking changes: + 1. Buf in CI for all pull requests to *release* branches only (and not on + `master`). + 2. Buf in CI on every pull request to every branch (this was the case at the + time of this decision, and the team decided that the signal-to-noise ratio + for this approach was too low to be of value). +3. `.proto` linting: + 1. Buf in CI on every pull request +4. `.proto` formatting: + 1. [clang-format] locally and a [clang-format GitHub Action] in CI to check + that files are formatted properly on every pull request. +5. Sharing of `.proto` files in a versioned, reliable manner: + 1. Consumers could simply clone the Tendermint repository, check out a + specific commit, tag or branch and manually copy out all of the `.proto` + files they need. This requires no effort from the Tendermint Core team and + will continue to be an option for consumers. The drawback of this approach + is that it requires manual coding/scripting to implement and is brittle in + the face of bigger changes. + 2. Uploading our `.proto` files to Buf's registry on every release. This is + by far the most seamless for consumers of our `.proto` files, but requires + the dependency on Buf. This has the additional benefit that the Buf + registry will automatically [generate and host + documentation][buf-docs-gen] for these protos. + 3. We could create a process that, upon release, creates a `.zip` file + containing our `.proto` files. + +### Popular alternatives to Buf + +[Prototool] was not considered as it appears deprecated, and the ecosystem seems +to be converging on Buf at this time. + +### Tooling complexity + +The more tools we have in our build/CI processes, the more complex and fragile +repository/CI management becomes, and the longer it takes to onboard new team +members. Maintainability is a core concern here. + +### Buf sustainability and costs + +One of the primary considerations regarding the usage of Buf is whether, for +example, access to its registry will eventually become a +paid-for/subscription-based service and whether this is valuable enough for us +and the ecosystem to pay for such a service. At this time, it appears as though +Buf will never charge for hosting open source projects' protos. + +Another consideration was Buf's sustainability as a project - what happens when +their resources run out? Will there be a strong and broad enough open source +community to continue maintaining it? + +### Local Buf usage options + +Local usage of Buf (i.e. not in CI) can be accomplished in two ways: + +1. Installing the relevant tools individually. +2. By way of its [Docker image][buf-docker]. + +Local installation of Buf requires developers to manually keep their toolchains +up-to-date. The Docker option comes with a number of complexities, including +how the file system permissions of code generated by a Docker container differ +between platforms (e.g. on Linux, Buf-generated code ends up being owned by +`root`). + +The trouble with the Docker-based approach is that we make use of the +[gogoprotobuf] plugin for `protoc`. Continuing to use the Docker-based approach +to using Buf will mean that we will have to continue building our own custom +Docker image with embedded gogoprotobuf. + +Along these lines, we could eventually consider coming up with a [Nix]- or +[redo]-based approach to developer tooling to ensure tooling consistency across +the team and for anyone who wants to be able to contribute to Tendermint. + +## Decision + +1. We will adopt Buf for now for proto generation, linting, breakage checking + and its registry (mainly in CI, with optional usage locally). +2. Failing CI when checking for breaking changes in `.proto` files will only + happen when performing minor/patch releases. +3. Local tooling will be favored over Docker-based tooling. + +## Detailed Design + +We currently aim to: + +1. Update to Buf `v1` to facilitate linting, breakage checking and uploading to + the Buf registry. +2. Configure CI appropriately for proto management: + 1. Uploading protos to the Buf registry on every release (e.g. the + [approach][cosmos-sdk-buf-registry-ci] used by the Cosmos SDK). + 2. Linting on every pull request (e.g. the + [approach][cosmos-sdk-buf-linting-ci] used by the Cosmos SDK). The linter + passing should be considered a requirement for accepting PRs. + 3. Checking for breaking changes in minor/patch version releases and failing + CI accordingly - see [\#8003]. + 4. Add [clang-format GitHub Action] to check `.proto` file formatting. Format + checking should be considered a requirement for accepting PRs. +3. Update the Tendermint [`Makefile`](../../Makefile) to primarily facilitate + local Protobuf stub generation, linting, formatting and breaking change + checking. More specifically: + 1. This includes removing the dependency on Docker and introducing the + dependency on local toolchain installation. CI-based equivalents, where + relevant, will rely on specific GitHub Actions instead of the Makefile. + 2. Go code generation will rely on `protoc` directly. + +## Consequences + +### Positive + +- We will still offer Go stub generation, proto linting and breakage checking. +- Breakage checking will only happen on minor/patch releases to increase the + signal-to-noise ratio in CI. +- Versioned protos will be made available via Buf's registry upon every release. + +### Negative + +- Developers/contributors will need to install the relevant Protocol + Buffers-related tooling (Buf, gogoprotobuf, clang-format) locally in order to + build, lint, format and check `.proto` files for breaking changes. + +### Neutral + +## References + +- [Protocol Buffers] +- [Buf] +- [\#7975] +- [protoc] - The Protocol Buffers compiler + +[Protocol Buffers]: https://developers.google.com/protocol-buffers +[Buf]: https://buf.build/ +[\#7975]: https://github.com/tendermint/tendermint/pull/7975 +[protoc]: https://github.com/protocolbuffers/protobuf +[clang-format]: https://clang.llvm.org/docs/ClangFormat.html +[clang-format GitHub Action]: https://github.com/marketplace/actions/clang-format-github-action +[buf-docker]: https://hub.docker.com/r/bufbuild/buf +[cosmos-sdk-buf-registry-ci]: https://github.com/cosmos/cosmos-sdk/blob/e6571906043b6751951a42b6546431b1c38b05bd/.github/workflows/proto-registry.yml +[cosmos-sdk-buf-linting-ci]: https://github.com/cosmos/cosmos-sdk/blob/e6571906043b6751951a42b6546431b1c38b05bd/.github/workflows/proto.yml#L15 +[\#8003]: https://github.com/tendermint/tendermint/issues/8003 +[Nix]: https://nixos.org/ +[gogoprotobuf]: https://github.com/gogo/protobuf +[Prototool]: https://github.com/uber/prototool +[buf-docs-gen]: https://docs.buf.build/bsr/documentation +[redo]: https://redo.readthedocs.io/en/latest/ diff --git a/docs/introduction/what-is-tendermint.md b/docs/introduction/what-is-tendermint.md index 2386626ea..417152d74 100644 --- a/docs/introduction/what-is-tendermint.md +++ b/docs/introduction/what-is-tendermint.md @@ -68,10 +68,10 @@ Tendermint is in essence similar software, but with two key differences: - It is Byzantine Fault Tolerant, meaning it can only tolerate up to a 1/3 of failures, but those failures can include arbitrary behaviour - - including hacking and malicious attacks. -- It does not specify a particular application, like a fancy key-value - store. Instead, it focuses on arbitrary state machine replication, - so developers can build the application logic that's right for them, + including hacking and malicious attacks. +- It does not specify a particular application, like a fancy key-value + store. Instead, it focuses on arbitrary state machine replication, + so developers can build the application logic that's right for them, from key-value store to cryptocurrency to e-voting platform and beyond. ### Bitcoin, Ethereum, etc @@ -104,12 +104,10 @@ to Tendermint, but is more opinionated about how the state is managed, and requires that all application behaviour runs in potentially many docker containers, modules it calls "chaincode". It uses an implementation of [PBFT](http://pmg.csail.mit.edu/papers/osdi99.pdf). -from a team at IBM that is [augmented to handle potentially -non-deterministic -chaincode](https://www.zurich.ibm.com/~cca/papers/sieve.pdf) It is -possible to implement this docker-based behaviour as a ABCI app in -Tendermint, though extending Tendermint to handle non-determinism -remains for future work. +from a team at IBM that is augmented to handle potentially non-deterministic +chaincode It is possible to implement this docker-based behaviour as a ABCI app +in Tendermint, though extending Tendermint to handle non-determinism remains +for future work. [Burrow](https://github.com/hyperledger/burrow) is an implementation of the Ethereum Virtual Machine and Ethereum transaction mechanics, with diff --git a/docs/nodes/configuration.md b/docs/nodes/configuration.md index 2e1f03341..a55bfb63a 100644 --- a/docs/nodes/configuration.md +++ b/docs/nodes/configuration.md @@ -16,7 +16,8 @@ the parameters set with their default values. It will look something like the file below, however, double check by inspecting the `config.toml` created with your version of `tendermint` installed: -```toml# This is a TOML config file. +```toml +# This is a TOML config file. # For more information, see https://github.com/toml-lang/toml # NOTE: Any path below can be absolute (e.g. "/var/myawesomeapp/data") or @@ -33,11 +34,10 @@ like the file below, however, double check by inspecting the proxy-app = "tcp://127.0.0.1:26658" # A custom human readable name for this node -moniker = "ape" +moniker = "sidewinder" - -# Mode of Node: full | validator | seed (default: "validator") -# * validator node (default) +# Mode of Node: full | validator | seed +# * validator node # - all reactors # - with priv_validator_key.json, priv_validator_state.json # * full node @@ -48,11 +48,6 @@ moniker = "ape" # - No priv_validator_key.json, priv_validator_state.json mode = "validator" -# If this node is many blocks behind the tip of the chain, FastSync -# allows them to catchup quickly by downloading blocks in parallel -# and verifying their commits -fast-sync = true - # Database backend: goleveldb | cleveldb | boltdb | rocksdb | badgerdb # * goleveldb (github.com/syndtr/goleveldb - most popular implementation) # - pure go @@ -120,10 +115,10 @@ laddr = "" client-certificate-file = "" # Client key generated while creating certificates for secure connection -validator-client-key-file = "" +client-key-file = "" # Path to the Root Certificate Authority used to sign both client and server certificates -certificate-authority = "" +root-ca-file = "" ####################################################################### @@ -149,26 +144,10 @@ cors-allowed-methods = ["HEAD", "GET", "POST", ] # A list of non simple headers the client is allowed to use with cross-domain requests cors-allowed-headers = ["Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time", ] -# TCP or UNIX socket address for the gRPC server to listen on -# NOTE: This server only supports /broadcast_tx_commit -# Deprecated gRPC in the RPC layer of Tendermint will be deprecated in 0.36. -grpc-laddr = "" - -# Maximum number of simultaneous connections. -# Does not include RPC (HTTP&WebSocket) connections. See max-open-connections -# If you want to accept a larger number than the default, make sure -# you increase your OS limits. -# 0 - unlimited. -# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} -# 1024 - 40 - 10 - 50 = 924 = ~900 -# Deprecated gRPC in the RPC layer of Tendermint will be deprecated in 0.36. -grpc-max-open-connections = 900 - # Activate unsafe RPC commands like /dial-seeds and /unsafe-flush-mempool unsafe = false # Maximum number of simultaneous connections (including WebSocket). -# Does not include gRPC connections. See grpc-max-open-connections # If you want to accept a larger number than the default, make sure # you increase your OS limits. # 0 - unlimited. @@ -182,10 +161,37 @@ max-open-connections = 900 max-subscription-clients = 100 # Maximum number of unique queries a given client can /subscribe to -# If you're using GRPC (or Local RPC client) and /broadcast_tx_commit, set to -# the estimated # maximum number of broadcast_tx_commit calls per block. +# If you're using a Local RPC client and /broadcast_tx_commit, set this +# to the estimated maximum number of broadcast_tx_commit calls per block. max-subscriptions-per-client = 5 +# If true, disable the websocket interface to the RPC service. This has +# the effect of disabling the /subscribe, /unsubscribe, and /unsubscribe_all +# methods for event subscription. +# +# EXPERIMENTAL: This setting will be removed in Tendermint v0.37. +experimental-disable-websocket = false + +# The time window size for the event log. All events up to this long before +# the latest (up to EventLogMaxItems) will be available for subscribers to +# fetch via the /events method. If 0 (the default) the event log and the +# /events RPC method are disabled. +event-log-window-size = "0s" + +# The maxiumum number of events that may be retained by the event log. If +# this value is 0, no upper limit is set. Otherwise, items in excess of +# this number will be discarded from the event log. +# +# Warning: This setting is a safety valve. Setting it too low may cause +# subscribers to miss events. Try to choose a value higher than the +# maximum worst-case expected event load within the chosen window size in +# ordinary operation. +# +# For example, if the window size is 10 minutes and the node typically +# averages 1000 events per ten minutes, but with occasional known spikes of +# up to 2000, choose a value > 2000. +event-log-max-items = 0 + # How long to wait for a tx to be committed during /broadcast_tx_commit. # WARNING: Using a value larger than 10s will result in increasing the # global HTTP write timeout, which applies to all connections and endpoints. @@ -252,63 +258,12 @@ persistent-peers = "" # UPNP port forwarding upnp = false -# Path to address book -# TODO: Remove once p2p refactor is complete -# ref: https:#github.com/tendermint/tendermint/issues/5670 -addr-book-file = "config/addrbook.json" - -# Set true for strict address routability rules -# Set false for private or local networks -addr-book-strict = true - -# Maximum number of inbound peers -# -# TODO: Remove once p2p refactor is complete in favor of MaxConnections. -# ref: https://github.com/tendermint/tendermint/issues/5670 -max-num-inbound-peers = 40 - -# Maximum number of outbound peers to connect to, excluding persistent peers -# -# TODO: Remove once p2p refactor is complete in favor of MaxConnections. -# ref: https://github.com/tendermint/tendermint/issues/5670 -max-num-outbound-peers = 10 - # Maximum number of connections (inbound and outbound). max-connections = 64 # Rate limits the number of incoming connection attempts per IP address. max-incoming-connection-attempts = 100 -# List of node IDs, to which a connection will be (re)established ignoring any existing limits -# TODO: Remove once p2p refactor is complete -# ref: https:#github.com/tendermint/tendermint/issues/5670 -unconditional-peer-ids = "" - -# Maximum pause when redialing a persistent peer (if zero, exponential backoff is used) -# TODO: Remove once p2p refactor is complete -# ref: https:#github.com/tendermint/tendermint/issues/5670 -persistent-peers-max-dial-period = "0s" - -# Time to wait before flushing messages out on the connection -# TODO: Remove once p2p refactor is complete -# ref: https:#github.com/tendermint/tendermint/issues/5670 -flush-throttle-timeout = "100ms" - -# Maximum size of a message packet payload, in bytes -# TODO: Remove once p2p refactor is complete -# ref: https:#github.com/tendermint/tendermint/issues/5670 -max-packet-msg-payload-size = 1400 - -# Rate at which packets can be sent, in bytes/second -# TODO: Remove once p2p refactor is complete -# ref: https:#github.com/tendermint/tendermint/issues/5670 -send-rate = 5120000 - -# Rate at which packets can be received, in bytes/second -# TODO: Remove once p2p refactor is complete -# ref: https:#github.com/tendermint/tendermint/issues/5670 -recv-rate = 5120000 - # Set true to enable the peer-exchange reactor pex = true @@ -323,16 +278,28 @@ allow-duplicate-ip = false handshake-timeout = "20s" dial-timeout = "3s" +# Time to wait before flushing messages out on the connection +# TODO: Remove once MConnConnection is removed. +flush-throttle-timeout = "100ms" + +# Maximum size of a message packet payload, in bytes +# TODO: Remove once MConnConnection is removed. +max-packet-msg-payload-size = 1400 + +# Rate at which packets can be sent, in bytes/second +# TODO: Remove once MConnConnection is removed. +send-rate = 5120000 + +# Rate at which packets can be received, in bytes/second +# TODO: Remove once MConnConnection is removed. +recv-rate = 5120000 + + ####################################################### ### Mempool Configuration Option ### ####################################################### [mempool] -# Mempool version to use: -# 1) "v0" - The legacy non-prioritized mempool reactor. -# 2) "v1" (default) - The prioritized mempool reactor. -version = "v1" - recheck = true broadcast = true @@ -388,22 +355,30 @@ ttl-num-blocks = 0 # starting from the height of the snapshot. enable = false -# RPC servers (comma-separated) for light client verification of the synced state machine and -# retrieval of state data for node bootstrapping. Also needs a trusted height and corresponding -# header hash obtained from a trusted source, and a period during which validators can be trusted. -# -# For Cosmos SDK-based chains, trust-period should usually be about 2/3 of the unbonding time (~2 -# weeks) during which they can be financially punished (slashed) for misbehavior. +# State sync uses light client verification to verify state. This can be done either through the +# P2P layer or RPC layer. Set this to true to use the P2P layer. If false (default), RPC layer +# will be used. +use-p2p = false + +# If using RPC, at least two addresses need to be provided. They should be compatible with net.Dial, +# for example: "host.example.com:2125" rpc-servers = "" + +# The hash and height of a trusted block. Must be within the trust-period. trust-height = 0 trust-hash = "" + +# The trust period should be set so that Tendermint can detect and gossip misbehavior before +# it is considered expired. For chains based on the Cosmos SDK, one day less than the unbonding +# period should suffice. trust-period = "168h0m0s" # Time to spend discovering snapshots before initiating a restore. discovery-time = "15s" -# Temporary directory for state sync snapshot chunks, defaults to the OS tempdir (typically /tmp). -# Will create a new, randomly named directory within, and remove it when done. +# Temporary directory for state sync snapshot chunks, defaults to os.TempDir(). +# The synchronizer will create a new, randomly named directory within this directory +# and remove it when the sync is complete. temp-dir = "" # The timeout duration before re-requesting a chunk, possibly from a different @@ -413,21 +388,6 @@ chunk-request-timeout = "15s" # The number of concurrent chunk and block fetchers to run (default: 4). fetchers = "4" -####################################################### -### Block Sync Configuration Connections ### -####################################################### -[blocksync] - -# If this node is many blocks behind the tip of the chain, BlockSync -# allows them to catchup quickly by downloading blocks in parallel -# and verifying their commits -enable = true - -# Block Sync version to use: -# 1) "v0" (default) - the standard block sync implementation -# 2) "v2" - DEPRECATED, please use v0 -version = "v0" - ####################################################### ### Consensus Configuration Options ### ####################################################### @@ -435,32 +395,12 @@ version = "v0" wal-file = "data/cs.wal/wal" -# How long we wait for a proposal block before prevoting nil -timeout-propose = "3s" -# How much timeout-propose increases with each round -timeout-propose-delta = "500ms" -# How long we wait after receiving +2/3 prevotes for “anything” (ie. not a single block or nil) -timeout-prevote = "1s" -# How much the timeout-prevote increases with each round -timeout-prevote-delta = "500ms" -# How long we wait after receiving +2/3 precommits for “anything” (ie. not a single block or nil) -timeout-precommit = "1s" -# How much the timeout-precommit increases with each round -timeout-precommit-delta = "500ms" -# How long we wait after committing a block, before starting on the new -# height (this gives us a chance to receive some more precommits, even -# though we already have +2/3). -timeout-commit = "1s" - # How many blocks to look back to check existence of the node's consensus votes before joining consensus # When non-zero, the node will panic upon restart # if the same consensus key was used to sign {double-sign-check-height} last blocks. # So, validators should stop the state machine, wait for some blocks, and then restart the state machine to avoid panic. double-sign-check-height = 0 -# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) -skip-timeout-commit = false - # EmptyBlocks mode and possible interval between empty blocks create-empty-blocks = true create-empty-blocks-interval = "0s" @@ -469,6 +409,50 @@ create-empty-blocks-interval = "0s" peer-gossip-sleep-duration = "100ms" peer-query-maj23-sleep-duration = "2s" +### Unsafe Timeout Overrides ### + +# These fields provide temporary overrides for the Timeout consensus parameters. +# Use of these parameters is strongly discouraged. Using these parameters may have serious +# liveness implications for the validator and for the chain. +# +# These fields will be removed from the configuration file in the v0.37 release of Tendermint. +# For additional information, see ADR-74: +# https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-074-timeout-params.md + +# This field provides an unsafe override of the Propose timeout consensus parameter. +# This field configures how long the consensus engine will wait for a proposal block before prevoting nil. +# If this field is set to a value greater than 0, it will take effect. +# unsafe-propose-timeout-override = 0s + +# This field provides an unsafe override of the ProposeDelta timeout consensus parameter. +# This field configures how much the propose timeout increases with each round. +# If this field is set to a value greater than 0, it will take effect. +# unsafe-propose-timeout-delta-override = 0s + +# This field provides an unsafe override of the Vote timeout consensus parameter. +# This field configures how long the consensus engine will wait after +# receiving +2/3 votes in a around. +# If this field is set to a value greater than 0, it will take effect. +# unsafe-vote-timeout-override = 0s + +# This field provides an unsafe override of the VoteDelta timeout consensus parameter. +# This field configures how much the vote timeout increases with each round. +# If this field is set to a value greater than 0, it will take effect. +# unsafe-vote-timeout-delta-override = 0s + +# This field provides an unsafe override of the Commit timeout consensus parameter. +# This field configures how long the consensus engine will wait after receiving +# +2/3 precommits before beginning the next height. +# If this field is set to a value greater than 0, it will take effect. +# unsafe-commit-timeout-override = 0s + +# This field provides an unsafe override of the BypassCommitTimeout consensus parameter. +# This field configures if the consensus engine will wait for the full Commit timeout +# before proceeding to the next height. +# If this field is set to true, the consensus engine will proceed to the next height +# as soon as the node has gathered votes from all of the validators on the network. +# unsafe-bypass-commit-timeout-override = + ####################################################### ### Transaction Indexer Configuration Options ### ####################################################### @@ -543,46 +527,6 @@ transactions every `create-empty-blocks-interval`. For instance, with Tendermint will only create blocks if there are transactions, or after waiting 30 seconds without receiving any transactions. -## Consensus timeouts explained - -There's a variety of information about timeouts in [Running in -production](../tendermint-core/running-in-production.md) - -You can also find more detailed technical explanation in the spec: [The latest -gossip on BFT consensus](https://arxiv.org/abs/1807.04938). - -```toml -[consensus] -... - -timeout-propose = "3s" -timeout-propose-delta = "500ms" -timeout-prevote = "1s" -timeout-prevote-delta = "500ms" -timeout-precommit = "1s" -timeout-precommit-delta = "500ms" -timeout-commit = "1s" -``` - -Note that in a successful round, the only timeout that we absolutely wait no -matter what is `timeout-commit`. - -Here's a brief summary of the timeouts: - -- `timeout-propose` = how long we wait for a proposal block before prevoting - nil -- `timeout-propose-delta` = how much timeout-propose increases with each round -- `timeout-prevote` = how long we wait after receiving +2/3 prevotes for - anything (ie. not a single block or nil) -- `timeout-prevote-delta` = how much the timeout-prevote increases with each - round -- `timeout-precommit` = how long we wait after receiving +2/3 precommits for - anything (ie. not a single block or nil) -- `timeout-precommit-delta` = how much the timeout-precommit increases with - each round -- `timeout-commit` = how long we wait after committing a block, before starting - on the new height (this gives us a chance to receive some more precommits, - even though we already have +2/3) ## P2P settings @@ -594,7 +538,7 @@ This section will cover settings within the p2p section of the `config.toml`. - `pex` = turns the peer exchange reactor on or off. Validator node will want the `pex` turned off so it would not begin gossiping to unknown peers on the network. PeX can also be turned off for statically configured networks with fixed network connectivity. For full nodes on open, dynamic networks, it should be turned on. - `private-peer-ids` = is a comma-separated list of node ids that will _not_ be exposed to other peers (i.e., you will not tell other peers about the ids in this list). This can be filled with a validator's node id. -Recently the Tendermint Team conducted a refactor of the p2p layer. This lead to multiple config paramters being deprecated and/or replaced. +Recently the Tendermint Team conducted a refactor of the p2p layer. This lead to multiple config parameters being deprecated and/or replaced. We will cover the new and deprecated parameters below. ### New Parameters @@ -648,3 +592,27 @@ Example: ```shell $ psql ... -f state/indexer/sink/psql/schema.sql ``` + +## Unsafe Consensus Timeout Overrides + +Tendermint version v0.36 provides a set of unsafe overrides for the consensus +timing parameters. These parameters are provided as a safety measure in case of +unusual timing issues during the upgrade to v0.36 so that an operator may +override the timings for a single node. These overrides will completely be +removed in Tendermint v0.37. + +- `unsafe-propose-override`: How long the Tendermint consensus engine will wait + for a proposal block before prevoting nil. +- `unsafe-propose-delta-override`: How much the propose timeout increase with + each round. +- `unsafe-vote-override`: How long the consensus engine will wait after + receiving +2/3 votes in a round. +- `unsafe-vote-delta-override`: How much the vote timeout increases with each + round. +- `unsafe-commit-override`: How long the consensus engine will wait after + receiving +2/3 precommits before beginning the next height. +- `unsafe-bypass-commit-timeout-override`: Configures if the consensus engine + will wait for the full commit timeout before proceeding to the next height. If + this field is set to true, the consensus engine will proceed to the next + height as soon as the node has gathered votes from all of the validators on + the network. diff --git a/docs/nodes/remote-signer.md b/docs/nodes/remote-signer.md index e7dfccacd..39a38e1b7 100644 --- a/docs/nodes/remote-signer.md +++ b/docs/nodes/remote-signer.md @@ -37,7 +37,7 @@ There are two ways to generate certificates, [openssl](https://www.openssl.org/) - Install `Certstrap`: ```sh - go get github.com/square/certstrap@v1.2.0 + go install github.com/square/certstrap@v1.2.0 ``` - Create certificate authority for self signing. diff --git a/docs/package-lock.json b/docs/package-lock.json index 94c756f54..447c8c27d 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -3037,9 +3037,9 @@ } }, "node_modules/async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dependencies": { "lodash": "^4.17.14" } @@ -8876,9 +8876,9 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "node_modules/mississippi": { "version": "3.0.0", @@ -16588,9 +16588,9 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "requires": { "lodash": "^4.17.14" } @@ -21113,9 +21113,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "mississippi": { "version": "3.0.0", diff --git a/docs/presubmit.sh b/docs/presubmit.sh new file mode 100755 index 000000000..19e931a4f --- /dev/null +++ b/docs/presubmit.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# +# This script verifies that each document in the docs and architecture +# directory has a corresponding table-of-contents entry in its README file. +# +# This can be run manually from the command line. +# It is also run in CI via the docs-toc.yml workflow. +# +set -euo pipefail + +readonly base="$(dirname $0)" +cd "$base" + +readonly workdir="$(mktemp -d)" +trap "rm -fr -- '$workdir'" EXIT + +checktoc() { + local dir="$1" + local tag="$2"'-*-*' + local out="$workdir/${dir}.out.txt" + ( + cd "$dir" >/dev/null + find . -type f -maxdepth 1 -name "$tag" -not -exec grep -q "({})" README.md ';' -print + ) > "$out" + if [[ -s "$out" ]] ; then + echo "-- The following files in $dir lack a ToC entry: +" + cat "$out" + return 1 + fi +} + +err=0 + +# Verify that each RFC and ADR has a ToC entry in its README file. +checktoc architecture adr || ((err++)) +checktoc rfc rfc || ((err++)) + +exit $err diff --git a/docs/rfc/README.md b/docs/rfc/README.md index 8905c0160..f2ad6ad69 100644 --- a/docs/rfc/README.md +++ b/docs/rfc/README.md @@ -52,5 +52,7 @@ sections. - [RFC-012: Event Indexing Revisited](./rfc-012-custom-indexing.md) - [RFC-013: ABCI++](./rfc-013-abci++.md) - [RFC-014: Semantic Versioning](./rfc-014-semantic-versioning.md) +- [RFC-015: ABCI++ Tx Mutation](./rfc-015-abci++-tx-mutation.md) +- [RFC-019: Configuration File Versioning](./rfc-019-config-version.md) diff --git a/docs/rfc/rfc-015-abci++-tx-mutation.md b/docs/rfc/rfc-015-abci++-tx-mutation.md new file mode 100644 index 000000000..3c7854ed3 --- /dev/null +++ b/docs/rfc/rfc-015-abci++-tx-mutation.md @@ -0,0 +1,261 @@ +# RFC 015: ABCI++ TX Mutation + +## Changelog + +- 23-Feb-2022: Initial draft (@williambanfield). +- 28-Feb-2022: Revised draft (@williambanfield). + +## Abstract + +A previous version of the ABCI++ specification detailed a mechanism for proposers to replace transactions +in the proposed block. This scheme required the proposer to construct new transactions +and mark these new transactions as replacing other removed transactions. The specification +was ambiguous as to how the replacement may be communicated to peer nodes. +This RFC discusses issues with this mechanism and possible solutions. + +## Background + +### What is the proposed change? + +A previous version of the ABCI++ specification proposed mechanisms for adding, removing, and replacing +transactions in a proposed block. To replace a transaction, the application running +`ProcessProposal` could mark a transaction as replaced by other application-supplied +transactions by returning a new transaction marked with the `ADDED` flag setting +the `new_hashes` field of the removed transaction to contain the list of transaction hashes +that replace it. In that previous specification for ABCI++, the full use of the +`new_hashes` field is left somewhat ambiguous. At present, these hashes are not +gossiped and are not eventually included in the block to signal replacement to +other nodes. The specification did indicate that the transactions specified in +the `new_hashes` field will be removed from the mempool but it's not clear how +peer nodes will learn about them. + +### What systems would be affected by adding transaction replacement? + +The 'transaction' is a central building block of a Tendermint blockchain, so adding +a mechanism for transaction replacement would require changes to many aspects of Tendermint. + +The following is a rough list of the functionality that this mechanism would affect: + +#### Transaction indexing + +Tendermint's indexer stores transactions and transaction results using the hash of the executed +transaction [as the key][tx-result-index] and the ABCI results and transaction bytes as the value. + +To allow transaction replacement, the replaced transactions would need to stored as well in the +indexer, likely as a mapping of original transaction to list of transaction hashes that replaced +the original transaction. + +#### Transaction inclusion proofs + +The result of a transaction query includes a Merkle proof of the existence of the +transaction in the block chain. This [proof is built][inclusion-proof] as a merkle tree +of the hashes of all of the transactions in the block where the queried transaction was executed. + +To allow transaction replacement, these proofs would need to be updated to prove +that a replaced transaction was included by replacement in the block. + +#### RPC-based transaction query parameters and results + +Tendermint's RPC allows clients to retrieve information about transactions via the +`/tx_search` and `/tx` RPC endpoints. + +RPC query results containing replaced transactions would need to be updated to include +information on replaced transactions, either by returning results for all of the replaced +transactions, or by including a response with just the hashes of the replaced transactions +which clients could proceed to query individually. + +#### Mempool transaction removal + +Additional logic would need to be added to the Tendermint mempool to clear out replaced +transactions after each block is executed. Tendermint currently removes executed transactions +from the mempool, so this would be a pretty straightforward change. + +## Discussion + +### What value may be added to Tendermint by introducing transaction replacement? + +Transaction replacement would would enable applications to aggregate or disaggregate transactions. + +For aggregation, a set of transactions that all related work, such as transferring +tokens between the same two accounts, could be replaced with a single transaction, +i.e. one that transfers a single sum from one account to the other. +Applications that make frequent use of aggregation may be able to achieve a higher throughput. +Aggregation would decrease the space occupied by a single client-submitted transaction in the block, allowing +more client-submitted transactions to be executed per block. + +For disaggregation, a very complex transaction could be split into multiple smaller transactions. +This may be useful if an application wishes to perform more fine-grained indexing on intermediate parts +of a multi-part transaction. + +### Drawbacks to transaction replacement + +Transaction replacement would require updating and shimming many of the places that +Tendermint records and exposes information about executed transactions. While +systems within Tendermint could be updated to account for transaction replacement, +such a system would leave new issues and rough edges. + +#### No way of guaranteeing correct replacement + +If a user issues a transaction to the network and the transaction is replaced, the +user has no guarantee that the replacement was correct. For example, suppose a set of users issue +transactions A, B, and C and they are all aggregated into a new transaction, D. +There is nothing guaranteeing that D was constructed correctly from the inputs. +The only way for users to ensure D is correct would be if D contained all of the +information of its constituent transactions, in which case, nothing is really gained by the replacement. + +#### Replacement transactions not signed by submitter + +Abstractly, Tendermint simply views transactions as a ball of bytes and therefore +should be fine with replacing one for another. However, many applications require +that transactions submitted to the chain be signed by some private key to authenticate +and authorize the transaction. Replaced transactions could not be signed by the +submitter, only by the application node. Therefore, any use of transaction replacement +could not contain authorization from the submitter and would either need to grant +application-submitted transactions power to perform application logic on behalf +of a user without their consent. + +Granting this power to application-submitted transactions would be very dangerous +and therefore might not be of much value to application developers. +Transaction replacement might only be really safe in the case of application-submitted +transactions or for transactions that require no authorization. For such transactions, +it's quite not quite clear what the utility of replacement is: the application can already +generate any transactions that it wants. The fact that such a transaction was a replacement +is not particularly relevant to participants in the chain since the application is +merely replacing its own transactions. + +#### New vector for censorship + +Depending on the implementation, transaction replacement may allow a node signal +to the rest of the chain that some transaction should no longer be considered for execution. +Honest nodes will use the replacement mechanism to signal that a transaction has been aggregated. +Malicious nodes will be granted a new vector for censoring transactions. +There is no guarantee that a replaced transactions is actually executed at all. +A malicious node could censor a transaction by simply listing it as replaced. +Honest nodes seeing the replacement would flush the transaction from their mempool +and not execute or propose it it in later blocks. + +### Transaction tracking implementations + +This section discusses possible ways to flesh out the implementation of transaction replacement. +Specifically, this section proposes a few alternative ways that Tendermint blockchains could +track and store transaction replacements. + +#### Include transaction replacements in the block + +One option to track transaction replacement is to include information on the +transaction replacement within the block. An additional structure may be added +the block of the following form: + +```proto +message Block { +... + repeated Replacement replacements = 5; +} + +message Replacement { + bytes included_tx_key = 1; + repeated bytes replaced_txs_keys = 2; +} +``` + +Applications executing `PrepareProposal` would return the list of replacements and +Tendermint would include an encoding of these replacements in the block that is gossiped +and committed. + +Tendermint's transaction indexing would include a new mapping for each replaced transaction +key to the committed transaction. +Transaction inclusion proofs would be updated to include these additional new transaction +keys in the Merkle tree and queries for transaction hashes that were replaced would return +information indicating that the transaction was replaced along with the hash of the +transaction that replaced it. + +Block validation of gossiped blocks would be updated to check that each of the +`included_txs_key` matches the hash of some transaction in the proposed block. + +Implementing the changes described in this section would allow Tendermint to gossip +and index transaction replacements as part of block propagation. These changes would +still require the application to certify that the replacements were valid. This +validation may be performed in one of two ways: + +1. **Applications optimistically trust that the proposer performed a legitimate replacement.** + +In this validation scheme, applications would not verify that the substitution +is valid during consensus and instead simply trust that the proposer is correct. +This would have the drawback of allowing a malicious proposer to remove transactions +it did not want executed. + +2. **Applications completely validate transaction replacement.** + +In this validation scheme, applications that allow replacement would check that +each listed replaced transaction was correctly reflected in the replacement transaction. +In order to perform such validation, the node would need to have the replaced transactions +locally. This could be accomplished one of a few ways: by querying the mempool, +by adding an additional p2p gossip channel for transaction replacements, or by including the replaced transactions +in the block. Replacement validation via mempool querying would require the node +to have received all of the replaced transactions in the mempool which is far from +guaranteed. Adding an additional gossip channel would make gossiping replaced transactions +a requirement for consensus to proceed, since all nodes would need to receive all replacement +messages before considering a block valid. Finally, including replaced transactions in +the block seems to obviate any benefit gained from performing a transaction replacement +since the replaced transaction and the original transactions would now both appear in the block. + +#### Application defined transaction replacement + +An additional option for allowing transaction replacement is to leave it entirely as a responsibility +of the application. The `PrepareProposal` ABCI++ call allows for applications to add +new transactions to a proposed block. Applications that wished to implement a transaction +replacement mechanism would be free to do so without the newly defined `new_hashes` field. +Applications wishing to implement transaction replacement would add the aggregated +transactions in the `PrepareProposal` response, and include one additional bookkeeping +transaction that listed all of the replacements, with a similar scheme to the `new_hashes` +field described in ABCI++. This new bookkeeping transaction could be used by the +application to determine which transactions to clear from the mempool in future calls +to `CheckTx`. + +The meaning of any transaction in the block is completely opaque to Tendermint, +so applications performing this style of replacement would not be able to have the replacement +reflected in any most of Tendermint's transaction tracking mechanisms, such as transaction indexing +and the `/tx` endpoint. + +#### Application defined Tx Keys + +Tendermint currently uses cryptographic hashes, SHA256, as a key for each transaction. +As noted in the section on systems that would require changing, this key is used +to identify the transaction in the mempool, in the indexer, and within the RPC system. + +An alternative approach to allowing `ProcessProposal` to specify a set of transaction +replacements would be instead to allow the application to specify an additional key or set +of keys for each transaction during `ProcessProposal`. This new `secondary_keys` set +would be included in the block and therefore gossiped during block propagation. +Additional RPC endpoints could be exposed to query by the application-defined keys. + +Applications wishing to implement replacement would leverage this new field by providing the +replaced transaction hashes as the `secondary_keys` and checking their validity during +`ProcessProposal`. During `RecheckTx` the application would then be responsible for +clearing out transactions that matched the `secondary_keys`. + +It is worth noting that something like this would be possible without `secondary_keys`. +An application wishing to implement a system like this one could define a replacement +transaction, as discussed in the section on application-defined transaction replacement, +and use a custom [ABCI event type][abci-event-type] to communicate that the replacement should +be indexed within Tendermint's ABCI event indexing. + +### Complexity to value-add tradeoff + +It is worth remarking that adding a system like this may introduce a decent amount +of new complexity into Tendermint. An approach that leaves much of the replacement +logic to Tendermint would require altering the core transaction indexing and querying +data. In many of the cases listed, a system for transaction replacement is possible +without explicitly defining it as part of `PrepareProposal`. Since applications +can now add transactions during `PrepareProposal` they can and should leverage this +functionality to include additional bookkeeping transactions in the block. It may +be worth encouraging applications to discover new and interesting ways to leverage this +power instead of immediately solving the problem for them. + +### References + +[inclusion-proof]: https://github.com/tendermint/tendermint/blob/0fcfaa4568cb700e27c954389c1fcd0b9e786332/types/tx.go#L67 +[tx-serach-result]: https://github.com/tendermint/tendermint/blob/0fcfaa4568cb700e27c954389c1fcd0b9e786332/rpc/coretypes/responses.go#L267 +[tx-rpc-func]: https://github.com/tendermint/tendermint/blob/0fcfaa4568cb700e27c954389c1fcd0b9e786332/internal/rpc/core/tx.go#L21 +[tx-result-index]: https://github.com/tendermint/tendermint/blob/0fcfaa4568cb700e27c954389c1fcd0b9e786332/internal/state/indexer/tx/kv/kv.go#L90 +[abci-event-type]: https://github.com/tendermint/tendermint/blob/0fcfaa4568cb700e27c954389c1fcd0b9e786332/abci/types/types.pb.go#L3168 diff --git a/docs/rfc/rfc-019-config-version.md b/docs/rfc/rfc-019-config-version.md new file mode 100644 index 000000000..3dfd7840b --- /dev/null +++ b/docs/rfc/rfc-019-config-version.md @@ -0,0 +1,400 @@ +# RFC 019: Configuration File Versioning + +## Changelog + +- 19-Apr-2022: Initial draft (@creachadair) +- 20-Apr-2022: Updates from review feedback (@creachadair) + +## Abstract + +Updating configuration settings is an essential part of upgrading an existing +node to a new version of the Tendermint software. Unfortunately, it is also +currently a very manual process. This document discusses some of the history of +changes to the config format, actions we've taken to improve the tooling for +configuration upgrades, and additional steps we may want to consider. + +## Background + +A Tendermint node reads configuration settings at startup from a TOML formatted +text file, typically named `config.toml`. The contents of this file are defined +by the [`github.com/tendermint/tendermint/config`][config-pkg]. + +Although many settings in this file remain valid from one version of Tendermint +to the next, new versions of Tendermint often add, update, and remove settings. +These changes often require manual intervention by operators who are upgrading +their nodes. + +I propose we should provide better tools and documentation to help operators +make configuration changes correctly during version upgrades. Ideally, as much +as possible of any configuration file update should be automated, and where +that is not possible or practical, we should provide clear, explicit directions +for what steps need to be taken manually. Moreover, when the node discovers +incorrect or invalid configuration, we should improve the diagnostics it emits +so that the operator can quickly and easily find the relevant documentation, +without having to grep through source code. + +## Discussion + +By convention, we are supposed to document required changes to the config file +in the `UPGRADING.md` file for the release that introduces them. Although we +have mostly done this, the level of detail in the upgrading instructions is +often insufficient for an operator to correctly update their file. + +The updates vary widely in complexity: Operators may need to add new required +settings, update obsolete values for existing settings, move or rename existing +settings within the file, or remove obsolete settings (which are thus invalid). +Here are a few examples of each of these cases: + +- **New required settings:** Tendermint v0.35 added a new top-level `mode` + setting that determines whether a node runs as a validator, a full node, or a + seed node. The default value is `"full"`, which means the operator of a + validator must manually add `mode = "validator"` (or set the `--mode` flag on + the command line) for their node to come up in the correct mode. + +- **Updated obsolete values:** Tendermint v0.35 removed support for versions + `"v1"` and `"v2"` of the blocksync (formerly "fastsync") protocol, requiring + any node using either of those values to update to `"v0"`. + +- **Moved/renamed settings:** Version v0.34 moved the top-level `pprof_laddr` + setting under the `[rpc]` section. + + Version v0.35 renamed every setting in the file from `snake_case` to + `kebab-case`, moved the top-level `fast_sync` setting into the `[blocksync]` + section as (itself renamed from `[fastsync]`), and moved all the top-level + `priv-validator-*` settings under a new `[priv-validator]` section with their + prefix trimmed off. + +- **Removed obsolete settings:** Version v0.34 removed the `index_all_keys` and + `index_keys` settings from the `[tx_index]` section; version v0.35 removed + the `wal-dir` setting from the `[mempool]` section, and version v0.36 removed + the `[blocksync]` section entirely. + +While many of these changes are mentioned in the config section of the upgrade +instructions, some are not mentioned at all, or are hidden in other parts of +the doc. For instance, the v0.34 `pprof_laddr` change was documented only as an +RPC flag change. (A savvy reader might realize that the flag `--rpc.pprof_laddr` +implies a corresponding config section, but it omits the related detail that +there was a top-level setting that's been renamed). The lesson here is not +that the docs are bad, but to point out that prose is not the most efficient +format to convey detailed changes like this. The upgrading instructions are +still valuable for the human reader to understand what to expect. + +### Concrete Steps + +As part of the v0.36 development cycle, we spent some time reverse-engineering +the configuration changes since the v0.34 release and built an experimental +command-line tool called [`confix`][confix], whose job it is to automatically +update the settings in a `config.toml` file to the latest version. We also +backported a version of this tool into the v0.35.x branch at release v0.35.4. + +This tool should work fine for configuration files created by Tendermint v0.34 +and later, but does not (yet) know how to handle changes from prior versions of +Tendermint. Part of the difficulty for older versions is simply logistical: To +figure out which changes to apply, we need to understand something about the +version that made the file, as well as the version we're converting it to. + +> **Discussion point:** In the future we might want to consider incorporating +> this into the node CLI directly, but we're keeping it separate for now until +> we can get some feedback from operators. + +For the experiment, we handled this by carefully searching the history of +config format changes for shibboleths to bound the version: For example, the +`[fastsync]` section was added in Tendermint v0.32 and renamed `[blocksync]` in +Tendermint v0.35. So if we see a `[fastsync]` section, we have some confidence +that the file was created by v0.32, v0.33, or v0.34. + +But such signals are delicate: The `[blocksync]` section was removed in v0.36, +so if we do not find `[fastsync]`, we cannot conclude from that alone that the +file is from v0.31 or earlier -- we have to look for corroborating details. +While such "sniffing" tactics are fine for an experiment, they aren't as robust +as we might like. + +This is especially relevant for configuration files that may have already been +manually upgraded across several versions by the time we are asked to update +them again. Another related concern is that we'd like to make sure conversion +is idempotent, so that it would be safe to rerun the tool over an +already-converted file without breaking anything. + +### Config Versioning + +One obvious tactic we could use for future releases is add a version marker to +the config file. This would give tools like `confix` (and the node itself) a +way to calibrate their expectations. Rather than being a version for the file +itself, however, this version marker would indicate which version of Tendermint +is needed to read the file. + +Provisionally, this might look something like: + +```toml +# THe minimum version of Tendermint compatible with the contents of +# this configuration file. +config-version = 'v0.35' +``` + +When initializing a new node, Tendermint would populate this field with its own +version (e.g., `v0.36`). When conducting an upgrade, tools like `confix` can +then use this to decide which conversions are valid, and then update the value +accordingly. After converting a file marked `'v0.35'` to`'v0.37'`, the +conversion tool sets the file's `config-version` to reflect its compatibility. + +> **Discussion point:** This example presumes we would keep config files +> compatible within a given release cycle, e.g., all of v0.36.x. We could also +> use patch numbers here, if we think there's some reason to permit changes +> that would require config file edits at that granularity. I don't think we +> should, but that's a design question to consider. + +Upon seeing an up-to-date version marker, the conversion tool can simply exit +with a diagnostic like "this file is already up-to-date", rather than sniffing +the keyspace and potentially introducing errors. In addition, this would let a +tool detect config files that are _newer_ than the one it understands, and +issue a safe diagnostic rather than doing something wrong. Plus, besides +avoiding potentially unsafe conversions, this would also serve as +human-readable documentation that the file is up-to-date for a given version. + +Adding a config version would not address the problem of how to convert files +created by older versions of Tendermint, but it would at least help us build +more robust config tooling going forward. + +### Stability and Change + +In light of the discussion so far, it is natural to examine why we make so many +changes to the configuration file from one version to the next, and whether we +could reduce friction by being more conservative about what we make +configurable, what config changes we make over time, and how we roll them out. + +Some changes, like renaming everything from snake case to kebab case, are +entirely gratuitous. We could safely agree not to make those kinds of changes. +Apart from that obvious case, however, many other configuration settings +provide value to node operators in cases where there is no simple, universal +setting that matches every application. + +Taking a high-level view, there are several broad reasons why we might want to +make changes to configuration settings: + +- **Lessons learned:** Configuration settings are a good way to try things out + in production, before making more invasive changes to the consensus protocol. + + For example, up until Tendermint v0.35, consensus timeouts were specified as + per-node configuration settings (e.g., `timeout-precommit` et al.). This + allowed operators to tune these values for the needs of their network, but + had the downside that individually-misconfigured nodes could stall consensus. + + Based on that experience, these timeouts have been deprecated in Tendermint + v0.36 and converted to consensus parameters, to be consistent across all + nodes in the network. + +- **Migration & experimentation:** Introducing new features and updating old + features can complicate migration for existing users of the software. + Temporary or "experimental" configuration settings can be a valuable way to + mitigate that friction. + + For example, Tendermint v0.36 introduces a new RPC event subscription + endpoint (see [ADR 075][adr075]) that will eventually replace the existing + webwocket-based interface. To give users time to migrate, v0.36 adds an + `experimental-disable-websocket` setting, defaulted to `false`, that allows + operators to selectively disable the websocket API for testing purposes + during the conversion. This setting is designed to be removed in v0.37, when + the old interface is no longer supported. + +- **Ongoing maintenance:** Sometimes configuration settings become obsolete, + and the cost of removing them trades off against the potential risks of + leaving a non-functional or deprecated knob hooked up indefinitely. + + For example, Tendermint v0.35 deprecated two alternate implementations of the + blocksync protocol, one of which was deleted entirely (`v1`) and one of which + was scheduled for removal (`v2`). The `blocksync.version` setting, which had + been added as a migration aid, became obsolete and needed to be updated. + + Despite our best intentions, sometimes engineering designs do not work out. + It's just as important to leave room to back out of changes we have since + reconsidered, as it is to support migrations forward onto new and improved + code. + +- **Clarity and legibility:** Besides configuring the software, another + important purpose of a config file is to document intent for the humans who + operate and maintain the software. Operators need adjust settings to keep the + node running, and developers need to know what options were in use when + something goes wrong so they can diagnose and fix bugs. The legibility of a + config file as a _human_ artifact is also thus important. + + For example, Tendermint v0.35 moved settings related to validator private + keys from the top-level section of the configuration file to their own + designated `[priv-validator]` section. Although this change did not make any + difference to the meaning of those settings, it made the organization of the + file easier to understand, and allowed the names of the individual settings + to be simplified (e.g., `priv-validator-key-file` became simply `key-file` in + the new section). + + Although such changes are "gratuitous" with respect to the software, there is + often value in making things more legible for the humans. While there is no + simple rule to define the line, the Potter Stewart principle can be used with + due care. + +Keeping these examples in mind, we can and should take reasonable steps to +avoid churn in the configuration file across versions where we can. However, we +must also accept that part of the reason for _having_ a config file is to allow +us flexibility elsewhere in the design. On that basis, we should not attempt +to be too dogmatic about config changes either. Unlike changes in the block +protocol, for example, which affect every user of every network that adopts +them, config changes are relatively self-contained. + +There are few guiding principles I think we can use to strike a sensible +balance: + +1. **No gratuitous changes.** Aesthetic changes that do not enhance legibility, + avert confusion, or clarity documentation, should be entirely avoided. + +2. **Prefer mechanical changes.** Whenever it is practical, change settings in + a way that can be updated by a tool without operator judgement. This implies + finding safe, universal defaults for new settings, and not changing the + default values of existing settings. + + Even if that means we have to make multiple changes (e.g., add a new setting + in the current version, deprecate the old one, and remove the old one in the + next version) it's preferable if we can mechanize each step. + +3. **Clearly signal intent.** When adding temporary or experimental settings, + they should be clearly named and documented as such. Use long names and + suggestive prefixes (e.g., `experimental-*`) so that they stand out when + read in the config file or printed in logs. + + Relatedly, using temporary or experimental settings should cause the + software to emit diagnostic logs at runtime. These log messages should be + easy to grep for, and should contain pointers to more complete documentation + (say, issue numbers or URLs) that the operator can read, as well as a hint + about when the setting is expected to become invalid. For example: + + ``` + WARNING: Websocket RPC access is deprecated and will be removed in + Tendermint v0.37. See https://tinyurl.com/adr075 for more information. + ``` + +4. **Consider both directions.** When adding a configuration setting, take some + time during the implementation process to think about how the setting could + be removed, as well as how it will be rolled out. This applies even for + settings we imagine should be permanent. Experience may cause is to rethink + our original design intent more broadly than we expected. + + This does not mean we have to spend a long time picking nits over the design + of every setting; merely that we should convince ourselves we _could_ undo + it without making too big a mess later. Even a little extra effort up front + can sometimes save a lot. + +## References + +- [Tendermint `config` package][config-pkg] +- [`confix` command-line tool][confix] +- [`condiff` command-line tool][condiff] +- [Configuration update plan][plan] +- [ADR 075: RPC Event Subscription Interface][adr075] + +[config-pkg]: https://godoc.org/github.com/tendermint/tendermint/config +[confix]: https://github.com/tendermint/tendermint/blob/master/scripts/confix +[condiff]: https://github.com/tendermint/tendermint/blob/master/scripts/confix/condiff +[plan]: https://github.com/tendermint/tendermint/blob/master/scripts/confix/plan.go +[testdata]: https://github.com/tendermint/tendermint/blob/master/scripts/confix/testdata +[adr075]: https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-075-rpc-subscription.md + +## Appendix: Research Notes + +Discovering when various configuration settings were added, updated, and +removed turns out to be surprisingly tedious. To solve this puzzle, we had to +answer the following questions: + +1. What changes were made between v0.x and v0.y? This is further complicated by + cases where we have backported config changes into the middle of an earlier + release cycle (e.g., `psql-conn` from v0.35.x into v0.34.13). + +2. When during the development cycle were those changes made? This allows us to + recognize features that were backported into a previous release. + +3. What were the default values of the changed settings, and did they change at + all during or across the release boundary? + +Each step of the [configuration update plan][plan] is commented with a link to +one or more PRs where that change was made. The sections below discuss how we +found these references. + +### Tracking Changes Across Releases + +To figure out what changed between two releases, we built a tool called +[`condiff`][condiff], which performs a "keyspace" diff of two TOML documents. +This diff respects the structure of the TOML file, but ignores comments, blank +lines, and configuration values, so that we can see what was added and removed. + +To use it, run: + +```shell +go run ./scripts/confix/condiff old.toml new.toml +``` + +This tool works on any TOML documents, but for our purposes we needed +Tendermint `config.toml` files. The easiest way to get these is to build the +node binary for your version of interest, run `tendermint init` on a clean home +directory, and copy the generated config file out. The [`testdata`][testdata] +directory for the `confix` tool has configs generated from the heads of each +release branch from v0.31 through v0.35. + +If you want to reproduce this yourself, it looks something like this: + +```shell +# Example for Tendermint v0.32. +git checkout --track origin/v0.32.x +go get golang.org/x/sys/unix +go mod tidy +make build +rm -fr -- tmhome +./build/tendermint --home=tmhome init +cp tmhome/config/config.toml config-v32.toml +``` + +Be advised that the further back you go, the more idiosyncrasies you will +encounter. For example, Tendermint v0.31 and earlier predate Go modules (v0.31 +used dep), and lack backport branches. And you may need to do some editing of +Makefile rules once you get back into the 20s. + +Note that when diffing config files across the v0.34/v0.35 gap, the swap from +`snake_case` to `kebab-case` makes it look like everything changed. The +`condiff` tool has a `-desnake` flag that normalizes all the keys to kebab case +in both inputs before comparison. + +### Locating Additions and Deletions + +To figure out when a configuration setting was added or removed, your tool of +choice is `git bisect`. The only tricky part is finding the endpoints for the +search. If the transition happened within a release, you can use that +release's backport branch as the endpoint (if it has one, e.g., `v0.35.x`). + +However, the start point can be more problematic. The backport branches are not +ancestors of `master` or of each other, which means you need to find some point +in history _prior_ to the change but still attached to the mainline. For recent +releases there is a dev root (e.g., `v0.35.0-dev`, `v0.34.0-dev1`, etc.). These +are not named consistently, but you can usually grep the output of `git tag` to +find them. + +In the worst case you could try starting from the root commit of the repo, but +that turns out not to work in all cases. We've done some branching shenanigans +over the years that mean the root is not a direct ancestor of all our release +branches. When you find this you will probably swear a lot. I did. + +Once you have a start and end point (say, `v0.35.0-dev` and `master`), you can +bisect in the usual way. I use `git grep` on the `config` directory to check +whether the case I am looking for is present. For example, to find when the +`[fastsync]` section was removed: + +```shell +# Setup: +git checkout master +git bisect start +git bisect bad # it's not present on tip of master. +git bisect good v0.34.0-dev1 # it was present at the start of v0.34. +``` + +```shell +# Now repeat this until it gives you a specific commit: +if git grep -q '\[fastsync\]' config ; then git bisect good ; else git bisect bad ; fi +``` + +The above example finds where a config was removed: To find where a setting was +added, do the same thing except reverse the sense of the test (`if ! git grep -q +...`). diff --git a/docs/tendermint-core/consensus/proposer-based-timestamps.md b/docs/tendermint-core/consensus/proposer-based-timestamps.md index def42dc20..7f98f10d6 100644 --- a/docs/tendermint-core/consensus/proposer-based-timestamps.md +++ b/docs/tendermint-core/consensus/proposer-based-timestamps.md @@ -1,4 +1,6 @@ ---- order: 3 --- +--- +order: 3 +--- # PBTS @@ -6,7 +8,7 @@ algorithm added to Tendermint in the v0.36 release. It outlines the core functionality as well as the parameters and constraints of the this algorithm. -## Algorithm Overview +## Algorithm Overview The PBTS algorithm defines a way for a Tendermint blockchain to create block timestamps that are within a reasonable bound of the clocks of the validators on @@ -91,4 +93,3 @@ proposed by their peers to be valid either. * [The PBTS specification](https://github.com/tendermint/tendermint/blob/master/spec/consensus/proposer-based-timestamp/README.md) contains all of the details of the algorithm. - diff --git a/docs/tools/debugging/proposer-based-timestamps-runbook.md b/docs/tools/debugging/proposer-based-timestamps-runbook.md new file mode 100644 index 000000000..a817bd29e --- /dev/null +++ b/docs/tools/debugging/proposer-based-timestamps-runbook.md @@ -0,0 +1,216 @@ +--- +order: 3 +--- + +# Proposer-Based Timestamps Runbook + +Version v0.36 of Tendermint added new constraints for the timestamps included in +each block created by Tendermint. The new constraints mean that validators may +fail to produce valid blocks or may issue `nil` `prevotes` for proposed blocks +depending on the configuration of the validator's local clock. + +## What is this document for? + +This document provides a set of actionable steps for application developers and +node operators to diagnose and fix issues related to clock synchronization and +configuration of the Proposer-Based Timestamps [SynchronyParams](https://github.com/tendermint/tendermint/blob/master/spec/core/data_structures.md#synchronyparams). + +Use this runbook if you observe that validators are frequently voting `nil` for a block that the rest +of the network votes for or if validators are frequently producing block proposals +that are not voted for by the rest of the network. + +## Requirements + +To use this runbook, you must be running a node that has the [Prometheus metrics endpoint enabled](https://github.com/tendermint/tendermint/blob/master/docs/nodes/metrics.md) +and the Tendermint RPC endpoint enabled and accessible. + +It is strongly recommended to also run a Prometheus metrics collector to gather and +analyze metrics from the Tendermint node. + +## Debugging a Single Node + +If you observe that a single validator is frequently failing to produce blocks or +voting nil for proposals that other validators vote for and suspect it may be +related to clock synchronization, use the following steps to debug and correct the issue. + +### Check Timely Metric + +Tendermint exposes a histogram metric for the difference between the timestamp in the proposal +the and the time read from the node's local clock when the proposal is received. + +The histogram exposes multiple metrics on the Prometheus `/metrics` endpoint called +* `tendermint_consensus_proposal_timestamp_difference_bucket`. +* `tendermint_consensus_proposal_timestamp_difference_sum`. +* `tendermint_consensus_proposal_timestamp_difference_count`. + +Each metric is also labeled with the key `is_timely`, which can have a value of +`true` or `false`. + +#### From the Prometheus Collector UI + +If you are running a Prometheus collector, navigate to the query web interface and select the 'Graph' tab. + +Issue a query for the following: + +``` +tendermint_consensus_proposal_timestamp_difference_count{is_timely="false"} / +tendermint_consensus_proposal_timestamp_difference_count{is_timely="true"} +``` + +This query will graph the ratio of proposals the node considered timely to those it +considered untimely. If the ratio is increasing, it means that your node is consistently +seeing more proposals that are far from its local clock. If this is the case, you should +check to make sure your local clock is properly synchronized to NTP. + +#### From the `/metrics` url + +If you are not running a Prometheus collector, navigate to the `/metrics` endpoint +exposed on the Prometheus metrics port with `curl` or a browser. + +Search for the `tendermint_consensus_proposal_timestamp_difference_count` metrics. +This metric is labeled with `is_timely`. Investigate the value of +`tendermint_consensus_proposal_timestamp_difference_count` where `is_timely="false"` +and where `is_timely="true"`. Refresh the endpoint and observe if the value of `is_timely="false"` +is growing. + +If you observe that `is_timely="false"` is growing, it means that your node is consistently +seeing proposals that are far from its local clock. If this is the case, you should check +to make sure your local clock is properly synchronized to NTP. + +### Checking Clock Sync + +NTP configuration and tooling is very specific to the operating system and distribution +that your validator node is running. This guide assumes you have `timedatectl` installed with +[chrony](https://chrony.tuxfamily.org/), a popular tool for interacting with time +synchronization on Linux distributions. If you are using an operating system or +distribution with a different time synchronization mechanism, please consult the +documentation for your operating system to check the status and re-synchronize the daemon. + +#### Check if NTP is Enabled + +```shell +$ timedatectl +``` + +From the output, ensure that `NTP service` is `active`. If `NTP service` is `inactive`, run: + +```shell +$ timedatectl set-ntp true +``` + +Re-run the `timedatectl` command and verify that the change has taken effect. + +#### Check if Your NTP Daemon is Synchronized + +Check the status of your local `chrony` NTP daemon using by running the following: + +```shell +$ chronyc tracking +``` + +If the `chrony` daemon is running, you will see output that indicates its current status. +If the `chrony` daemon is not running, restart it and re-run `chronyc tracking`. + +The `System time` field of the response should show a value that is much smaller than 100 +milliseconds. + +If the value is very large, restart the `chronyd` daemon. + +## Debugging a Network + +If you observe that a network is frequently failing to produce blocks and suspect +it may be related to clock synchronization, use the following steps to debug and correct the issue. + +### Check Prevote Message Delay + +Tendermint exposes metrics that help determine how synchronized the clocks on a network are. + +These metrics are visible on the Prometheus `/metrics` endpoint and are called: +* `tendermint_consensus_quorum_prevote_delay` +* `tendermint_consensus_full_prevote_delay` + +These metrics calculate the difference between the timestamp in the proposal message and +the timestamp of a prevote that was issued during consensus. + +The `tendermint_consensus_quorum_prevote_delay` metric is the interval in seconds +between the proposal timestamp and the timestamp of the earliest prevote that +achieved a quorum during the prevote step. + +The `tendermint_consensus_full_prevote_delay` metric is the interval in seconds +between the proposal timestamp and the timestamp of the latest prevote in a round +where 100% of the validators voted. + +#### From the Prometheus Collector UI + +If you are running a Prometheus collector, navigate to the query web interface and select the 'Graph' tab. + +Issue a query for the following: + +``` +sum(tendermint_consensus_quorum_prevote_delay) by (proposer_address) +``` + +This query will graph the difference in seconds for each proposer on the network. + +If the value is much larger for some proposers, then the issue is likely related to the clock +synchronization of their nodes. Contact those proposers and ensure that their nodes +are properly connected to NTP using the steps for [Debugging a Single Node](#debugging-a-single-node). + +If the value is relatively similar for all proposers you should next compare this +value to the `SynchronyParams` values for the network. Continue to the [Checking +Sychrony](#checking-synchrony) steps. + +#### From the `/metrics` url + +If you are not running a Prometheus collector, navigate to the `/metrics` endpoint +exposed on the Prometheus metrics port. + +Search for the `tendermint_consensus_quorum_prevote_delay` metric. There will be one +entry of this metric for each `proposer_address`. If the value of this metric is +much larger for some proposers, then the issue is likely related to synchronization of their +nodes with NTP. Contact those proposers and ensure that their nodes are properly connected +to NTP using the steps for [Debugging a Single Node](#debugging-a-single-node). + +If the values are relatively similar for all proposers you should next compare, +you'll need to compare this value to the `SynchronyParams` for the network. Continue +to the [Checking Sychrony](#checking-synchrony) steps. + +### Checking Synchrony + +To determine the currently configured `SynchronyParams` for your network, issue a +request to your node's RPC endpoint. For a node running locally with the RPC server +exposed on port `26657`, run the following command: + +```shell +$ curl localhost:26657/consensus_params +``` + +The json output will contain a field named `synchrony`, with the following structure: + +```json +{ + "precision": "500000000", + "message_delay": "3000000000" +} +``` + +The `precision` and `message_delay` values returned are listed in nanoseconds: +In the examples above, the precision is 500ms and the message delay is 3s. +Remember, `tendermint_consensus_quorum_prevote_delay` is listed in seconds. +If the `tendermint_consensus_quorum_prevote_delay` value approaches the sum of `precision` and `message_delay`, +then the value selected for these parameters is too small. Your application will +need to be modified to update the `SynchronyParams` to have larger values. + +### Updating SynchronyParams + +The `SynchronyParams` are `ConsensusParameters` which means they are set and updated +by the application running alongside Tendermint. Updates to these parameters must +be passed to the application during the `FinalizeBlock` ABCI method call. + +If the application was built using the CosmosSDK, then these parameters can be updated +programatically using a governance proposal. For more information, see the [CosmosSDK +documentation](https://hub.cosmos.network/main/governance/submitting.html#sending-the-transaction-that-submits-your-governance-proposal). + +If the application does not implement a way to update the consensus parameters +programatically, then the application itself must be updated to do so. More information on updating +the consensus parameters via ABCI can be found in the [FinalizeBlock documentation](https://github.com/tendermint/tendermint/blob/master/spec/abci++/abci++_methods_002_draft.md#finalizeblock). diff --git a/docs/tools/terraform-and-ansible.md b/docs/tools/terraform-and-ansible.md index 6efbf3751..78e45652c 100644 --- a/docs/tools/terraform-and-ansible.md +++ b/docs/tools/terraform-and-ansible.md @@ -164,7 +164,7 @@ page](https://app.logz.io/#/dashboard/data-sources/Filebeat), then: yum install systemd-devel || echo "This will only work on RHEL-based systems." apt-get install libsystemd-dev || echo "This will only work on Debian-based systems." -go get github.com/mheese/journalbeat +go install github.com/mheese/journalbeat@latest ansible-playbook -i inventory/digital_ocean.py -l sentrynet logzio.yml -e LOGZIO_TOKEN=ABCDEFGHIJKLMNOPQRSTUVWXYZ012345 ``` diff --git a/go.mod b/go.mod index c795f1319..ff5b4e11f 100644 --- a/go.mod +++ b/go.mod @@ -3,21 +3,20 @@ module github.com/tendermint/tendermint go 1.17 require ( - github.com/BurntSushi/toml v1.0.0 - github.com/adlio/schema v1.2.3 - github.com/btcsuite/btcd v0.22.0-beta + github.com/BurntSushi/toml v1.1.0 + github.com/adlio/schema v1.3.0 + github.com/btcsuite/btcd v0.22.1 github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce github.com/fortytw2/leaktest v1.3.0 github.com/go-kit/kit v0.12.0 github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.2 - github.com/golangci/golangci-lint v1.44.2 github.com/google/orderedcode v0.0.1 github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.5.0 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 - github.com/lib/pq v1.10.4 + github.com/lib/pq v1.10.5 github.com/libp2p/go-buffer-pool v0.0.2 github.com/mroth/weightedrand v0.4.1 github.com/oasisprotocol/curve25519-voi v0.0.0-20210609091139-0a56a4bca00b @@ -26,25 +25,29 @@ require ( github.com/rs/cors v1.8.2 github.com/rs/zerolog v1.26.1 github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa - github.com/spf13/cobra v1.3.0 - github.com/spf13/viper v1.10.1 - github.com/stretchr/testify v1.7.0 + github.com/spf13/cobra v1.4.0 + github.com/spf13/viper v1.11.0 + github.com/stretchr/testify v1.7.1 github.com/tendermint/tm-db v0.6.6 - github.com/vektra/mockery/v2 v2.10.0 - golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce - golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b + golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 + golang.org/x/net v0.0.0-20220412020605-290c469a71a5 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - google.golang.org/grpc v1.44.0 + google.golang.org/grpc v1.46.0 gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect pgregory.net/rapid v0.4.7 ) require ( - github.com/creachadair/atomicfile v0.2.4 - github.com/google/go-cmp v0.5.7 + github.com/creachadair/atomicfile v0.2.5 + github.com/creachadair/taskgroup v0.3.2 + github.com/golangci/golangci-lint v1.45.2 + github.com/google/go-cmp v0.5.8 + github.com/vektra/mockery/v2 v2.12.1 gotest.tools v2.2.0+incompatible ) +require github.com/pelletier/go-toml/v2 v2.0.0-beta.8 // indirect + require ( 4d63.com/gochecknoglobals v0.1.0 // indirect github.com/Antonboom/errname v0.1.5 // indirect @@ -58,10 +61,10 @@ require ( github.com/OpenPeeDeeP/depguard v1.1.0 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/ashanbrown/forbidigo v1.3.0 // indirect - github.com/ashanbrown/makezero v1.1.0 // indirect + github.com/ashanbrown/makezero v1.1.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bkielbasa/cyclop v1.2.0 // indirect - github.com/blizzy78/varnamelen v0.6.0 // indirect + github.com/blizzy78/varnamelen v0.6.1 // indirect github.com/bombsimon/wsl/v3 v3.3.0 // indirect github.com/breml/bidichk v0.2.2 // indirect github.com/breml/errchkjson v0.2.3 // indirect @@ -72,9 +75,10 @@ require ( github.com/charithe/durationcheck v0.0.9 // indirect github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af // indirect github.com/containerd/continuity v0.2.1 // indirect - github.com/daixiang0/gci v0.3.1-0.20220208004058-76d765e3ab48 // indirect + github.com/creachadair/tomledit v0.0.19 + github.com/daixiang0/gci v0.3.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/denis-tingajkin/go-header v0.4.2 // indirect + github.com/denis-tingaikin/go-header v0.4.3 // indirect github.com/dgraph-io/badger/v2 v2.2007.2 // indirect github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de // indirect github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 // indirect @@ -119,6 +123,7 @@ require ( github.com/gostaticanalysis/nilerr v0.1.1 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-version v1.4.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect @@ -135,7 +140,7 @@ require ( github.com/ldez/gomoddirectives v0.2.2 // indirect github.com/ldez/tagliatelle v0.3.1 // indirect github.com/leonklingele/grouper v1.1.0 // indirect - github.com/magiconair/properties v1.8.5 // indirect + github.com/magiconair/properties v1.8.6 // indirect github.com/maratori/testpackage v1.0.1 // indirect github.com/matoous/godox v0.0.0-20210227103229-6504466cf951 // indirect github.com/mattn/go-colorable v0.1.12 // indirect @@ -143,7 +148,6 @@ require ( github.com/mattn/go-runewidth v0.0.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mbilski/exhaustivestruct v1.2.0 // indirect - github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect github.com/mgechev/revive v1.1.4 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect @@ -170,14 +174,14 @@ require ( github.com/ryancurrah/gomodguard v1.2.3 // indirect github.com/ryanrolds/sqlclosecheck v0.3.0 // indirect github.com/sanposhiho/wastedassign/v2 v2.0.6 // indirect - github.com/securego/gosec/v2 v2.9.6 // indirect + github.com/securego/gosec/v2 v2.10.0 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect github.com/sirupsen/logrus v1.8.1 // indirect - github.com/sivchari/containedctx v1.0.1 // indirect + github.com/sivchari/containedctx v1.0.2 // indirect github.com/sivchari/tenv v1.4.7 // indirect github.com/sonatard/noctx v0.0.1 // indirect github.com/sourcegraph/go-diff v0.6.1 // indirect - github.com/spf13/afero v1.8.0 // indirect + github.com/spf13/afero v1.8.2 // indirect github.com/spf13/cast v1.4.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -190,7 +194,7 @@ require ( github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect github.com/tetafro/godot v1.4.11 // indirect github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 // indirect - github.com/tomarrell/wrapcheck/v2 v2.4.0 // indirect + github.com/tomarrell/wrapcheck/v2 v2.5.0 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.0 // indirect github.com/ultraware/funlen v0.0.3 // indirect github.com/ultraware/whitespace v0.0.5 // indirect @@ -199,19 +203,19 @@ require ( github.com/yeya24/promlinter v0.1.1-0.20210918184747-d757024714a1 // indirect gitlab.com/bosi/decorder v0.2.1 // indirect go.etcd.io/bbolt v1.3.6 // indirect - golang.org/x/mod v0.5.1 // indirect - golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect + golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect + golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/tools v0.1.9 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect - google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect - google.golang.org/protobuf v1.27.1 // indirect - gopkg.in/ini.v1 v1.66.3 // indirect + golang.org/x/tools v0.1.10 // indirect + golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect + google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac // indirect + google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect honnef.co/go/tools v0.2.2 // indirect - mvdan.cc/gofumpt v0.2.1 // indirect + mvdan.cc/gofumpt v0.3.0 // indirect mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect mvdan.cc/unparam v0.0.0-20211214103731-d0ef000c54e5 // indirect diff --git a/go.sum b/go.sum index cd3e0e4f5..a7b2d7bd8 100644 --- a/go.sum +++ b/go.sum @@ -34,12 +34,16 @@ cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= @@ -62,11 +66,15 @@ github.com/Antonboom/errname v0.1.5 h1:IM+A/gz0pDhKmlt5KSNTVAvfLMb+65RxavBXpRtCU github.com/Antonboom/errname v0.1.5/go.mod h1:DugbBstvPFQbv/5uLcRRzfrNqKE9tVdVCqWCLp6Cifo= github.com/Antonboom/nilnil v0.1.0 h1:DLDavmg0a6G/F4Lt9t7Enrbgb3Oph6LnDE6YVsmTt74= github.com/Antonboom/nilnil v0.1.0/go.mod h1:PhHLvRPSghY5Y7mX4TW+BHZQYo1A8flE5H20D3IPZBo= +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= @@ -96,8 +104,8 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/adlio/schema v1.2.3 h1:GfKThfEsjS9cCz7gaF8zdXv4cpTdUqdljkKGDTbJjys= -github.com/adlio/schema v1.2.3/go.mod h1:nD7ZWmMMbwU12Pqwg+qL0rTvHBrBXfNz+5UQxTfy38M= +github.com/adlio/schema v1.3.0 h1:eSVYLxYWbm/6ReZBCkLw4Fz7uqC+ZNoPvA39bOwi52A= +github.com/adlio/schema v1.3.0/go.mod h1:51QzxkpeFs6lRY11kPye26IaFPOV+HqEj01t5aXXKfs= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= @@ -122,8 +130,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/ashanbrown/forbidigo v1.3.0 h1:VkYIwb/xxdireGAdJNZoo24O4lmnEWkactplBlWTShc= github.com/ashanbrown/forbidigo v1.3.0/go.mod h1:vVW7PEdqEFqapJe95xHkTfB1+XvZXBFg8t0sG2FIxmI= -github.com/ashanbrown/makezero v1.1.0 h1:b2FVq4dTlBpy9f6qxhbyWH+6zy56IETE9cFbBGtDqs8= -github.com/ashanbrown/makezero v1.1.0/go.mod h1:oG9Dnez7/ESBqc4EdrdNlryeo7d0KcW1ftXHm7nU/UU= +github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= +github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.36.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= @@ -140,8 +148,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bkielbasa/cyclop v1.2.0 h1:7Jmnh0yL2DjKfw28p86YTd/B4lRGcNuu12sKE35sM7A= github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI= -github.com/blizzy78/varnamelen v0.6.0 h1:TOIDk9qRIMspALZKX8x+5hQfAjuvAFogppnxtvuNmBo= -github.com/blizzy78/varnamelen v0.6.0/go.mod h1:zy2Eic4qWqjrxa60jG34cfL0VXcSwzUrIx68eJPb4Q8= +github.com/blizzy78/varnamelen v0.6.1 h1:kttPCLzXFa+0nt++Cw9fb7GrSSM4KkyIAoX/vXsbuqA= +github.com/blizzy78/varnamelen v0.6.1/go.mod h1:zy2Eic4qWqjrxa60jG34cfL0VXcSwzUrIx68eJPb4Q8= github.com/bombsimon/wsl/v3 v3.3.0 h1:Mka/+kRLoQJq7g2rggtgQsjuI/K5Efd87WX96EWFxjM= github.com/bombsimon/wsl/v3 v3.3.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= github.com/breml/bidichk v0.2.2 h1:w7QXnpH0eCBJm55zGCTJveZEkQBt6Fs5zThIdA6qQ9Y= @@ -149,8 +157,10 @@ github.com/breml/bidichk v0.2.2/go.mod h1:zbfeitpevDUGI7V91Uzzuwrn4Vls8MoBMrwtt7 github.com/breml/errchkjson v0.2.3 h1:97eGTmR/w0paL2SwfRPI1jaAZHaH/fXnxWTw2eEIqE0= github.com/breml/errchkjson v0.2.3/go.mod h1:jZEATw/jF69cL1iy7//Yih8yp/mXp2CBoBr9GJwCAsY= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo= -github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= +github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= +github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= @@ -217,21 +227,27 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creachadair/atomicfile v0.2.4 h1:GRjpQLmz/78I4+nBQpGMFrRa9yrL157AUTrA6hnF0YU= -github.com/creachadair/atomicfile v0.2.4/go.mod h1:BRq8Une6ckFneYXZQ+kO7p1ZZP3I2fzVzf28JxrIkBc= +github.com/creachadair/atomicfile v0.2.5 h1:wkOlpsjyJOvJ3Hd8juHKdirJnCSIPacvtY21/3nYjAo= +github.com/creachadair/atomicfile v0.2.5/go.mod h1:BRq8Une6ckFneYXZQ+kO7p1ZZP3I2fzVzf28JxrIkBc= +github.com/creachadair/taskgroup v0.3.2 h1:zlfutDS+5XG40AOxcHDSThxKzns8Tnr9jnr6VqkYlkM= +github.com/creachadair/taskgroup v0.3.2/go.mod h1:wieWwecHVzsidg2CsUnFinW1faVN4+kq+TDlRJQ0Wbk= +github.com/creachadair/tomledit v0.0.19 h1:zbpfUtYFYFdpRjwJY9HJlto1iZ4M5YwYB6qqc37F6UM= +github.com/creachadair/tomledit v0.0.19/go.mod h1:gvtfnSZLa+YNQD28vaPq0Nk12bRxEhmUdBzAWn+EGF4= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= -github.com/daixiang0/gci v0.3.1-0.20220208004058-76d765e3ab48 h1:9rJGqaC5do9zkvKrtRdx0HJoxj7Jd6vDa0O2eBU0AbU= -github.com/daixiang0/gci v0.3.1-0.20220208004058-76d765e3ab48/go.mod h1:jaASoJmv/ykO9dAAPy31iJnreV19248qKDdVWf3QgC4= +github.com/daixiang0/gci v0.3.3 h1:55xJKH7Gl9Vk6oQ1cMkwrDWjAkT1D+D1G9kNmRcAIY4= +github.com/daixiang0/gci v0.3.3/go.mod h1:1Xr2bxnQbDxCqqulUOv8qpGqkgRw9RSCGGjEC2LjF8o= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= -github.com/denis-tingajkin/go-header v0.4.2 h1:jEeSF4sdv8/3cT/WY8AgDHUoItNSoEZ7qg9dX7pc218= -github.com/denis-tingajkin/go-header v0.4.2/go.mod h1:eLRHAVXzE5atsKAnNRDB90WHCFFnBUn4RN0nRcs1LJA= +github.com/denis-tingaikin/go-header v0.4.3 h1:tEaZKAlqql6SKCY++utLmkPLd6K8IBM20Ha7UVm+mtU= +github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c= +github.com/denisenkom/go-mssqldb v0.12.0 h1:VtrkII767ttSPNRfFekePK3sctr+joXgO58stqQbtUA= +github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU= github.com/dgraph-io/badger/v2 v2.2007.2 h1:EjjK0KqwaFMlPin1ajhP943VPENHJdEz1KLIegjaI3k= github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA= @@ -240,6 +256,7 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= @@ -260,6 +277,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= @@ -287,8 +305,8 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= -github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss= -github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= +github.com/frankban/quicktest v1.14.2 h1:SPb1KFFmM+ybpEjPUhCCkZOM5xlovT5UbrMvWnXyBns= +github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= @@ -352,6 +370,10 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188 h1:+eHOFJl1BaXrQxKX+T06f78590z4qA2ZzBTqahsKSE4= +github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -400,8 +422,8 @@ github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 h1:9kfjN3AdxcbsZB github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks= github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= -github.com/golangci/golangci-lint v1.44.2 h1:MzvkDt1j1OHkv42/feNJVNNXRFACPp7aAWBWDo5aYQw= -github.com/golangci/golangci-lint v1.44.2/go.mod h1:KjBgkLvsTWDkhfu12iCrv0gwL1kON5KNhbyjQ6qN7Jo= +github.com/golangci/golangci-lint v1.45.2 h1:9I3PzkvscJkFAQpTQi5Ga0V4qWdJERajX1UZ7QqkW+I= +github.com/golangci/golangci-lint v1.45.2/go.mod h1:f20dpzMmUTRp+oYnX0OGjV1Au3Jm2JeI9yLqHq1/xsI= github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= @@ -429,8 +451,9 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -467,6 +490,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -525,6 +550,7 @@ github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/S github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= @@ -538,8 +564,9 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4= +github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= @@ -552,6 +579,7 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -644,16 +672,18 @@ github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ= +github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/maratori/testpackage v1.0.1 h1:QtJ5ZjqapShm0w5DosRjg0PRlSdAdlx+W6cCKoALdbQ= github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU= github.com/matoous/godox v0.0.0-20210227103229-6504466cf951 h1:pWxk9e//NbPwfxat7RXkts09K+dEBJWakUWwICVqYbA= @@ -687,7 +717,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwgOdMUQePUo= github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= -github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 h1:zpIH83+oKzcpryru8ceC6BxnoG8TBrhgAvRg8obzup0= github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= github.com/mgechev/revive v1.1.4 h1:sZOjY6GU35Kr9jKa/wsKSHgrFz8eASIB5i3tqWZMp0A= github.com/mgechev/revive v1.1.4/go.mod h1:ZZq2bmyssGh8MSPz3VVziqRNIMYTJXzP8MUKG90vZ9A= @@ -719,6 +748,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/moricho/tparallel v0.2.1 h1:95FytivzT6rYzdJLdtfn6m1bfFJylOJK41+lgv/EHf4= github.com/moricho/tparallel v0.2.1/go.mod h1:fXEIZxG2vdfl0ZF8b42f5a78EhjjD5mX8qUplsoSU4k= @@ -768,15 +798,17 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= -github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -802,11 +834,14 @@ github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144T github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.0-beta.8 h1:dy81yyLYJDwMTifq24Oi/IslOslRrDSb3jwDggjz3Z0= +github.com/pelletier/go-toml/v2 v2.0.0-beta.8/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d h1:CdDQnGF8Nq9ocOS/xlSptM1N3BbrA6/kmaep5ggwaIA= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -893,16 +928,17 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= +github.com/sagikazarmark/crypt v0.5.0/go.mod h1:l+nzl7KWh51rpzp2h7t4MZWyiEWdhNpOAnclKvg+mdA= github.com/sanposhiho/wastedassign/v2 v2.0.6 h1:+6/hQIHKNJAUixEj6EmOngGIisyeI+T3335lYTyxRoA= github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= -github.com/securego/gosec/v2 v2.9.6 h1:ysfvgQBp2zmTgXQl65UkqEkYlQGbnVSRUGpCrJiiR4c= -github.com/securego/gosec/v2 v2.9.6/go.mod h1:EESY9Ywxo/Zc5NyF/qIj6Cop+4PSWM0F0OfGD7FdIXc= +github.com/securego/gosec/v2 v2.10.0 h1:l6BET4EzWtyUXCpY2v7N92v0DDCas0L7ngg3bpqbr8g= +github.com/securego/gosec/v2 v2.10.0/go.mod h1:PVq8Ewh/nCN8l/kKC6zrGXSr7m2NmEK6ITIAWMtIaA0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= -github.com/shirou/gopsutil/v3 v3.22.1/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY= +github.com/shirou/gopsutil/v3 v3.22.2/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -912,8 +948,8 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sivchari/containedctx v1.0.1 h1:fJq44cX+tD+uT5xGrsg25GwiaY61NGybQk9WWKij3Uo= -github.com/sivchari/containedctx v1.0.1/go.mod h1:PwZOeqm4/DLoJOqMSIJs3aKqXRX4YO+uXww087KZ7Bw= +github.com/sivchari/containedctx v1.0.2 h1:0hLQKpgC53OVF1VT7CeoFHk9YKstur1XOgfYIc1yrHI= +github.com/sivchari/containedctx v1.0.2/go.mod h1:PwZOeqm4/DLoJOqMSIJs3aKqXRX4YO+uXww087KZ7Bw= github.com/sivchari/tenv v1.4.7 h1:FdTpgRlTue5eb5nXIYgS/lyVXSjugU8UUVDwhP1NLU8= github.com/sivchari/tenv v1.4.7/go.mod h1:5nF+bITvkebQVanjU6IuMbvIot/7ReNsUV7I5NbprB0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= @@ -932,16 +968,18 @@ github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.8.0 h1:5MmtuhAgYeU6qpa7w7bP0dv6MBYuup0vekhSpSkoq60= github.com/spf13/afero v1.8.0/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= +github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0= github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= +github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= +github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -953,8 +991,9 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= -github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= +github.com/spf13/viper v1.11.0 h1:7OX/1FS6n7jHD1zGrZTM7WtY13ZELRyosK4k93oPr44= +github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUsX7Zk= github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= @@ -970,8 +1009,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV 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 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/sylvia7788/contextcheck v1.0.4 h1:MsiVqROAdr0efZc/fOCt0c235qm9XJqHtWwM+2h2B04= @@ -998,8 +1038,8 @@ github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcy github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tomarrell/wrapcheck/v2 v2.4.0 h1:mU4H9KsqqPZUALOUbVOpjy8qNQbWLoLI9fV68/1tq30= -github.com/tomarrell/wrapcheck/v2 v2.4.0/go.mod h1:68bQ/eJg55BROaRTbMjC7vuhL2OgfoG8bLp9ZyoBfyY= +github.com/tomarrell/wrapcheck/v2 v2.5.0 h1:g27SGGHNoQdvHz4KZA9o4v09RcWzylR+b1yueE5ECiw= +github.com/tomarrell/wrapcheck/v2 v2.5.0/go.mod h1:68bQ/eJg55BROaRTbMjC7vuhL2OgfoG8bLp9ZyoBfyY= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= github.com/tommy-muehle/go-mnd/v2 v2.5.0 h1:iAj0a8e6+dXSL7Liq0aXPox36FiN1dBbjA6lt9fl65s= github.com/tommy-muehle/go-mnd/v2 v2.5.0/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= @@ -1019,8 +1059,8 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -github.com/vektra/mockery/v2 v2.10.0 h1:MiiQWxwdq7/ET6dCXLaJzSGEN17k758H7JHS9kOdiks= -github.com/vektra/mockery/v2 v2.10.0/go.mod h1:m/WO2UzWzqgVX3nvqpRQq70I4Z7jbSCRhdmkgtp+Ab4= +github.com/vektra/mockery/v2 v2.12.1 h1:BAJk2fGjVg/P9Fi+BxZD1/ZeKTOclpeAb/SKCc12zXc= +github.com/vektra/mockery/v2 v2.12.1/go.mod h1:8vf4KDDUptfkyypzdHLuE7OE2xA7Gdt60WgIS8PgD+U= github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8/go.mod h1:dniwbG03GafCjFohMDmz6Zc6oCuiqgH6tGNyXTkHzXE= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= @@ -1052,10 +1092,13 @@ go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v0.0.0-20200513171258-e048e166ab9c/go.mod h1:xCI7ZzBfRuGgBXyXO6yfWfDmlWd35khcWpUa4L0xI/k= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/api/v3 v3.5.2/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.2/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= +go.etcd.io/etcd/client/v2 v2.305.2/go.mod h1:2D7ZejHVMIfog1221iLSYlQRzrtECw3kz4I4VAQm3qI= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= go.mozilla.org/mozlog v0.0.0-20170222151521-4bb13139d403/go.mod h1:jHoPAGnDrCy6kaI2tAze5Prf0Nr0w/oNkROt2lw3n3o= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -1097,6 +1140,7 @@ golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= @@ -1104,10 +1148,13 @@ golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5 golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce h1:Roh6XWxHFKrPgC/EQhVubSAGQ6Ozk6IdxHSzt1mR0EI= golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1149,8 +1196,9 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1204,14 +1252,19 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b h1:MWaHNqZy3KTpuTMAGvv+Kw+ylsEpmyJZizz1dqxnu28= golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5 h1:bRb386wvrE+oBNdF1d/Xh9mQrfQ4ecYhW5qJ5GvTGT4= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1229,6 +1282,9 @@ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/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= @@ -1345,8 +1401,13 @@ golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211213223007-03aa0b5f6827/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= @@ -1466,13 +1527,15 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.9-0.20211228192929-ee1ca4ffc4da/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= @@ -1511,6 +1574,10 @@ google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUb google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= google.golang.org/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= @@ -1590,8 +1657,17 @@ google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac h1:qSNTkEN+L2mvWcLgJOR+8bdHX9rN/IdU3A1Ghpfb1Rg= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -1625,8 +1701,10 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1640,8 +1718,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1656,8 +1735,9 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.66.3 h1:jRskFVxYaMGAMUbN0UZ7niA9gzL9B49DOqE78vg0k3w= gopkg.in/ini.v1 v1.66.3/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= +gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -1687,8 +1767,8 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.2.2 h1:MNh1AVMyVX23VUHE2O27jm6lNj3vjO5DexS4A1xvnzk= honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= -mvdan.cc/gofumpt v0.2.1 h1:7jakRGkQcLAJdT+C8Bwc9d0BANkVPSkHZkzNv07pJAs= -mvdan.cc/gofumpt v0.2.1/go.mod h1:a/rvZPhsNaedOJBzqRD9omnwVwHZsBdJirXHa9Gh9Ig= +mvdan.cc/gofumpt v0.3.0 h1:kTojdZo9AcEYbQYhGuLf/zszYthRdhDNDUi2JKTxas4= +mvdan.cc/gofumpt v0.3.0/go.mod h1:0+VyGZWleeIj5oostkOex+nDBA0eyavuDnDusAJ8ylo= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= diff --git a/internal/blocksync/pool.go b/internal/blocksync/pool.go index f3d4a9e0a..f00a2fab5 100644 --- a/internal/blocksync/pool.go +++ b/internal/blocksync/pool.go @@ -28,7 +28,7 @@ eg, L = latency = 0.1s */ const ( - requestIntervalMS = 2 + requestInterval = 2 * time.Millisecond maxTotalRequesters = 600 maxPeerErrBuffer = 1000 maxPendingRequests = maxTotalRequesters @@ -86,7 +86,6 @@ type BlockPool struct { requestsCh chan<- BlockRequest errorsCh chan<- peerError - exitedCh chan struct{} startHeight int64 lastHundredBlockTimeStamp time.Time @@ -109,7 +108,6 @@ func NewBlockPool( height: start, startHeight: start, numPending: 0, - exitedCh: make(chan struct{}), requestsCh: requestsCh, errorsCh: errorsCh, lastSyncRate: 0, @@ -125,11 +123,6 @@ func (pool *BlockPool) OnStart(ctx context.Context) error { pool.lastHundredBlockTimeStamp = pool.lastAdvance go pool.makeRequestersRoutine(ctx) - go func() { - defer close(pool.exitedCh) - pool.Wait() - }() - return nil } @@ -137,27 +130,23 @@ func (*BlockPool) OnStop() {} // spawns requesters as needed func (pool *BlockPool) makeRequestersRoutine(ctx context.Context) { - for { - if !pool.IsRunning() { - break + for pool.IsRunning() { + if ctx.Err() != nil { + return } _, numPending, lenRequesters := pool.GetStatus() - switch { - case numPending >= maxPendingRequests: - // sleep for a bit. - time.Sleep(requestIntervalMS * time.Millisecond) - // check for timed out peers + if numPending >= maxPendingRequests || lenRequesters >= maxTotalRequesters { + // This is preferable to using a timer because the request interval + // is so small. Larger request intervals may necessitate using a + // timer/ticker. + time.Sleep(requestInterval) pool.removeTimedoutPeers() - case lenRequesters >= maxTotalRequesters: - // sleep for a bit. - time.Sleep(requestIntervalMS * time.Millisecond) - // check for timed out peers - pool.removeTimedoutPeers() - default: - // request for more blocks. - pool.makeNextRequester(ctx) + continue } + + // request for more blocks. + pool.makeNextRequester(ctx) } } @@ -168,7 +157,7 @@ func (pool *BlockPool) removeTimedoutPeers() { for _, peer := range pool.peers { // check if peer timed out if !peer.didTimeout && peer.numPending > 0 { - curRate := peer.recvMonitor.Status().CurRate + curRate := peer.recvMonitor.CurrentTransferRate() // curRate can be 0 on start if curRate != 0 && curRate < minRecvRate { err := errors.New("peer is not sending us data fast enough") @@ -637,12 +626,6 @@ func (bpr *bpRequester) redo(peerID types.NodeID) { // Responsible for making more requests as necessary // Returns only when a block is found (e.g. AddBlock() is called) func (bpr *bpRequester) requestRoutine(ctx context.Context) { - bprPoolDone := make(chan struct{}) - go func() { - defer close(bprPoolDone) - bpr.pool.Wait() - }() - OUTER_LOOP: for { // Pick a peer to send request to. @@ -652,9 +635,16 @@ OUTER_LOOP: if !bpr.IsRunning() || !bpr.pool.IsRunning() { return } + if ctx.Err() != nil { + return + } + peer = bpr.pool.pickIncrAvailablePeer(bpr.height) if peer == nil { - time.Sleep(requestIntervalMS * time.Millisecond) + // This is preferable to using a timer because the request + // interval is so small. Larger request intervals may + // necessitate using a timer/ticker. + time.Sleep(requestInterval) continue PICK_PEER_LOOP } break PICK_PEER_LOOP @@ -670,9 +660,6 @@ OUTER_LOOP: select { case <-ctx.Done(): return - case <-bpr.pool.exitedCh: - bpr.Stop() - return case peerID := <-bpr.redoCh: if peerID == bpr.peerID { bpr.reset() diff --git a/internal/blocksync/pool_test.go b/internal/blocksync/pool_test.go index 0306a31c0..1cb8cca40 100644 --- a/internal/blocksync/pool_test.go +++ b/internal/blocksync/pool_test.go @@ -86,7 +86,7 @@ func TestBlockPoolBasic(t *testing.T) { peers := makePeers(10, start+1, 1000) errorsCh := make(chan peerError, 1000) requestsCh := make(chan BlockRequest, 1000) - pool := NewBlockPool(log.TestingLogger(), start, requestsCh, errorsCh) + pool := NewBlockPool(log.NewNopLogger(), start, requestsCh, errorsCh) if err := pool.Start(ctx); err != nil { t.Error(err) @@ -138,7 +138,7 @@ func TestBlockPoolTimeout(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.TestingLogger() + logger := log.NewNopLogger() start := int64(42) peers := makePeers(10, start+1, 1000) @@ -207,7 +207,7 @@ func TestBlockPoolRemovePeer(t *testing.T) { requestsCh := make(chan BlockRequest) errorsCh := make(chan peerError) - pool := NewBlockPool(log.TestingLogger(), 1, requestsCh, errorsCh) + pool := NewBlockPool(log.NewNopLogger(), 1, requestsCh, errorsCh) err := pool.Start(ctx) require.NoError(t, err) t.Cleanup(func() { cancel(); pool.Wait() }) diff --git a/internal/blocksync/reactor.go b/internal/blocksync/reactor.go index cf1a10623..0bf0561d3 100644 --- a/internal/blocksync/reactor.go +++ b/internal/blocksync/reactor.go @@ -11,6 +11,7 @@ import ( "github.com/tendermint/tendermint/internal/consensus" "github.com/tendermint/tendermint/internal/eventbus" "github.com/tendermint/tendermint/internal/p2p" + "github.com/tendermint/tendermint/internal/p2p/conn" sm "github.com/tendermint/tendermint/internal/state" "github.com/tendermint/tendermint/internal/store" "github.com/tendermint/tendermint/libs/log" @@ -45,6 +46,7 @@ func GetChannelDescriptor() *p2p.ChannelDescriptor { SendQueueCapacity: 1000, RecvBufferCapacity: 1024, RecvMessageCapacity: MaxMsgSize, + Name: "blockSync", } } @@ -79,16 +81,8 @@ type Reactor struct { consReactor consensusReactor blockSync *atomicBool - blockSyncCh *p2p.Channel - // blockSyncOutBridgeCh defines a channel that acts as a bridge between sending Envelope - // messages that the reactor will consume in processBlockSyncCh and receiving messages - // from the peer updates channel and other goroutines. We do this instead of directly - // sending on blockSyncCh.Out to avoid race conditions in the case where other goroutines - // send Envelopes directly to the to blockSyncCh.Out channel, since processBlockSyncCh - // may close the blockSyncCh.Out channel at the same time that other goroutines send to - // blockSyncCh.Out. - blockSyncOutBridgeCh chan p2p.Envelope - peerUpdates *p2p.PeerUpdates + chCreator p2p.ChannelCreator + peerEvents p2p.PeerEventSubscriber requestsCh <-chan BlockRequest errorsCh <-chan peerError @@ -101,39 +95,32 @@ type Reactor struct { // NewReactor returns new reactor instance. func NewReactor( - ctx context.Context, logger log.Logger, stateStore sm.Store, blockExec *sm.BlockExecutor, store *store.BlockStore, consReactor consensusReactor, channelCreator p2p.ChannelCreator, - peerUpdates *p2p.PeerUpdates, + peerEvents p2p.PeerEventSubscriber, blockSync bool, metrics *consensus.Metrics, eventBus *eventbus.EventBus, -) (*Reactor, error) { - blockSyncCh, err := channelCreator(ctx, GetChannelDescriptor()) - if err != nil { - return nil, err - } - +) *Reactor { r := &Reactor{ - logger: logger, - stateStore: stateStore, - blockExec: blockExec, - store: store, - consReactor: consReactor, - blockSync: newAtomicBool(blockSync), - blockSyncCh: blockSyncCh, - blockSyncOutBridgeCh: make(chan p2p.Envelope), - peerUpdates: peerUpdates, - metrics: metrics, - eventBus: eventBus, + logger: logger, + stateStore: stateStore, + blockExec: blockExec, + store: store, + consReactor: consReactor, + blockSync: newAtomicBool(blockSync), + chCreator: channelCreator, + peerEvents: peerEvents, + metrics: metrics, + eventBus: eventBus, } r.BaseService = *service.NewBaseService(logger, "BlockSync", r) - return r, nil + return r } // OnStart starts separate go routines for each p2p Channel and listens for @@ -144,6 +131,12 @@ func NewReactor( // If blockSync is enabled, we also start the pool and the pool processing // goroutine. If the pool fails to start, an error is returned. func (r *Reactor) OnStart(ctx context.Context) error { + blockSyncCh, err := r.chCreator(ctx, GetChannelDescriptor()) + if err != nil { + return err + } + r.chCreator = func(context.Context, *conn.ChannelDescriptor) (*p2p.Channel, error) { return blockSyncCh, nil } + state, err := r.stateStore.Load() if err != nil { return err @@ -169,14 +162,13 @@ func (r *Reactor) OnStart(ctx context.Context) error { if err := r.pool.Start(ctx); err != nil { return err } - go r.requestRoutine(ctx) + go r.requestRoutine(ctx, blockSyncCh) - go r.poolRoutine(ctx, false) + go r.poolRoutine(ctx, false, blockSyncCh) } - go r.processBlockSyncCh(ctx) - go r.processBlockSyncBridge(ctx) - go r.processPeerUpdates(ctx) + go r.processBlockSyncCh(ctx, blockSyncCh) + go r.processPeerUpdates(ctx, r.peerEvents(ctx), blockSyncCh) return nil } @@ -191,7 +183,7 @@ func (r *Reactor) OnStop() { // respondToPeer loads a block and sends it to the requesting peer, if we have it. // Otherwise, we'll respond saying we do not have it. -func (r *Reactor) respondToPeer(ctx context.Context, msg *bcproto.BlockRequest, peerID types.NodeID) error { +func (r *Reactor) respondToPeer(ctx context.Context, msg *bcproto.BlockRequest, peerID types.NodeID, blockSyncCh *p2p.Channel) error { block := r.store.LoadBlock(msg.Height) if block != nil { blockProto, err := block.ToProto() @@ -200,7 +192,7 @@ func (r *Reactor) respondToPeer(ctx context.Context, msg *bcproto.BlockRequest, return err } - return r.blockSyncCh.Send(ctx, p2p.Envelope{ + return blockSyncCh.Send(ctx, p2p.Envelope{ To: peerID, Message: &bcproto.BlockResponse{Block: blockProto}, }) @@ -208,55 +200,16 @@ func (r *Reactor) respondToPeer(ctx context.Context, msg *bcproto.BlockRequest, r.logger.Info("peer requesting a block we do not have", "peer", peerID, "height", msg.Height) - return r.blockSyncCh.Send(ctx, p2p.Envelope{ + return blockSyncCh.Send(ctx, p2p.Envelope{ To: peerID, Message: &bcproto.NoBlockResponse{Height: msg.Height}, }) } -// handleBlockSyncMessage handles envelopes sent from peers on the -// BlockSyncChannel. It returns an error only if the Envelope.Message is unknown -// for this channel. This should never be called outside of handleMessage. -func (r *Reactor) handleBlockSyncMessage(ctx context.Context, envelope *p2p.Envelope) error { - logger := r.logger.With("peer", envelope.From) - - switch msg := envelope.Message.(type) { - case *bcproto.BlockRequest: - return r.respondToPeer(ctx, msg, envelope.From) - case *bcproto.BlockResponse: - block, err := types.BlockFromProto(msg.Block) - if err != nil { - logger.Error("failed to convert block from proto", "err", err) - return err - } - - r.pool.AddBlock(envelope.From, block, block.Size()) - - case *bcproto.StatusRequest: - return r.blockSyncCh.Send(ctx, p2p.Envelope{ - To: envelope.From, - Message: &bcproto.StatusResponse{ - Height: r.store.Height(), - Base: r.store.Base(), - }, - }) - case *bcproto.StatusResponse: - r.pool.SetPeerRange(envelope.From, msg.Base, msg.Height) - - case *bcproto.NoBlockResponse: - logger.Debug("peer does not have the requested block", "height", msg.Height) - - default: - return fmt.Errorf("received unknown message: %T", msg) - } - - return nil -} - // handleMessage handles an Envelope sent from a peer on a specific p2p Channel. // It will handle errors and any possible panics gracefully. A caller can handle // any error returned by sending a PeerError on the respective channel. -func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelope *p2p.Envelope) (err error) { +func (r *Reactor) handleMessage(ctx context.Context, envelope *p2p.Envelope, blockSyncCh *p2p.Channel) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("panic in processing message: %v", e) @@ -270,12 +223,44 @@ func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelop r.logger.Debug("received message", "message", envelope.Message, "peer", envelope.From) - switch chID { + switch envelope.ChannelID { case BlockSyncChannel: - err = r.handleBlockSyncMessage(ctx, envelope) + switch msg := envelope.Message.(type) { + case *bcproto.BlockRequest: + return r.respondToPeer(ctx, msg, envelope.From, blockSyncCh) + case *bcproto.BlockResponse: + block, err := types.BlockFromProto(msg.Block) + if err != nil { + r.logger.Error("failed to convert block from proto", + "peer", envelope.From, + "err", err) + return err + } + + r.pool.AddBlock(envelope.From, block, block.Size()) + + case *bcproto.StatusRequest: + return blockSyncCh.Send(ctx, p2p.Envelope{ + To: envelope.From, + Message: &bcproto.StatusResponse{ + Height: r.store.Height(), + Base: r.store.Base(), + }, + }) + case *bcproto.StatusResponse: + r.pool.SetPeerRange(envelope.From, msg.Base, msg.Height) + + case *bcproto.NoBlockResponse: + r.logger.Debug("peer does not have the requested block", + "peer", envelope.From, + "height", msg.Height) + + default: + return fmt.Errorf("received unknown message: %T", msg) + } default: - err = fmt.Errorf("unknown channel ID (%d) for envelope (%v)", chID, envelope) + err = fmt.Errorf("unknown channel ID (%d) for envelope (%v)", envelope.ChannelID, envelope) } return err @@ -286,17 +271,17 @@ func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelop // message execution will result in a PeerError being sent on the BlockSyncChannel. // When the reactor is stopped, we will catch the signal and close the p2p Channel // gracefully. -func (r *Reactor) processBlockSyncCh(ctx context.Context) { - iter := r.blockSyncCh.Receive(ctx) +func (r *Reactor) processBlockSyncCh(ctx context.Context, blockSyncCh *p2p.Channel) { + iter := blockSyncCh.Receive(ctx) for iter.Next(ctx) { envelope := iter.Envelope() - if err := r.handleMessage(ctx, r.blockSyncCh.ID, envelope); err != nil { + if err := r.handleMessage(ctx, envelope, blockSyncCh); err != nil { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { return } - r.logger.Error("failed to process message", "ch_id", r.blockSyncCh.ID, "envelope", envelope, "err", err) - if serr := r.blockSyncCh.SendError(ctx, p2p.PeerError{ + r.logger.Error("failed to process message", "ch_id", envelope.ChannelID, "envelope", envelope, "err", err) + if serr := blockSyncCh.SendError(ctx, p2p.PeerError{ NodeID: envelope.From, Err: err, }); serr != nil { @@ -306,21 +291,8 @@ func (r *Reactor) processBlockSyncCh(ctx context.Context) { } } -func (r *Reactor) processBlockSyncBridge(ctx context.Context) { - for { - select { - case <-ctx.Done(): - return - case envelope := <-r.blockSyncOutBridgeCh: - if err := r.blockSyncCh.Send(ctx, envelope); err != nil { - return - } - } - } -} - // processPeerUpdate processes a PeerUpdate. -func (r *Reactor) processPeerUpdate(peerUpdate p2p.PeerUpdate) { +func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpdate, blockSyncCh *p2p.Channel) { r.logger.Debug("received peer update", "peer", peerUpdate.NodeID, "status", peerUpdate.Status) // XXX: Pool#RedoRequest can sometimes give us an empty peer. @@ -331,12 +303,20 @@ func (r *Reactor) processPeerUpdate(peerUpdate p2p.PeerUpdate) { switch peerUpdate.Status { case p2p.PeerStatusUp: // send a status update the newly added peer - r.blockSyncOutBridgeCh <- p2p.Envelope{ + if err := blockSyncCh.Send(ctx, p2p.Envelope{ To: peerUpdate.NodeID, Message: &bcproto.StatusResponse{ Base: r.store.Base(), Height: r.store.Height(), }, + }); err != nil { + r.pool.RemovePeer(peerUpdate.NodeID) + if err := blockSyncCh.SendError(ctx, p2p.PeerError{ + NodeID: peerUpdate.NodeID, + Err: err, + }); err != nil { + return + } } case p2p.PeerStatusDown: @@ -347,13 +327,13 @@ func (r *Reactor) processPeerUpdate(peerUpdate p2p.PeerUpdate) { // processPeerUpdates initiates a blocking process where we listen for and handle // PeerUpdate messages. When the reactor is stopped, we will catch the signal and // close the p2p PeerUpdatesCh gracefully. -func (r *Reactor) processPeerUpdates(ctx context.Context) { +func (r *Reactor) processPeerUpdates(ctx context.Context, peerUpdates *p2p.PeerUpdates, blockSyncCh *p2p.Channel) { for { select { case <-ctx.Done(): return - case peerUpdate := <-r.peerUpdates.Updates(): - r.processPeerUpdate(peerUpdate) + case peerUpdate := <-peerUpdates.Updates(): + r.processPeerUpdate(ctx, peerUpdate, blockSyncCh) } } } @@ -371,14 +351,25 @@ func (r *Reactor) SwitchToBlockSync(ctx context.Context, state sm.State) error { r.syncStartTime = time.Now() - go r.requestRoutine(ctx) + bsCh, err := r.chCreator(ctx, GetChannelDescriptor()) + if err != nil { + return err + } - go r.poolRoutine(ctx, true) + go r.requestRoutine(ctx, bsCh) + go r.poolRoutine(ctx, true, bsCh) + + if err := r.PublishStatus(types.EventDataBlockSyncStatus{ + Complete: false, + Height: state.LastBlockHeight, + }); err != nil { + return err + } return nil } -func (r *Reactor) requestRoutine(ctx context.Context) { +func (r *Reactor) requestRoutine(ctx context.Context, blockSyncCh *p2p.Channel) { statusUpdateTicker := time.NewTicker(statusUpdateIntervalSeconds * time.Second) defer statusUpdateTicker.Stop() @@ -387,33 +378,31 @@ func (r *Reactor) requestRoutine(ctx context.Context) { case <-ctx.Done(): return case request := <-r.requestsCh: - select { - case <-ctx.Done(): - return - case r.blockSyncOutBridgeCh <- p2p.Envelope{ + if err := blockSyncCh.Send(ctx, p2p.Envelope{ To: request.PeerID, Message: &bcproto.BlockRequest{Height: request.Height}, - }: + }); err != nil { + if err := blockSyncCh.SendError(ctx, p2p.PeerError{ + NodeID: request.PeerID, + Err: err, + }); err != nil { + return + } } - case pErr := <-r.errorsCh: - if err := r.blockSyncCh.SendError(ctx, p2p.PeerError{ + if err := blockSyncCh.SendError(ctx, p2p.PeerError{ NodeID: pErr.peerID, Err: pErr.err, }); err != nil { return } case <-statusUpdateTicker.C: - go func() { - select { - case <-ctx.Done(): - return - case r.blockSyncOutBridgeCh <- p2p.Envelope{ - Broadcast: true, - Message: &bcproto.StatusRequest{}, - }: - } - }() + if err := blockSyncCh.Send(ctx, p2p.Envelope{ + Broadcast: true, + Message: &bcproto.StatusRequest{}, + }); err != nil { + return + } } } } @@ -422,7 +411,7 @@ func (r *Reactor) requestRoutine(ctx context.Context) { // do. // // NOTE: Don't sleep in the FOR_LOOP or otherwise slow it down! -func (r *Reactor) poolRoutine(ctx context.Context, stateSynced bool) { +func (r *Reactor) poolRoutine(ctx context.Context, stateSynced bool, blockSyncCh *p2p.Channel) { var ( trySyncTicker = time.NewTicker(trySyncIntervalMS * time.Millisecond) switchToConsensusTicker = time.NewTicker(switchToConsensusIntervalSeconds * time.Second) @@ -445,8 +434,6 @@ func (r *Reactor) poolRoutine(ctx context.Context, stateSynced bool) { select { case <-ctx.Done(): return - case <-r.pool.exitedCh: - return case <-switchToConsensusTicker.C: var ( height, numPending, lenRequesters = r.pool.GetStatus() @@ -542,7 +529,7 @@ func (r *Reactor) poolRoutine(ctx context.Context, stateSynced bool) { // NOTE: We've already removed the peer's request, but we still need // to clean up the rest. peerID := r.pool.RedoRequest(first.Height) - if serr := r.blockSyncCh.SendError(ctx, p2p.PeerError{ + if serr := blockSyncCh.SendError(ctx, p2p.PeerError{ NodeID: peerID, Err: err, }); serr != nil { @@ -551,7 +538,7 @@ func (r *Reactor) poolRoutine(ctx context.Context, stateSynced bool) { peerID2 := r.pool.RedoRequest(second.Height) if peerID2 != peerID { - if serr := r.blockSyncCh.SendError(ctx, p2p.PeerError{ + if serr := blockSyncCh.SendError(ctx, p2p.PeerError{ NodeID: peerID2, Err: err, }); serr != nil { @@ -622,11 +609,11 @@ func (r *Reactor) GetRemainingSyncTime() time.Duration { return time.Duration(int64(remain * float64(time.Second))) } -func (r *Reactor) PublishStatus(ctx context.Context, event types.EventDataBlockSyncStatus) error { +func (r *Reactor) PublishStatus(event types.EventDataBlockSyncStatus) error { if r.eventBus == nil { return errors.New("event bus is not configured") } - return r.eventBus.PublishEventBlockSyncStatus(ctx, event) + return r.eventBus.PublishEventBlockSyncStatus(event) } // atomicBool is an atomic Boolean, safe for concurrent use by multiple diff --git a/internal/blocksync/reactor_test.go b/internal/blocksync/reactor_test.go index d73a522fa..857b0a519 100644 --- a/internal/blocksync/reactor_test.go +++ b/internal/blocksync/reactor_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/fortytw2/leaktest" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" dbm "github.com/tendermint/tm-db" @@ -14,7 +15,8 @@ import ( abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/internal/consensus" - "github.com/tendermint/tendermint/internal/mempool/mock" + "github.com/tendermint/tendermint/internal/eventbus" + mpmocks "github.com/tendermint/tendermint/internal/mempool/mocks" "github.com/tendermint/tendermint/internal/p2p" "github.com/tendermint/tendermint/internal/p2p/p2ptest" "github.com/tendermint/tendermint/internal/proxy" @@ -48,7 +50,6 @@ func setup( genDoc *types.GenesisDoc, privVal types.PrivValidator, maxBlockHeights []int64, - chBuf uint, ) *reactorTestSuite { t.Helper() @@ -60,7 +61,7 @@ func setup( "must specify at least one block height (nodes)") rts := &reactorTestSuite{ - logger: log.TestingLogger().With("module", "block_sync", "testCase", t.Name()), + logger: log.NewNopLogger().With("module", "block_sync", "testCase", t.Name()), network: p2ptest.MakeNetwork(ctx, t, p2ptest.NetworkOptions{NumNodes: numNodes}), nodes: make([]types.NodeID, 0, numNodes), reactors: make(map[types.NodeID]*Reactor, numNodes), @@ -106,7 +107,7 @@ func (rts *reactorTestSuite) addNode( ) { t.Helper() - logger := log.TestingLogger() + logger := log.NewNopLogger() rts.nodes = append(rts.nodes, nodeID) rts.app[nodeID] = proxy.New(abciclient.NewLocalClient(logger, &abci.BaseApplication{}), logger, proxy.NopMetrics()) @@ -120,14 +121,30 @@ func (rts *reactorTestSuite) addNode( state, err := sm.MakeGenesisState(genDoc) require.NoError(t, err) require.NoError(t, stateStore.Save(state)) + mp := &mpmocks.Mempool{} + mp.On("Lock").Return() + mp.On("Unlock").Return() + mp.On("FlushAppConn", mock.Anything).Return(nil) + mp.On("Update", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything).Return(nil) + + eventbus := eventbus.NewDefault(logger) + require.NoError(t, eventbus.Start(ctx)) blockExec := sm.NewBlockExecutor( stateStore, - log.TestingLogger(), + log.NewNopLogger(), rts.app[nodeID], - mock.Mempool{}, + mp, sm.EmptyEvidencePool{}, blockStore, + eventbus, + sm.NopMetrics(), ) for blockHeight := int64(1); blockHeight <= maxBlockHeight; blockHeight++ { @@ -154,8 +171,7 @@ func (rts *reactorTestSuite) addNode( ) } - thisBlock, err := sf.MakeBlock(state, blockHeight, lastCommit) - require.NoError(t, err) + thisBlock := sf.MakeBlock(state, blockHeight, lastCommit) thisParts, err := thisBlock.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) blockID := types.BlockID{Hash: thisBlock.Hash(), PartSetHeader: thisParts.Header()} @@ -173,20 +189,18 @@ func (rts *reactorTestSuite) addNode( chCreator := func(ctx context.Context, chdesc *p2p.ChannelDescriptor) (*p2p.Channel, error) { return rts.blockSyncChannels[nodeID], nil } - rts.reactors[nodeID], err = NewReactor( - ctx, + rts.reactors[nodeID] = NewReactor( rts.logger.With("nodeID", nodeID), stateStore, blockExec, blockStore, nil, chCreator, - rts.peerUpdates[nodeID], + func(ctx context.Context) *p2p.PeerUpdates { return rts.peerUpdates[nodeID] }, rts.blockSync, consensus.NopMetrics(), nil, // eventbus, can be nil ) - require.NoError(t, err) require.NoError(t, rts.reactors[nodeID].Start(ctx)) require.True(t, rts.reactors[nodeID].IsRunning()) @@ -210,10 +224,10 @@ func TestReactor_AbruptDisconnect(t *testing.T) { defer os.RemoveAll(cfg.RootDir) valSet, privVals := factory.ValidatorSet(ctx, t, 1, 30) - genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, nil) + genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, factory.ConsensusParams()) maxBlockHeight := int64(64) - rts := setup(ctx, t, genDoc, privVals[0], []int64{maxBlockHeight, 0}, 0) + rts := setup(ctx, t, genDoc, privVals[0], []int64{maxBlockHeight, 0}) require.Equal(t, maxBlockHeight, rts.reactors[rts.nodes[0]].store.Height()) @@ -250,10 +264,10 @@ func TestReactor_SyncTime(t *testing.T) { defer os.RemoveAll(cfg.RootDir) valSet, privVals := factory.ValidatorSet(ctx, t, 1, 30) - genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, nil) + genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, factory.ConsensusParams()) maxBlockHeight := int64(101) - rts := setup(ctx, t, genDoc, privVals[0], []int64{maxBlockHeight, 0}, 0) + rts := setup(ctx, t, genDoc, privVals[0], []int64{maxBlockHeight, 0}) require.Equal(t, maxBlockHeight, rts.reactors[rts.nodes[0]].store.Height()) rts.start(ctx, t) @@ -278,10 +292,10 @@ func TestReactor_NoBlockResponse(t *testing.T) { defer os.RemoveAll(cfg.RootDir) valSet, privVals := factory.ValidatorSet(ctx, t, 1, 30) - genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, nil) + genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, factory.ConsensusParams()) maxBlockHeight := int64(65) - rts := setup(ctx, t, genDoc, privVals[0], []int64{maxBlockHeight, 0}, 0) + rts := setup(ctx, t, genDoc, privVals[0], []int64{maxBlockHeight, 0}) require.Equal(t, maxBlockHeight, rts.reactors[rts.nodes[0]].store.Height()) @@ -331,9 +345,9 @@ func TestReactor_BadBlockStopsPeer(t *testing.T) { maxBlockHeight := int64(48) valSet, privVals := factory.ValidatorSet(ctx, t, 1, 30) - genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, nil) + genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, factory.ConsensusParams()) - rts := setup(ctx, t, genDoc, privVals[0], []int64{maxBlockHeight, 0, 0, 0, 0}, 1000) + rts := setup(ctx, t, genDoc, privVals[0], []int64{maxBlockHeight, 0, 0, 0, 0}) require.Equal(t, maxBlockHeight, rts.reactors[rts.nodes[0]].store.Height()) @@ -366,7 +380,7 @@ func TestReactor_BadBlockStopsPeer(t *testing.T) { // XXX: This causes a potential race condition. // See: https://github.com/tendermint/tendermint/issues/6005 valSet, otherPrivVals := factory.ValidatorSet(ctx, t, 1, 30) - otherGenDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, nil) + otherGenDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, factory.ConsensusParams()) newNode := rts.network.MakeNode(ctx, t, p2ptest.NodeOptions{ MaxPeers: uint16(len(rts.nodes) + 1), MaxConnected: uint16(len(rts.nodes) + 1), diff --git a/internal/consensus/byzantine_test.go b/internal/consensus/byzantine_test.go index f0df502f9..804ebdb18 100644 --- a/internal/consensus/byzantine_test.go +++ b/internal/consensus/byzantine_test.go @@ -48,7 +48,7 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { tickerFunc := newMockTickerFunc(true) valSet, privVals := factory.ValidatorSet(ctx, t, nValidators, 30) - genDoc := factory.GenesisDoc(config, time.Now(), valSet.Validators, nil) + genDoc := factory.GenesisDoc(config, time.Now(), valSet.Validators, factory.ConsensusParams()) states := make([]*State, nValidators) for i := 0; i < nValidators; i++ { @@ -68,7 +68,8 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { ensureDir(t, path.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal app := kvstore.NewApplication() vals := types.TM2PB.ValidatorUpdates(state.Validators) - app.InitChain(abci.RequestInitChain{Validators: vals}) + _, err = app.InitChain(ctx, &abci.RequestInitChain{Validators: vals}) + require.NoError(t, err) blockDB := dbm.NewMemDB() blockStore := store.NewBlockStore(blockDB) @@ -79,7 +80,7 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { // Make Mempool mempool := mempool.NewTxMempool( - log.TestingLogger().With("module", "mempool"), + log.NewNopLogger().With("module", "mempool"), thisConfig.Mempool, proxyAppConnMem, ) @@ -87,24 +88,21 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { mempool.EnableTxsAvailable() } + eventBus := eventbus.NewDefault(log.NewNopLogger().With("module", "events")) + require.NoError(t, eventBus.Start(ctx)) + // Make a full instance of the evidence pool evidenceDB := dbm.NewMemDB() - evpool, err := evidence.NewPool(logger.With("module", "evidence"), evidenceDB, stateStore, blockStore, evidence.NopMetrics()) - require.NoError(t, err) + evpool := evidence.NewPool(logger.With("module", "evidence"), evidenceDB, stateStore, blockStore, evidence.NopMetrics(), eventBus) // Make State - blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyAppConnCon, mempool, evpool, blockStore) - cs, err := NewState(ctx, logger, thisConfig.Consensus, stateStore, blockExec, blockStore, mempool, evpool) + blockExec := sm.NewBlockExecutor(stateStore, log.NewNopLogger(), proxyAppConnCon, mempool, evpool, blockStore, eventBus, sm.NopMetrics()) + cs, err := NewState(logger, thisConfig.Consensus, stateStore, blockExec, blockStore, mempool, evpool, eventBus) require.NoError(t, err) // set private validator pv := privVals[i] cs.SetPrivValidator(ctx, pv) - eventBus := eventbus.NewDefault(log.TestingLogger().With("module", "events")) - err = eventBus.Start(ctx) - require.NoError(t, err) - cs.SetEventBus(eventBus) - evpool.SetEventBus(eventBus) cs.SetTimeoutTicker(tickerFunc()) states[i] = cs @@ -144,8 +142,10 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { // send two votes to all peers (1st to one half, 2nd to another half) i := 0 for _, ps := range bzReactor.peers { + voteCh := rts.voteChannels[bzNodeID] if i < len(bzReactor.peers)/2 { - require.NoError(t, bzReactor.voteCh.Send(ctx, + + require.NoError(t, voteCh.Send(ctx, p2p.Envelope{ To: ps.peerID, Message: &tmcons.Vote{ @@ -153,7 +153,7 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { }, })) } else { - require.NoError(t, bzReactor.voteCh.Send(ctx, + require.NoError(t, voteCh.Send(ctx, p2p.Envelope{ To: ps.peerID, Message: &tmcons.Vote{ @@ -179,7 +179,6 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { require.NotNil(t, lazyNodeState.privValidator) var commit *types.Commit - var votes []*types.Vote switch { case lazyNodeState.Height == lazyNodeState.state.InitialHeight: // We're creating a proposal for the first block. @@ -188,7 +187,6 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { case lazyNodeState.LastCommit.HasTwoThirdsMajority(): // Make the commit from LastCommit commit = lazyNodeState.LastCommit.MakeCommit() - votes = lazyNodeState.LastCommit.GetVotes() default: // This shouldn't happen. lazyNodeState.logger.Error("enterPropose: Cannot propose anything: No commit for the previous block") return @@ -205,9 +203,10 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { } proposerAddr := lazyNodeState.privValidatorPubKey.Address() - block, blockParts, err := lazyNodeState.blockExec.CreateProposalBlock( - ctx, lazyNodeState.Height, lazyNodeState.state, commit, proposerAddr, votes, - ) + block, err := lazyNodeState.blockExec.CreateProposalBlock( + ctx, lazyNodeState.Height, lazyNodeState.state, commit, proposerAddr, lazyNodeState.LastCommit.GetVotes()) + require.NoError(t, err) + blockParts, err := block.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) // Flush the WAL. Otherwise, we may not recompute the same proposal to sign, @@ -300,267 +299,3 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { assert.Equal(t, prevoteHeight, ev.Height()) } } - -// 4 validators. 1 is byzantine. The other three are partitioned into A (1 val) and B (2 vals). -// byzantine validator sends conflicting proposals into A and B, -// and prevotes/precommits on both of them. -// B sees a commit, A doesn't. -// Heal partition and ensure A sees the commit -func TestByzantineConflictingProposalsWithPartition(t *testing.T) { - // TODO: https://github.com/tendermint/tendermint/issues/6092 - t.SkipNow() - - // n := 4 - // logger := consensusLogger().With("test", "byzantine") - // app := newCounter - - // states, cleanup := randConsensusState(n, "consensus_byzantine_test", newMockTickerFunc(false), app) - // t.Cleanup(cleanup) - - // // give the byzantine validator a normal ticker - // ticker := NewTimeoutTicker() - // ticker.SetLogger(states[0].logger) - // states[0].SetTimeoutTicker(ticker) - - // p2pLogger := logger.With("module", "p2p") - - // blocksSubs := make([]types.Subscription, n) - // reactors := make([]p2p.Reactor, n) - // for i := 0; i < n; i++ { - // // enable txs so we can create different proposals - // assertMempool(states[i].txNotifier).EnableTxsAvailable() - - // eventBus := states[i].eventBus - // eventBus.SetLogger(logger.With("module", "events", "validator", i)) - - // var err error - // blocksSubs[i], err = eventBus.Subscribe(ctx, testSubscriber, types.EventQueryNewBlock) - // require.NoError(t, err) - - // conR := NewReactor(states[i], true) // so we don't start the consensus states - // conR.SetLogger(logger.With("validator", i)) - // conR.SetEventBus(eventBus) - - // var conRI p2p.Reactor = conR - - // // make first val byzantine - // if i == 0 { - // conRI = NewByzantineReactor(conR) - // } - - // reactors[i] = conRI - // err = states[i].blockExec.Store().Save(states[i].state) // for save height 1's validators info - // require.NoError(t, err) - // } - - // switches := p2p.MakeConnectedSwitches(config.P2P, N, func(i int, sw *p2p.Switch) *p2p.Switch { - // sw.SetLogger(p2pLogger.With("validator", i)) - // sw.AddReactor("CONSENSUS", reactors[i]) - // return sw - // }, func(sws []*p2p.Switch, i, j int) { - // // the network starts partitioned with globally active adversary - // if i != 0 { - // return - // } - // p2p.Connect2Switches(sws, i, j) - // }) - - // // make first val byzantine - // // NOTE: Now, test validators are MockPV, which by default doesn't - // // do any safety checks. - // states[0].privValidator.(types.MockPV).DisableChecks() - // states[0].decideProposal = func(j int32) func(int64, int32) { - // return func(height int64, round int32) { - // byzantineDecideProposalFunc(t, height, round, states[j], switches[j]) - // } - // }(int32(0)) - // // We are setting the prevote function to do nothing because the prevoting - // // and precommitting are done alongside the proposal. - // states[0].doPrevote = func(height int64, round int32) {} - - // defer func() { - // for _, sw := range switches { - // err := sw.Stop() - // require.NoError(t, err) - // } - // }() - - // // start the non-byz state machines. - // // note these must be started before the byz - // for i := 1; i < n; i++ { - // cr := reactors[i].(*Reactor) - // cr.SwitchToConsensus(cr.conS.GetState(), false) - // } - - // // start the byzantine state machine - // byzR := reactors[0].(*ByzantineReactor) - // s := byzR.reactor.conS.GetState() - // byzR.reactor.SwitchToConsensus(s, false) - - // // byz proposer sends one block to peers[0] - // // and the other block to peers[1] and peers[2]. - // // note peers and switches order don't match. - // peers := switches[0].Peers().List() - - // // partition A - // ind0 := getSwitchIndex(switches, peers[0]) - - // // partition B - // ind1 := getSwitchIndex(switches, peers[1]) - // ind2 := getSwitchIndex(switches, peers[2]) - // p2p.Connect2Switches(switches, ind1, ind2) - - // // wait for someone in the big partition (B) to make a block - // <-blocksSubs[ind2].Out() - - // t.Log("A block has been committed. Healing partition") - // p2p.Connect2Switches(switches, ind0, ind1) - // p2p.Connect2Switches(switches, ind0, ind2) - - // // wait till everyone makes the first new block - // // (one of them already has) - // wg := new(sync.WaitGroup) - // for i := 1; i < N-1; i++ { - // wg.Add(1) - // go func(j int) { - // <-blocksSubs[j].Out() - // wg.Done() - // }(i) - // } - - // done := make(chan struct{}) - // go func() { - // wg.Wait() - // close(done) - // }() - - // tick := time.NewTicker(time.Second * 10) - // select { - // case <-done: - // case <-tick.C: - // for i, reactor := range reactors { - // t.Log(fmt.Sprintf("Consensus Reactor %v", i)) - // t.Log(fmt.Sprintf("%v", reactor)) - // } - // t.Fatalf("Timed out waiting for all validators to commit first block") - // } -} - -// func byzantineDecideProposalFunc(t *testing.T, height int64, round int32, cs *State, sw *p2p.Switch) { -// // byzantine user should create two proposals and try to split the vote. -// // Avoid sending on internalMsgQueue and running consensus state. - -// // Create a new proposal block from state/txs from the mempool. -// block1, blockParts1 := cs.createProposalBlock() -// polRound, propBlockID := cs.ValidRound, types.BlockID{Hash: block1.Hash(), PartSetHeader: blockParts1.Header()} -// proposal1 := types.NewProposal(height, round, polRound, propBlockID) -// p1 := proposal1.ToProto() -// if err := cs.privValidator.SignProposal(cs.state.ChainID, p1); err != nil { -// t.Error(err) -// } - -// proposal1.Signature = p1.Signature - -// // some new transactions come in (this ensures that the proposals are different) -// deliverTxsRange(cs, 0, 1) - -// // Create a new proposal block from state/txs from the mempool. -// block2, blockParts2 := cs.createProposalBlock() -// polRound, propBlockID = cs.ValidRound, types.BlockID{Hash: block2.Hash(), PartSetHeader: blockParts2.Header()} -// proposal2 := types.NewProposal(height, round, polRound, propBlockID) -// p2 := proposal2.ToProto() -// if err := cs.privValidator.SignProposal(cs.state.ChainID, p2); err != nil { -// t.Error(err) -// } - -// proposal2.Signature = p2.Signature - -// block1Hash := block1.Hash() -// block2Hash := block2.Hash() - -// // broadcast conflicting proposals/block parts to peers -// peers := sw.Peers().List() -// t.Logf("Byzantine: broadcasting conflicting proposals to %d peers", len(peers)) -// for i, peer := range peers { -// if i < len(peers)/2 { -// go sendProposalAndParts(height, round, cs, peer, proposal1, block1Hash, blockParts1) -// } else { -// go sendProposalAndParts(height, round, cs, peer, proposal2, block2Hash, blockParts2) -// } -// } -// } - -// func sendProposalAndParts( -// height int64, -// round int32, -// cs *State, -// peer p2p.Peer, -// proposal *types.Proposal, -// blockHash []byte, -// parts *types.PartSet, -// ) { -// // proposal -// msg := &ProposalMessage{Proposal: proposal} -// peer.Send(DataChannel, MustEncode(msg)) - -// // parts -// for i := 0; i < int(parts.Total()); i++ { -// part := parts.GetPart(i) -// msg := &BlockPartMessage{ -// Height: height, // This tells peer that this part applies to us. -// Round: round, // This tells peer that this part applies to us. -// Part: part, -// } -// peer.Send(DataChannel, MustEncode(msg)) -// } - -// // votes -// cs.mtx.Lock() -// prevote, _ := cs.signVote(tmproto.PrevoteType, blockHash, parts.Header()) -// precommit, _ := cs.signVote(tmproto.PrecommitType, blockHash, parts.Header()) -// cs.mtx.Unlock() - -// peer.Send(VoteChannel, MustEncode(&VoteMessage{prevote})) -// peer.Send(VoteChannel, MustEncode(&VoteMessage{precommit})) -// } - -// type ByzantineReactor struct { -// service.Service -// reactor *Reactor -// } - -// func NewByzantineReactor(conR *Reactor) *ByzantineReactor { -// return &ByzantineReactor{ -// Service: conR, -// reactor: conR, -// } -// } - -// func (br *ByzantineReactor) SetSwitch(s *p2p.Switch) { br.reactor.SetSwitch(s) } -// func (br *ByzantineReactor) GetChannels() []*p2p.ChannelDescriptor { return br.reactor.GetChannels() } - -// func (br *ByzantineReactor) AddPeer(peer p2p.Peer) { -// if !br.reactor.IsRunning() { -// return -// } - -// // Create peerState for peer -// peerState := NewPeerState(peer).SetLogger(br.reactor.logger) -// peer.Set(types.PeerStateKey, peerState) - -// // Send our state to peer. -// // If we're syncing, broadcast a RoundStepMessage later upon SwitchToConsensus(). -// if !br.reactor.waitSync { -// br.reactor.sendNewRoundStepMessage(peer) -// } -// } - -// func (br *ByzantineReactor) RemovePeer(peer p2p.Peer, reason interface{}) { -// br.reactor.RemovePeer(peer, reason) -// } - -// func (br *ByzantineReactor) Receive(chID byte, peer p2p.Peer, msgBytes []byte) { -// br.reactor.Receive(chID, peer, msgBytes) -// } - -// func (br *ByzantineReactor) InitPeer(peer p2p.Peer) p2p.Peer { return peer } diff --git a/internal/consensus/common_test.go b/internal/consensus/common_test.go index 4a6b96b2d..ca1db8425 100644 --- a/internal/consensus/common_test.go +++ b/internal/consensus/common_test.go @@ -12,6 +12,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" dbm "github.com/tendermint/tm-db" @@ -69,6 +70,9 @@ func configSetup(t *testing.T) *config.Config { require.NoError(t, err) t.Cleanup(func() { os.RemoveAll(configByzantineTest.RootDir) }) + walDir := filepath.Dir(cfg.Consensus.WalFile()) + ensureDir(t, walDir, 0700) + return cfg } @@ -109,7 +113,8 @@ func (vs *validatorStub) signVote( ctx context.Context, voteType tmproto.SignedMsgType, chainID string, - blockID types.BlockID) (*types.Vote, error) { + blockID types.BlockID, + voteExtension []byte) (*types.Vote, error) { pubKey, err := vs.PrivValidator.GetPubKey(ctx) if err != nil { @@ -117,28 +122,30 @@ func (vs *validatorStub) signVote( } vote := &types.Vote{ - ValidatorIndex: vs.Index, - ValidatorAddress: pubKey.Address(), + Type: voteType, Height: vs.Height, Round: vs.Round, - Timestamp: vs.clock.Now(), - Type: voteType, BlockID: blockID, - VoteExtension: types.VoteExtensionFromProto(kvstore.ConstructVoteExtension(pubKey.Address())), + Timestamp: vs.clock.Now(), + ValidatorAddress: pubKey.Address(), + ValidatorIndex: vs.Index, + Extension: voteExtension, } v := vote.ToProto() - if err := vs.PrivValidator.SignVote(ctx, chainID, v); err != nil { + if err = vs.PrivValidator.SignVote(ctx, chainID, v); err != nil { return nil, fmt.Errorf("sign vote failed: %w", err) } - // ref: signVote in FilePV, the vote should use the privious vote info when the sign data is the same. + // ref: signVote in FilePV, the vote should use the previous vote info when the sign data is the same. if signDataIsEqual(vs.lastVote, v) { v.Signature = vs.lastVote.Signature v.Timestamp = vs.lastVote.Timestamp + v.ExtensionSignature = vs.lastVote.ExtensionSignature } vote.Signature = v.Signature vote.Timestamp = v.Timestamp + vote.ExtensionSignature = v.ExtensionSignature return vote, err } @@ -152,13 +159,13 @@ func signVote( chainID string, blockID types.BlockID) *types.Vote { - v, err := vs.signVote(ctx, voteType, chainID, blockID) + var ext []byte + if voteType == tmproto.PrecommitType { + ext = []byte("extension") + } + v, err := vs.signVote(ctx, voteType, chainID, blockID, ext) require.NoError(t, err, "failed to sign vote") - // TODO: remove hardcoded vote extension. - // currently set for abci/examples/kvstore/persistent_kvstore.go - v.VoteExtension = types.VoteExtensionFromProto(kvstore.ConstructVoteExtension(v.ValidatorAddress)) - vs.lastVote = v return v @@ -239,7 +246,9 @@ func decideProposal( t.Helper() cs1.mtx.Lock() - block, blockParts, err := cs1.createProposalBlock(ctx) + block, err := cs1.createProposalBlock(ctx) + require.NoError(t, err) + blockParts, err := block.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) validRound := cs1.ValidRound chainID := cs1.state.ChainID @@ -346,18 +355,19 @@ func validatePrecommit( require.True(t, bytes.Equal(vote.BlockID.Hash, votedBlockHash), "Expected precommit to be for proposal block") } + rs := cs.GetRoundState() if lockedBlockHash == nil { - require.False(t, cs.LockedRound != lockRound || cs.LockedBlock != nil, + require.False(t, rs.LockedRound != lockRound || rs.LockedBlock != nil, "Expected to be locked on nil at round %d. Got locked at round %d with block %v", lockRound, - cs.LockedRound, - cs.LockedBlock) + rs.LockedRound, + rs.LockedBlock) } else { - require.False(t, cs.LockedRound != lockRound || !bytes.Equal(cs.LockedBlock.Hash(), lockedBlockHash), + require.False(t, rs.LockedRound != lockRound || !bytes.Equal(rs.LockedBlock.Hash(), lockedBlockHash), "Expected block to be locked on round %d, got %d. Got locked block %X, expected %X", lockRound, - cs.LockedRound, - cs.LockedBlock.Hash(), + rs.LockedRound, + rs.LockedBlock.Hash(), lockedBlockHash) } } @@ -482,15 +492,18 @@ func newStateWithConfigAndBlockStore( stateStore := sm.NewStore(stateDB) require.NoError(t, stateStore.Save(state)) - blockExec := sm.NewBlockExecutor(stateStore, logger, proxyAppConnCon, mempool, evpool, blockStore) - cs, err := NewState(ctx, - logger.With("module", "consensus"), + eventBus := eventbus.NewDefault(logger.With("module", "events")) + require.NoError(t, eventBus.Start(ctx)) + + blockExec := sm.NewBlockExecutor(stateStore, logger, proxyAppConnCon, mempool, evpool, blockStore, eventBus, sm.NopMetrics()) + cs, err := NewState(logger.With("module", "consensus"), thisConfig.Consensus, stateStore, blockExec, blockStore, mempool, evpool, + eventBus, ) if err != nil { t.Fatal(err) @@ -498,10 +511,6 @@ func newStateWithConfigAndBlockStore( cs.SetPrivValidator(ctx, pv) - eventBus := eventbus.NewDefault(logger.With("module", "events")) - require.NoError(t, eventBus.Start(ctx)) - - cs.SetEventBus(eventBus) return cs } @@ -543,6 +552,7 @@ func makeState(ctx context.Context, t *testing.T, args makeStateArgs) (*State, [ } state, privVals := makeGenesisState(ctx, t, args.config, genesisStateArgs{ + Params: factory.ConsensusParams(), Validators: validators, }) @@ -725,10 +735,9 @@ func ensureVoteMatch(t *testing.T, voteCh <-chan tmpubsub.Message, height int64, msg.Data()) vote := voteEvent.Vote - require.Equal(t, height, vote.Height) - require.Equal(t, round, vote.Round) - - require.Equal(t, voteType, vote.Type) + assert.Equal(t, height, vote.Height, "expected height %d, but got %d", height, vote.Height) + assert.Equal(t, round, vote.Round, "expected round %d, but got %d", round, vote.Round) + assert.Equal(t, voteType, vote.Type, "expected type %s, but got %s", voteType, vote.Type) if hash == nil { require.Nil(t, vote.BlockID.Hash, "Expected prevote to be for nil, got %X", vote.BlockID.Hash) } else { @@ -736,6 +745,7 @@ func ensureVoteMatch(t *testing.T, voteCh <-chan tmpubsub.Message, height int64, } } } + func ensureVote(t *testing.T, voteCh <-chan tmpubsub.Message, height int64, round int32, voteType tmproto.SignedMsgType) { t.Helper() msg := ensureMessageBeforeTimeout(t, voteCh, ensureTimeout) @@ -744,10 +754,9 @@ func ensureVote(t *testing.T, voteCh <-chan tmpubsub.Message, height int64, roun msg.Data()) vote := voteEvent.Vote - require.Equal(t, height, vote.Height) - require.Equal(t, round, vote.Round) - - require.Equal(t, voteType, vote.Type) + require.Equal(t, height, vote.Height, "expected height %d, but got %d", height, vote.Height) + require.Equal(t, round, vote.Round, "expected round %d, but got %d", round, vote.Round) + require.Equal(t, voteType, vote.Type, "expected type %s, but got %s", voteType, vote.Type) } func ensureNewEventOnChannel(t *testing.T, ch <-chan tmpubsub.Message) { @@ -772,7 +781,7 @@ func ensureMessageBeforeTimeout(t *testing.T, ch <-chan tmpubsub.Message, to tim // consensusLogger is a TestingLogger which uses a different // color for each validator ("validator" key must exist). func consensusLogger() log.Logger { - return log.TestingLogger().With("module", "consensus") + return log.NewNopLogger().With("module", "consensus") } func makeConsensusState( @@ -785,9 +794,10 @@ func makeConsensusState( configOpts ...func(*config.Config), ) ([]*State, cleanupFunc) { t.Helper() + tempDir := t.TempDir() valSet, privVals := factory.ValidatorSet(ctx, t, nValidators, 30) - genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, nil) + genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, factory.ConsensusParams()) css := make([]*State, nValidators) logger := consensusLogger() @@ -799,7 +809,7 @@ func makeConsensusState( blockStore := store.NewBlockStore(dbm.NewMemDB()) // each state needs its own db state, err := sm.MakeGenesisState(genDoc) require.NoError(t, err) - thisConfig, err := ResetConfig(t.TempDir(), fmt.Sprintf("%s_%d", testName, i)) + thisConfig, err := ResetConfig(tempDir, fmt.Sprintf("%s_%d", testName, i)) require.NoError(t, err) configRootDirs = append(configRootDirs, thisConfig.RootDir) @@ -808,13 +818,15 @@ func makeConsensusState( opt(thisConfig) } - ensureDir(t, filepath.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal + walDir := filepath.Dir(thisConfig.Consensus.WalFile()) + ensureDir(t, walDir, 0700) app := kvstore.NewApplication() closeFuncs = append(closeFuncs, app.Close) vals := types.TM2PB.ValidatorUpdates(state.Validators) - app.InitChain(abci.RequestInitChain{Validators: vals}) + _, err = app.InitChain(ctx, &abci.RequestInitChain{Validators: vals}) + require.NoError(t, err) l := logger.With("validator", i, "module", "consensus") css[i] = newStateWithConfigAndBlockStore(ctx, t, l, thisConfig, state, privVals[i], app, blockStore) @@ -845,7 +857,7 @@ func randConsensusNetWithPeers( t.Helper() valSet, privVals := factory.ValidatorSet(ctx, t, nValidators, testMinPower) - genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, nil) + genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, factory.ConsensusParams()) css := make([]*State, nPeers) t.Helper() logger := consensusLogger() @@ -878,11 +890,15 @@ func randConsensusNetWithPeers( app := appFunc(logger, filepath.Join(cfg.DBDir(), fmt.Sprintf("%s_%d", testName, i))) vals := types.TM2PB.ValidatorUpdates(state.Validators) - if _, ok := app.(*kvstore.PersistentKVStoreApplication); ok { - // simulate handshake, receive app version. If don't do this, replay test will fail + switch app.(type) { + // simulate handshake, receive app version. If don't do this, replay test will fail + case *kvstore.PersistentKVStoreApplication: + state.Version.Consensus.App = kvstore.ProtocolVersion + case *kvstore.Application: state.Version.Consensus.App = kvstore.ProtocolVersion } - app.InitChain(abci.RequestInitChain{Validators: vals}) + _, err = app.InitChain(ctx, &abci.RequestInitChain{Validators: vals}) + require.NoError(t, err) // sm.SaveState(stateDB,state) //height 1's validatorsInfo already saved in LoadStateFromDBOrGenesisDoc above css[i] = newStateWithConfig(ctx, t, logger.With("validator", i, "module", "consensus"), thisConfig, state, privVal, app) @@ -966,10 +982,6 @@ func newEpehemeralKVStore(_ log.Logger, _ string) abci.Application { return kvstore.NewApplication() } -func newPersistentKVStore(logger log.Logger, dbDir string) abci.Application { - return kvstore.NewPersistentKVStoreApplication(logger, dbDir) -} - func signDataIsEqual(v1 *types.Vote, v2 *tmproto.Vote) bool { if v1 == nil || v2 == nil { return false @@ -980,5 +992,6 @@ func signDataIsEqual(v1 *types.Vote, v2 *tmproto.Vote) bool { v1.Height == v2.GetHeight() && v1.Round == v2.Round && bytes.Equal(v1.ValidatorAddress.Bytes(), v2.GetValidatorAddress()) && - v1.ValidatorIndex == v2.GetValidatorIndex() + v1.ValidatorIndex == v2.GetValidatorIndex() && + bytes.Equal(v1.Extension, v2.Extension) } diff --git a/internal/consensus/invalid_test.go b/internal/consensus/invalid_test.go index 033b096ba..93c5cea1b 100644 --- a/internal/consensus/invalid_test.go +++ b/internal/consensus/invalid_test.go @@ -26,13 +26,13 @@ func TestReactorInvalidPrecommit(t *testing.T) { config := configSetup(t) - n := 4 + const n = 2 states, cleanup := makeConsensusState(ctx, t, config, n, "consensus_reactor_test", newMockTickerFunc(true)) t.Cleanup(cleanup) - for i := 0; i < 4; i++ { + for i := 0; i < n; i++ { ticker := NewTimeoutTicker(states[i].logger) states[i].SetTimeoutTicker(ticker) } @@ -57,7 +57,7 @@ func TestReactorInvalidPrecommit(t *testing.T) { privVal := byzState.privValidator byzState.doPrevote = func(ctx context.Context, height int64, round int32) { defer close(signal) - invalidDoPrevoteFunc(ctx, t, height, round, byzState, byzReactor, privVal) + invalidDoPrevoteFunc(ctx, t, height, round, byzState, byzReactor, rts.voteChannels[node.NodeID], privVal) } byzState.mtx.Unlock() @@ -107,6 +107,7 @@ func invalidDoPrevoteFunc( round int32, cs *State, r *Reactor, + voteCh *p2p.Channel, pv types.PrivValidator, ) { // routine to: @@ -155,7 +156,7 @@ func invalidDoPrevoteFunc( count := 0 for _, peerID := range ids { count++ - err := r.voteCh.Send(ctx, p2p.Envelope{ + err := voteCh.Send(ctx, p2p.Envelope{ To: peerID, Message: &tmcons.Vote{ Vote: precommit.ToProto(), diff --git a/internal/consensus/mempool_test.go b/internal/consensus/mempool_test.go index bad688982..ac04e4027 100644 --- a/internal/consensus/mempool_test.go +++ b/internal/consensus/mempool_test.go @@ -17,6 +17,7 @@ import ( "github.com/tendermint/tendermint/internal/mempool" sm "github.com/tendermint/tendermint/internal/state" "github.com/tendermint/tendermint/internal/store" + "github.com/tendermint/tendermint/internal/test/factory" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/types" ) @@ -42,8 +43,9 @@ func TestMempoolNoProgressUntilTxsAvailable(t *testing.T) { config.Consensus.CreateEmptyBlocks = false state, privVals := makeGenesisState(ctx, t, baseConfig, genesisStateArgs{ Validators: 1, - Power: 10}) - cs := newStateWithConfig(ctx, t, log.TestingLogger(), config, state, privVals[0], NewCounterApplication()) + Power: 10, + Params: factory.ConsensusParams()}) + cs := newStateWithConfig(ctx, t, log.NewNopLogger(), config, state, privVals[0], NewCounterApplication()) assertMempool(t, cs.txNotifier).EnableTxsAvailable() height, round := cs.Height, cs.Round newBlockCh := subscribe(ctx, t, cs.eventBus, types.EventQueryNewBlock) @@ -69,8 +71,9 @@ func TestMempoolProgressAfterCreateEmptyBlocksInterval(t *testing.T) { config.Consensus.CreateEmptyBlocksInterval = ensureTimeout state, privVals := makeGenesisState(ctx, t, baseConfig, genesisStateArgs{ Validators: 1, - Power: 10}) - cs := newStateWithConfig(ctx, t, log.TestingLogger(), config, state, privVals[0], NewCounterApplication()) + Power: 10, + Params: factory.ConsensusParams()}) + cs := newStateWithConfig(ctx, t, log.NewNopLogger(), config, state, privVals[0], NewCounterApplication()) assertMempool(t, cs.txNotifier).EnableTxsAvailable() @@ -94,8 +97,9 @@ func TestMempoolProgressInHigherRound(t *testing.T) { config.Consensus.CreateEmptyBlocks = false state, privVals := makeGenesisState(ctx, t, baseConfig, genesisStateArgs{ Validators: 1, - Power: 10}) - cs := newStateWithConfig(ctx, t, log.TestingLogger(), config, state, privVals[0], NewCounterApplication()) + Power: 10, + Params: factory.ConsensusParams()}) + cs := newStateWithConfig(ctx, t, log.NewNopLogger(), config, state, privVals[0], NewCounterApplication()) assertMempool(t, cs.txNotifier).EnableTxsAvailable() height, round := cs.Height, cs.Round newBlockCh := subscribe(ctx, t, cs.eventBus, types.EventQueryNewBlock) @@ -119,7 +123,7 @@ func TestMempoolProgressInHigherRound(t *testing.T) { ensureNewRound(t, newRoundCh, height, round) // first round at next height checkTxsRange(ctx, t, cs, 0, 1) // we deliver txs, but don't set a proposal so we get the next round - ensureNewTimeout(t, timeoutCh, height, round, cs.config.TimeoutPropose.Nanoseconds()) + ensureNewTimeout(t, timeoutCh, height, round, cs.state.ConsensusParams.Timeout.ProposeTimeout(round).Nanoseconds()) round++ // moving to the next round ensureNewRound(t, newRoundCh, height, round) // wait for the next round @@ -142,10 +146,12 @@ func TestMempoolTxConcurrentWithCommit(t *testing.T) { defer cancel() config := configSetup(t) - logger := log.TestingLogger() + logger := log.NewNopLogger() state, privVals := makeGenesisState(ctx, t, config, genesisStateArgs{ Validators: 1, - Power: 10}) + Power: 10, + Params: factory.ConsensusParams(), + }) stateStore := sm.NewStore(dbm.NewMemDB()) blockStore := store.NewBlockStore(dbm.NewMemDB()) @@ -180,11 +186,12 @@ func TestMempoolRmBadTx(t *testing.T) { state, privVals := makeGenesisState(ctx, t, config, genesisStateArgs{ Validators: 1, - Power: 10}) + Power: 10, + Params: factory.ConsensusParams()}) app := NewCounterApplication() stateStore := sm.NewStore(dbm.NewMemDB()) blockStore := store.NewBlockStore(dbm.NewMemDB()) - cs := newStateWithConfigAndBlockStore(ctx, t, log.TestingLogger(), config, state, privVals[0], app, blockStore) + cs := newStateWithConfigAndBlockStore(ctx, t, log.NewNopLogger(), config, state, privVals[0], app, blockStore) err := stateStore.Save(state) require.NoError(t, err) @@ -192,10 +199,12 @@ func TestMempoolRmBadTx(t *testing.T) { txBytes := make([]byte, 8) binary.BigEndian.PutUint64(txBytes, uint64(0)) - resFinalize := app.FinalizeBlock(abci.RequestFinalizeBlock{Txs: [][]byte{txBytes}}) + resFinalize, err := app.FinalizeBlock(ctx, &abci.RequestFinalizeBlock{Txs: [][]byte{txBytes}}) + require.NoError(t, err) assert.False(t, resFinalize.TxResults[0].IsErr(), fmt.Sprintf("expected no error. got %v", resFinalize)) - resCommit := app.Commit() + resCommit, err := app.Commit(ctx) + require.NoError(t, err) assert.True(t, len(resCommit.Data) > 0) emptyMempoolCh := make(chan struct{}) @@ -212,7 +221,7 @@ func TestMempoolRmBadTx(t *testing.T) { checkTxRespCh <- struct{}{} }, mempool.TxInfo{}) if err != nil { - t.Errorf("error after CheckTx: %w", err) + t.Errorf("error after CheckTx: %v", err) return } @@ -260,11 +269,11 @@ func NewCounterApplication() *CounterApplication { return &CounterApplication{} } -func (app *CounterApplication) Info(req abci.RequestInfo) abci.ResponseInfo { - return abci.ResponseInfo{Data: fmt.Sprintf("txs:%v", app.txCount)} +func (app *CounterApplication) Info(_ context.Context, req *abci.RequestInfo) (*abci.ResponseInfo, error) { + return &abci.ResponseInfo{Data: fmt.Sprintf("txs:%v", app.txCount)}, nil } -func (app *CounterApplication) FinalizeBlock(req abci.RequestFinalizeBlock) abci.ResponseFinalizeBlock { +func (app *CounterApplication) FinalizeBlock(_ context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { respTxs := make([]*abci.ExecTxResult, len(req.Txs)) for i, tx := range req.Txs { txValue := txAsUint64(tx) @@ -278,18 +287,19 @@ func (app *CounterApplication) FinalizeBlock(req abci.RequestFinalizeBlock) abci app.txCount++ respTxs[i] = &abci.ExecTxResult{Code: code.CodeTypeOK} } - return abci.ResponseFinalizeBlock{TxResults: respTxs} + return &abci.ResponseFinalizeBlock{TxResults: respTxs}, nil } -func (app *CounterApplication) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { +func (app *CounterApplication) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) { txValue := txAsUint64(req.Tx) if txValue != uint64(app.mempoolTxCount) { - return abci.ResponseCheckTx{ + return &abci.ResponseCheckTx{ Code: code.CodeTypeBadNonce, - Log: fmt.Sprintf("Invalid nonce. Expected %v, got %v", app.mempoolTxCount, txValue)} + Log: fmt.Sprintf("Invalid nonce. Expected %v, got %v", app.mempoolTxCount, txValue), + }, nil } app.mempoolTxCount++ - return abci.ResponseCheckTx{Code: code.CodeTypeOK} + return &abci.ResponseCheckTx{Code: code.CodeTypeOK}, nil } func txAsUint64(tx []byte) uint64 { @@ -298,22 +308,32 @@ func txAsUint64(tx []byte) uint64 { return binary.BigEndian.Uint64(tx8) } -func (app *CounterApplication) Commit() abci.ResponseCommit { +func (app *CounterApplication) Commit(context.Context) (*abci.ResponseCommit, error) { app.mempoolTxCount = app.txCount if app.txCount == 0 { - return abci.ResponseCommit{} + return &abci.ResponseCommit{}, nil } hash := make([]byte, 8) binary.BigEndian.PutUint64(hash, uint64(app.txCount)) - return abci.ResponseCommit{Data: hash} + return &abci.ResponseCommit{Data: hash}, nil } -func (app *CounterApplication) PrepareProposal( - req abci.RequestPrepareProposal) abci.ResponsePrepareProposal { - return abci.ResponsePrepareProposal{BlockData: req.BlockData} +func (app *CounterApplication) PrepareProposal(_ context.Context, req *abci.RequestPrepareProposal) (*abci.ResponsePrepareProposal, error) { + trs := make([]*abci.TxRecord, 0, len(req.Txs)) + var totalBytes int64 + for _, tx := range req.Txs { + totalBytes += int64(len(tx)) + if totalBytes > req.MaxTxBytes { + break + } + trs = append(trs, &abci.TxRecord{ + Action: abci.TxRecord_UNMODIFIED, + Tx: tx, + }) + } + return &abci.ResponsePrepareProposal{TxRecords: trs}, nil } -func (app *CounterApplication) ProcessProposal( - req abci.RequestProcessProposal) abci.ResponseProcessProposal { - return abci.ResponseProcessProposal{Accept: true} +func (app *CounterApplication) ProcessProposal(_ context.Context, req *abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error) { + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil } diff --git a/internal/consensus/metrics.go b/internal/consensus/metrics.go index 5934be420..ed31ec636 100644 --- a/internal/consensus/metrics.go +++ b/internal/consensus/metrics.go @@ -92,12 +92,12 @@ type Metrics struct { // be above 2/3 of the total voting power of the network defines the endpoint // the endpoint of the interval. Subtract the proposal timestamp from this endpoint // to obtain the quorum delay. - QuorumPrevoteMessageDelay metrics.Gauge + QuorumPrevoteDelay metrics.Gauge - // FullPrevoteMessageDelay is the interval in seconds between the proposal + // FullPrevoteDelay is the interval in seconds between the proposal // timestamp and the timestamp of the latest prevote in a round where 100% // of the voting power on the network issued prevotes. - FullPrevoteMessageDelay metrics.Gauge + FullPrevoteDelay metrics.Gauge // ProposalTimestampDifference is the difference between the timestamp in // the proposal message and the local time of the validator at the time @@ -257,17 +257,17 @@ func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics { Help: "Time spent per step.", Buckets: stdprometheus.ExponentialBucketsRange(0.1, 100, 8), }, append(labels, "step")).With(labelsAndValues...), - QuorumPrevoteMessageDelay: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + QuorumPrevoteDelay: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, - Name: "quorum_prevote_message_delay", + Name: "quorum_prevote_delay", Help: "Difference in seconds between the proposal timestamp and the timestamp " + "of the latest prevote that achieved a quorum in the prevote step.", }, append(labels, "proposer_address")).With(labelsAndValues...), - FullPrevoteMessageDelay: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + FullPrevoteDelay: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, - Name: "full_prevote_message_delay", + Name: "full_prevote_delay", Help: "Difference in seconds between the proposal timestamp and the timestamp " + "of the latest prevote that achieved 100% of the voting power in the prevote step.", }, append(labels, "proposer_address")).With(labelsAndValues...), @@ -314,8 +314,8 @@ func NopMetrics() *Metrics { BlockParts: discard.NewCounter(), BlockGossipReceiveLatency: discard.NewHistogram(), BlockGossipPartsReceived: discard.NewCounter(), - QuorumPrevoteMessageDelay: discard.NewGauge(), - FullPrevoteMessageDelay: discard.NewGauge(), + QuorumPrevoteDelay: discard.NewGauge(), + FullPrevoteDelay: discard.NewGauge(), ProposalTimestampDifference: discard.NewHistogram(), } } diff --git a/internal/consensus/mocks/cons_sync_reactor.go b/internal/consensus/mocks/cons_sync_reactor.go index 5ac592f0d..f904e9129 100644 --- a/internal/consensus/mocks/cons_sync_reactor.go +++ b/internal/consensus/mocks/cons_sync_reactor.go @@ -3,6 +3,8 @@ package mocks import ( + testing "testing" + mock "github.com/stretchr/testify/mock" state "github.com/tendermint/tendermint/internal/state" ) @@ -26,3 +28,13 @@ func (_m *ConsSyncReactor) SetStateSyncingMetrics(_a0 float64) { func (_m *ConsSyncReactor) SwitchToConsensus(_a0 state.State, _a1 bool) { _m.Called(_a0, _a1) } + +// NewConsSyncReactor creates a new instance of ConsSyncReactor. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewConsSyncReactor(t testing.TB) *ConsSyncReactor { + mock := &ConsSyncReactor{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/consensus/msgs.go b/internal/consensus/msgs.go index 8b19db423..c59c06a41 100644 --- a/internal/consensus/msgs.go +++ b/internal/consensus/msgs.go @@ -220,9 +220,13 @@ type VoteMessage struct { func (*VoteMessage) TypeTag() string { return "tendermint/Vote" } -// ValidateBasic performs basic validation. +// ValidateBasic checks whether the vote within the message is well-formed. func (m *VoteMessage) ValidateBasic() error { - return m.Vote.ValidateBasic() + // Here we validate votes with vote extensions, since we require vote + // extensions to be sent in precommit messages during consensus. Prevote + // messages should never have vote extensions, and this is also validated + // here. + return m.Vote.ValidateWithExtension() } // String returns a string representation. @@ -534,6 +538,8 @@ func MsgFromProto(msg *tmcons.Message) (Message, error) { Part: parts, } case *tmcons.Message_Vote: + // Vote validation will be handled in the vote message ValidateBasic + // call below. vote, err := types.VoteFromProto(msg.Vote.Vote) if err != nil { return nil, fmt.Errorf("vote msg to proto error: %w", err) diff --git a/internal/consensus/msgs_test.go b/internal/consensus/msgs_test.go index e85936820..5a6465294 100644 --- a/internal/consensus/msgs_test.go +++ b/internal/consensus/msgs_test.go @@ -12,8 +12,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/merkle" - "github.com/tendermint/tendermint/crypto/tmhash" cstypes "github.com/tendermint/tendermint/internal/consensus/types" "github.com/tendermint/tendermint/internal/test/factory" "github.com/tendermint/tendermint/libs/bits" @@ -357,11 +357,6 @@ func TestConsMsgsVectors(t *testing.T) { } pbProposal := proposal.ToProto() - ext := types.VoteExtension{ - AppDataToSign: []byte("signed"), - AppDataSelfAuthenticating: []byte("auth"), - } - v := &types.Vote{ ValidatorAddress: []byte("add_more_exclamation"), ValidatorIndex: 1, @@ -370,7 +365,7 @@ func TestConsMsgsVectors(t *testing.T) { Timestamp: date, Type: tmproto.PrecommitType, BlockID: bi, - VoteExtension: ext, + Extension: []byte("extension"), } vpb := v.ToProto() @@ -407,7 +402,7 @@ func TestConsMsgsVectors(t *testing.T) { "2a36080110011a3008011204746573741a26080110011a206164645f6d6f72655f6578636c616d6174696f6e5f6d61726b735f636f64652d"}, {"Vote", &tmcons.Message{Sum: &tmcons.Message_Vote{ Vote: &tmcons.Vote{Vote: vpb}}}, - "3280010a7e0802100122480a206164645f6d6f72655f6578636c616d6174696f6e5f6d61726b735f636f64652d1224080112206164645f6d6f72655f6578636c616d6174696f6e5f6d61726b735f636f64652d2a0608c0b89fdc0532146164645f6d6f72655f6578636c616d6174696f6e38014a0e0a067369676e6564120461757468"}, + "327b0a790802100122480a206164645f6d6f72655f6578636c616d6174696f6e5f6d61726b735f636f64652d1224080112206164645f6d6f72655f6578636c616d6174696f6e5f6d61726b735f636f64652d2a0608c0b89fdc0532146164645f6d6f72655f6578636c616d6174696f6e38014a09657874656e73696f6e"}, {"HasVote", &tmcons.Message{Sum: &tmcons.Message_HasVote{ HasVote: &tmcons.HasVote{Height: 1, Round: 1, Type: tmproto.PrevoteType, Index: 1}}}, "3a080801100118012001"}, @@ -672,7 +667,7 @@ func TestProposalPOLMessageValidateBasic(t *testing.T) { func TestBlockPartMessageValidateBasic(t *testing.T) { testPart := new(types.Part) - testPart.Proof.LeafHash = tmhash.Sum([]byte("leaf")) + testPart.Proof.LeafHash = crypto.Checksum([]byte("leaf")) testCases := []struct { testName string messageHeight int64 diff --git a/internal/consensus/pbts_test.go b/internal/consensus/pbts_test.go index f99ec7e39..a34870c9f 100644 --- a/internal/consensus/pbts_test.go +++ b/internal/consensus/pbts_test.go @@ -12,6 +12,7 @@ import ( "github.com/tendermint/tendermint/abci/example/kvstore" "github.com/tendermint/tendermint/internal/eventbus" tmpubsub "github.com/tendermint/tendermint/internal/pubsub" + "github.com/tendermint/tendermint/internal/test/factory" "github.com/tendermint/tendermint/libs/log" tmtimemocks "github.com/tendermint/tendermint/libs/time/mocks" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -105,8 +106,8 @@ func newPBTSTestHarness(ctx context.Context, t *testing.T, tc pbtsTestConfigurat // height 4 therefore occurs 2*blockTimeIota after height 2. tc.height4ProposedBlockOffset = tc.height2ProposalTimeDeliveryOffset + 2*blockTimeIota } - cfg.Consensus.TimeoutPropose = tc.timeoutPropose - consensusParams := types.DefaultConsensusParams() + consensusParams := factory.ConsensusParams() + consensusParams.Timeout.Propose = tc.timeoutPropose consensusParams.Synchrony = tc.synchronyParams state, privVals := makeGenesisState(ctx, t, cfg, genesisStateArgs{ @@ -114,7 +115,7 @@ func newPBTSTestHarness(ctx context.Context, t *testing.T, tc pbtsTestConfigurat Time: tc.genesisTime, Validators: validators, }) - cs := newState(ctx, t, log.TestingLogger(), state, privVals[0], kvstore.NewApplication()) + cs := newState(ctx, t, log.NewNopLogger(), state, privVals[0], kvstore.NewApplication()) vss := make([]*validatorStub, validators) for i := 0; i < validators; i++ { vss[i] = newValidatorStub(privVals[i], int32(i)) @@ -203,7 +204,7 @@ func (p *pbtsTestHarness) nextHeight(ctx context.Context, t *testing.T, proposer ensureNewRound(t, p.roundCh, p.currentHeight, p.currentRound) - b, _, err := p.observedState.createProposalBlock(ctx) + b, err := p.observedState.createProposalBlock(ctx) require.NoError(t, err) b.Height = p.currentHeight b.Header.Height = p.currentHeight diff --git a/internal/consensus/peer_state_test.go b/internal/consensus/peer_state_test.go index 06f49508a..97be569ff 100644 --- a/internal/consensus/peer_state_test.go +++ b/internal/consensus/peer_state_test.go @@ -4,13 +4,14 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/log" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/types" ) func peerStateSetup(h, r, v int) *PeerState { - ps := NewPeerState(log.TestingLogger(), "testPeerState") + ps := NewPeerState(log.NewNopLogger(), "testPeerState") ps.PRS.Height = int64(h) ps.PRS.Round = int32(r) ps.ensureVoteBitArrays(int64(h), v) diff --git a/internal/consensus/reactor.go b/internal/consensus/reactor.go index eb038d9f5..eea74b5e1 100644 --- a/internal/consensus/reactor.go +++ b/internal/consensus/reactor.go @@ -38,6 +38,7 @@ func getChannelDescriptors() map[p2p.ChannelID]*p2p.ChannelDescriptor { SendQueueCapacity: 64, RecvMessageCapacity: maxMsgSize, RecvBufferCapacity: 128, + Name: "state", }, DataChannel: { // TODO: Consider a split between gossiping current block and catchup @@ -49,6 +50,7 @@ func getChannelDescriptors() map[p2p.ChannelID]*p2p.ChannelDescriptor { SendQueueCapacity: 64, RecvBufferCapacity: 512, RecvMessageCapacity: maxMsgSize, + Name: "data", }, VoteChannel: { ID: VoteChannel, @@ -57,6 +59,7 @@ func getChannelDescriptors() map[p2p.ChannelID]*p2p.ChannelDescriptor { SendQueueCapacity: 64, RecvBufferCapacity: 128, RecvMessageCapacity: maxMsgSize, + Name: "vote", }, VoteSetBitsChannel: { ID: VoteSetBitsChannel, @@ -65,6 +68,7 @@ func getChannelDescriptors() map[p2p.ChannelID]*p2p.ChannelDescriptor { SendQueueCapacity: 8, RecvBufferCapacity: 128, RecvMessageCapacity: maxMsgSize, + Name: "voteSet", }, } } @@ -119,13 +123,11 @@ type Reactor struct { mtx sync.RWMutex peers map[types.NodeID]*PeerState waitSync bool + rs *cstypes.RoundState readySignal chan struct{} // closed when the node is ready to start consensus - stateCh *p2p.Channel - dataCh *p2p.Channel - voteCh *p2p.Channel - voteSetBitsCh *p2p.Channel - peerUpdates *p2p.PeerUpdates + peerEvents p2p.PeerEventSubscriber + chCreator p2p.ChannelCreator } // NewReactor returns a reference to a new consensus reactor, which implements @@ -133,46 +135,25 @@ type Reactor struct { // to relevant p2p Channels and a channel to listen for peer updates on. The // reactor will close all p2p Channels when stopping. func NewReactor( - ctx context.Context, logger log.Logger, cs *State, channelCreator p2p.ChannelCreator, - peerUpdates *p2p.PeerUpdates, + peerEvents p2p.PeerEventSubscriber, + eventBus *eventbus.EventBus, waitSync bool, metrics *Metrics, -) (*Reactor, error) { - chans := getChannelDescriptors() - stateCh, err := channelCreator(ctx, chans[StateChannel]) - if err != nil { - return nil, err - } - - dataCh, err := channelCreator(ctx, chans[DataChannel]) - if err != nil { - return nil, err - } - - voteCh, err := channelCreator(ctx, chans[VoteChannel]) - if err != nil { - return nil, err - } - - voteSetBitsCh, err := channelCreator(ctx, chans[VoteSetBitsChannel]) - if err != nil { - return nil, err - } +) *Reactor { r := &Reactor{ - logger: logger, - state: cs, - waitSync: waitSync, - peers: make(map[types.NodeID]*PeerState), - Metrics: metrics, - stateCh: stateCh, - dataCh: dataCh, - voteCh: voteCh, - voteSetBitsCh: voteSetBitsCh, - peerUpdates: peerUpdates, - readySignal: make(chan struct{}), + logger: logger, + state: cs, + waitSync: waitSync, + rs: cs.GetRoundState(), + peers: make(map[types.NodeID]*PeerState), + eventBus: eventBus, + Metrics: metrics, + peerEvents: peerEvents, + chCreator: channelCreator, + readySignal: make(chan struct{}), } r.BaseService = *service.NewBaseService(logger, "Consensus", r) @@ -180,7 +161,14 @@ func NewReactor( close(r.readySignal) } - return r, nil + return r +} + +type channelBundle struct { + state *p2p.Channel + data *p2p.Channel + vote *p2p.Channel + votSet *p2p.Channel } // OnStart starts separate go routines for each p2p Channel and listens for @@ -190,13 +178,39 @@ func NewReactor( func (r *Reactor) OnStart(ctx context.Context) error { r.logger.Debug("consensus wait sync", "wait_sync", r.WaitSync()) + peerUpdates := r.peerEvents(ctx) + + var chBundle channelBundle + var err error + + chans := getChannelDescriptors() + chBundle.state, err = r.chCreator(ctx, chans[StateChannel]) + if err != nil { + return err + } + + chBundle.data, err = r.chCreator(ctx, chans[DataChannel]) + if err != nil { + return err + } + + chBundle.vote, err = r.chCreator(ctx, chans[VoteChannel]) + if err != nil { + return err + } + + chBundle.votSet, err = r.chCreator(ctx, chans[VoteSetBitsChannel]) + if err != nil { + return err + } + // start routine that computes peer statistics for evaluating peer quality // // TODO: Evaluate if we need this to be synchronized via WaitGroup as to not // leak the goroutine when stopping the reactor. - go r.peerStatsRoutine(ctx) + go r.peerStatsRoutine(ctx, peerUpdates) - r.subscribeToBroadcastEvents() + r.subscribeToBroadcastEvents(ctx, chBundle.state) if !r.WaitSync() { if err := r.state.Start(ctx); err != nil { @@ -204,11 +218,13 @@ func (r *Reactor) OnStart(ctx context.Context) error { } } - go r.processStateCh(ctx) - go r.processDataCh(ctx) - go r.processVoteCh(ctx) - go r.processVoteSetBitsCh(ctx) - go r.processPeerUpdates(ctx) + go r.updateRoundStateRoutine(ctx) + + go r.processStateCh(ctx, chBundle) + go r.processDataCh(ctx, chBundle) + go r.processVoteCh(ctx, chBundle) + go r.processVoteSetBitsCh(ctx, chBundle) + go r.processPeerUpdates(ctx, peerUpdates, chBundle) return nil } @@ -217,8 +233,6 @@ func (r *Reactor) OnStart(ctx context.Context) error { // blocking until they all exit, as well as unsubscribing from events and stopping // state. func (r *Reactor) OnStop() { - r.unsubscribeFromBroadcastEvents() - r.state.Stop() if !r.WaitSync() { @@ -226,12 +240,6 @@ func (r *Reactor) OnStop() { } } -// SetEventBus sets the reactor's event bus. -func (r *Reactor) SetEventBus(b *eventbus.EventBus) { - r.eventBus = b - r.state.SetEventBus(b) -} - // WaitSync returns whether the consensus reactor is waiting for state/block sync. func (r *Reactor) WaitSync() bool { r.mtx.RLock() @@ -252,7 +260,16 @@ func (r *Reactor) SwitchToConsensus(ctx context.Context, state sm.State, skipWAL // NOTE: The line below causes broadcastNewRoundStepRoutine() to broadcast a // NewRoundStepMessage. - r.state.updateToState(ctx, state) + r.state.updateToState(state) + if err := r.state.Start(ctx); err != nil { + panic(fmt.Sprintf(`failed to start consensus state: %v + +conS: +%+v + +conR: +%+v`, err, r.state, r)) + } r.mtx.Lock() r.waitSync = false @@ -266,18 +283,8 @@ func (r *Reactor) SwitchToConsensus(ctx context.Context, state sm.State, skipWAL r.state.doWALCatchup = false } - if err := r.state.Start(ctx); err != nil { - panic(fmt.Sprintf(`failed to start consensus state: %v - -conS: -%+v - -conR: -%+v`, err, r.state, r)) - } - d := types.EventDataBlockSyncStatus{Complete: true, Height: state.LastBlockHeight} - if err := r.eventBus.PublishEventBlockSyncStatus(ctx, d); err != nil { + if err := r.eventBus.PublishEventBlockSyncStatus(d); err != nil { r.logger.Error("failed to emit the blocksync complete event", "err", err) } } @@ -292,22 +299,6 @@ func (r *Reactor) String() string { return "ConsensusReactor" } -// StringIndented returns an indented string representation of the Reactor. -func (r *Reactor) StringIndented(indent string) string { - r.mtx.RLock() - defer r.mtx.RUnlock() - - s := "ConsensusReactor{\n" - s += indent + " " + r.state.StringIndented(indent+" ") + "\n" - - for _, ps := range r.peers { - s += indent + " " + ps.StringIndented(indent+" ") + "\n" - } - - s += indent + "}" - return s -} - // GetPeerState returns PeerState for a given NodeID. func (r *Reactor) GetPeerState(peerID types.NodeID) (*PeerState, bool) { r.mtx.RLock() @@ -317,16 +308,16 @@ func (r *Reactor) GetPeerState(peerID types.NodeID) (*PeerState, bool) { return ps, ok } -func (r *Reactor) broadcastNewRoundStepMessage(ctx context.Context, rs *cstypes.RoundState) error { - return r.stateCh.Send(ctx, p2p.Envelope{ +func (r *Reactor) broadcastNewRoundStepMessage(ctx context.Context, rs *cstypes.RoundState, stateCh *p2p.Channel) error { + return stateCh.Send(ctx, p2p.Envelope{ Broadcast: true, Message: makeRoundStepMessage(rs), }) } -func (r *Reactor) broadcastNewValidBlockMessage(ctx context.Context, rs *cstypes.RoundState) error { +func (r *Reactor) broadcastNewValidBlockMessage(ctx context.Context, rs *cstypes.RoundState, stateCh *p2p.Channel) error { psHeader := rs.ProposalBlockParts.Header() - return r.stateCh.Send(ctx, p2p.Envelope{ + return stateCh.Send(ctx, p2p.Envelope{ Broadcast: true, Message: &tmcons.NewValidBlock{ Height: rs.Height, @@ -338,8 +329,8 @@ func (r *Reactor) broadcastNewValidBlockMessage(ctx context.Context, rs *cstypes }) } -func (r *Reactor) broadcastHasVoteMessage(ctx context.Context, vote *types.Vote) error { - return r.stateCh.Send(ctx, p2p.Envelope{ +func (r *Reactor) broadcastHasVoteMessage(ctx context.Context, vote *types.Vote, stateCh *p2p.Channel) error { + return stateCh.Send(ctx, p2p.Envelope{ Broadcast: true, Message: &tmcons.HasVote{ Height: vote.Height, @@ -353,16 +344,18 @@ func (r *Reactor) broadcastHasVoteMessage(ctx context.Context, vote *types.Vote) // subscribeToBroadcastEvents subscribes for new round steps and votes using the // internal pubsub defined in the consensus state to broadcast them to peers // upon receiving. -func (r *Reactor) subscribeToBroadcastEvents() { +func (r *Reactor) subscribeToBroadcastEvents(ctx context.Context, stateCh *p2p.Channel) { + onStopCh := r.state.getOnStopCh() + err := r.state.evsw.AddListenerForEvent( listenerIDConsensus, types.EventNewRoundStepValue, - func(ctx context.Context, data tmevents.EventData) error { - if err := r.broadcastNewRoundStepMessage(ctx, data.(*cstypes.RoundState)); err != nil { + func(data tmevents.EventData) error { + if err := r.broadcastNewRoundStepMessage(ctx, data.(*cstypes.RoundState), stateCh); err != nil { return err } select { - case r.state.onStopCh <- data.(*cstypes.RoundState): + case onStopCh <- data.(*cstypes.RoundState): return nil case <-ctx.Done(): return ctx.Err() @@ -378,8 +371,8 @@ func (r *Reactor) subscribeToBroadcastEvents() { err = r.state.evsw.AddListenerForEvent( listenerIDConsensus, types.EventValidBlockValue, - func(ctx context.Context, data tmevents.EventData) error { - return r.broadcastNewValidBlockMessage(ctx, data.(*cstypes.RoundState)) + func(data tmevents.EventData) error { + return r.broadcastNewValidBlockMessage(ctx, data.(*cstypes.RoundState), stateCh) }, ) if err != nil { @@ -389,8 +382,8 @@ func (r *Reactor) subscribeToBroadcastEvents() { err = r.state.evsw.AddListenerForEvent( listenerIDConsensus, types.EventVoteValue, - func(ctx context.Context, data tmevents.EventData) error { - return r.broadcastHasVoteMessage(ctx, data.(*types.Vote)) + func(data tmevents.EventData) error { + return r.broadcastHasVoteMessage(ctx, data.(*types.Vote), stateCh) }, ) if err != nil { @@ -398,10 +391,6 @@ func (r *Reactor) subscribeToBroadcastEvents() { } } -func (r *Reactor) unsubscribeFromBroadcastEvents() { - r.state.evsw.RemoveListener(listenerIDConsensus) -} - func makeRoundStepMessage(rs *cstypes.RoundState) *tmcons.NewRoundStep { return &tmcons.NewRoundStep{ Height: rs.Height, @@ -412,14 +401,37 @@ func makeRoundStepMessage(rs *cstypes.RoundState) *tmcons.NewRoundStep { } } -func (r *Reactor) sendNewRoundStepMessage(ctx context.Context, peerID types.NodeID) error { - return r.stateCh.Send(ctx, p2p.Envelope{ +func (r *Reactor) sendNewRoundStepMessage(ctx context.Context, peerID types.NodeID, stateCh *p2p.Channel) error { + return stateCh.Send(ctx, p2p.Envelope{ To: peerID, - Message: makeRoundStepMessage(r.state.GetRoundState()), + Message: makeRoundStepMessage(r.getRoundState()), }) } -func (r *Reactor) gossipDataForCatchup(ctx context.Context, rs *cstypes.RoundState, prs *cstypes.PeerRoundState, ps *PeerState) { +func (r *Reactor) updateRoundStateRoutine(ctx context.Context) { + t := time.NewTicker(100 * time.Microsecond) + defer t.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-t.C: + rs := r.state.GetRoundState() + r.mtx.Lock() + r.rs = rs + r.mtx.Unlock() + } + } +} + +func (r *Reactor) getRoundState() *cstypes.RoundState { + r.mtx.RLock() + defer r.mtx.RUnlock() + return r.rs +} + +func (r *Reactor) gossipDataForCatchup(ctx context.Context, rs *cstypes.RoundState, prs *cstypes.PeerRoundState, ps *PeerState, dataCh *p2p.Channel) { logger := r.logger.With("height", prs.Height).With("peer", ps.peerID) if index, ok := prs.ProposalBlockParts.Not().PickRandom(); ok { @@ -468,7 +480,7 @@ func (r *Reactor) gossipDataForCatchup(ctx context.Context, rs *cstypes.RoundSta } logger.Debug("sending block part for catchup", "round", prs.Round, "index", index) - _ = r.dataCh.Send(ctx, p2p.Envelope{ + _ = dataCh.Send(ctx, p2p.Envelope{ To: ps.peerID, Message: &tmcons.BlockPart{ Height: prs.Height, // not our height, so it does not matter. @@ -483,7 +495,7 @@ func (r *Reactor) gossipDataForCatchup(ctx context.Context, rs *cstypes.RoundSta time.Sleep(r.state.config.PeerGossipSleepDuration) } -func (r *Reactor) gossipDataRoutine(ctx context.Context, ps *PeerState) { +func (r *Reactor) gossipDataRoutine(ctx context.Context, ps *PeerState, dataCh *p2p.Channel) { logger := r.logger.With("peer", ps.peerID) timer := time.NewTimer(0) @@ -501,7 +513,7 @@ OUTER_LOOP: default: } - rs := r.state.GetRoundState() + rs := r.getRoundState() prs := ps.GetRoundState() // Send proposal Block parts? @@ -515,7 +527,7 @@ OUTER_LOOP: } logger.Debug("sending block part", "height", prs.Height, "round", prs.Round) - if err := r.dataCh.Send(ctx, p2p.Envelope{ + if err := dataCh.Send(ctx, p2p.Envelope{ To: ps.peerID, Message: &tmcons.BlockPart{ Height: rs.Height, // this tells peer that this part applies to us @@ -562,7 +574,7 @@ OUTER_LOOP: continue OUTER_LOOP } - r.gossipDataForCatchup(ctx, rs, prs, ps) + r.gossipDataForCatchup(ctx, rs, prs, ps, dataCh) continue OUTER_LOOP } @@ -589,7 +601,7 @@ OUTER_LOOP: propProto := rs.Proposal.ToProto() logger.Debug("sending proposal", "height", prs.Height, "round", prs.Round) - if err := r.dataCh.Send(ctx, p2p.Envelope{ + if err := dataCh.Send(ctx, p2p.Envelope{ To: ps.peerID, Message: &tmcons.Proposal{ Proposal: *propProto, @@ -612,7 +624,7 @@ OUTER_LOOP: pPolProto := pPol.ToProto() logger.Debug("sending POL", "height", prs.Height, "round", prs.Round) - if err := r.dataCh.Send(ctx, p2p.Envelope{ + if err := dataCh.Send(ctx, p2p.Envelope{ To: ps.peerID, Message: &tmcons.ProposalPOL{ Height: rs.Height, @@ -640,14 +652,14 @@ OUTER_LOOP: // pickSendVote picks a vote and sends it to the peer. It will return true if // there is a vote to send and false otherwise. -func (r *Reactor) pickSendVote(ctx context.Context, ps *PeerState, votes types.VoteSetReader) (bool, error) { +func (r *Reactor) pickSendVote(ctx context.Context, ps *PeerState, votes types.VoteSetReader, voteCh *p2p.Channel) (bool, error) { vote, ok := ps.PickVoteToSend(votes) if !ok { return false, nil } r.logger.Debug("sending vote message", "ps", ps, "vote", vote) - if err := r.voteCh.Send(ctx, p2p.Envelope{ + if err := voteCh.Send(ctx, p2p.Envelope{ To: ps.peerID, Message: &tmcons.Vote{ Vote: vote.ToProto(), @@ -668,12 +680,13 @@ func (r *Reactor) gossipVotesForHeight( rs *cstypes.RoundState, prs *cstypes.PeerRoundState, ps *PeerState, + voteCh *p2p.Channel, ) (bool, error) { logger := r.logger.With("height", prs.Height).With("peer", ps.peerID) // if there are lastCommits to send... if prs.Step == cstypes.RoundStepNewHeight { - if ok, err := r.pickSendVote(ctx, ps, rs.LastCommit); err != nil { + if ok, err := r.pickSendVote(ctx, ps, rs.LastCommit, voteCh); err != nil { return false, err } else if ok { logger.Debug("picked rs.LastCommit to send") @@ -685,7 +698,7 @@ func (r *Reactor) gossipVotesForHeight( // if there are POL prevotes to send... if prs.Step <= cstypes.RoundStepPropose && prs.Round != -1 && prs.Round <= rs.Round && prs.ProposalPOLRound != -1 { if polPrevotes := rs.Votes.Prevotes(prs.ProposalPOLRound); polPrevotes != nil { - if ok, err := r.pickSendVote(ctx, ps, polPrevotes); err != nil { + if ok, err := r.pickSendVote(ctx, ps, polPrevotes, voteCh); err != nil { return false, err } else if ok { logger.Debug("picked rs.Prevotes(prs.ProposalPOLRound) to send", "round", prs.ProposalPOLRound) @@ -696,7 +709,7 @@ func (r *Reactor) gossipVotesForHeight( // if there are prevotes to send... if prs.Step <= cstypes.RoundStepPrevoteWait && prs.Round != -1 && prs.Round <= rs.Round { - if ok, err := r.pickSendVote(ctx, ps, rs.Votes.Prevotes(prs.Round)); err != nil { + if ok, err := r.pickSendVote(ctx, ps, rs.Votes.Prevotes(prs.Round), voteCh); err != nil { return false, err } else if ok { logger.Debug("picked rs.Prevotes(prs.Round) to send", "round", prs.Round) @@ -706,7 +719,7 @@ func (r *Reactor) gossipVotesForHeight( // if there are precommits to send... if prs.Step <= cstypes.RoundStepPrecommitWait && prs.Round != -1 && prs.Round <= rs.Round { - if ok, err := r.pickSendVote(ctx, ps, rs.Votes.Precommits(prs.Round)); err != nil { + if ok, err := r.pickSendVote(ctx, ps, rs.Votes.Precommits(prs.Round), voteCh); err != nil { return false, err } else if ok { logger.Debug("picked rs.Precommits(prs.Round) to send", "round", prs.Round) @@ -716,7 +729,7 @@ func (r *Reactor) gossipVotesForHeight( // if there are prevotes to send...(which are needed because of validBlock mechanism) if prs.Round != -1 && prs.Round <= rs.Round { - if ok, err := r.pickSendVote(ctx, ps, rs.Votes.Prevotes(prs.Round)); err != nil { + if ok, err := r.pickSendVote(ctx, ps, rs.Votes.Prevotes(prs.Round), voteCh); err != nil { return false, err } else if ok { logger.Debug("picked rs.Prevotes(prs.Round) to send", "round", prs.Round) @@ -727,7 +740,7 @@ func (r *Reactor) gossipVotesForHeight( // if there are POLPrevotes to send... if prs.ProposalPOLRound != -1 { if polPrevotes := rs.Votes.Prevotes(prs.ProposalPOLRound); polPrevotes != nil { - if ok, err := r.pickSendVote(ctx, ps, polPrevotes); err != nil { + if ok, err := r.pickSendVote(ctx, ps, polPrevotes, voteCh); err != nil { return false, err } else if ok { logger.Debug("picked rs.Prevotes(prs.ProposalPOLRound) to send", "round", prs.ProposalPOLRound) @@ -739,12 +752,9 @@ func (r *Reactor) gossipVotesForHeight( return false, nil } -func (r *Reactor) gossipVotesRoutine(ctx context.Context, ps *PeerState) { +func (r *Reactor) gossipVotesRoutine(ctx context.Context, ps *PeerState, voteCh *p2p.Channel) { logger := r.logger.With("peer", ps.peerID) - // XXX: simple hack to throttle logs upon sleep - logThrottle := 0 - timer := time.NewTimer(0) defer timer.Stop() @@ -759,19 +769,12 @@ func (r *Reactor) gossipVotesRoutine(ctx context.Context, ps *PeerState) { default: } - rs := r.state.GetRoundState() + rs := r.getRoundState() prs := ps.GetRoundState() - switch logThrottle { - case 1: // first sleep - logThrottle = 2 - case 2: // no more sleep - logThrottle = 0 - } - // if height matches, then send LastCommit, Prevotes, and Precommits if rs.Height == prs.Height { - if ok, err := r.gossipVotesForHeight(ctx, rs, prs, ps); err != nil { + if ok, err := r.gossipVotesForHeight(ctx, rs, prs, ps, voteCh); err != nil { return } else if ok { continue @@ -780,7 +783,7 @@ func (r *Reactor) gossipVotesRoutine(ctx context.Context, ps *PeerState) { // special catchup logic -- if peer is lagging by height 1, send LastCommit if prs.Height != 0 && rs.Height == prs.Height+1 { - if ok, err := r.pickSendVote(ctx, ps, rs.LastCommit); err != nil { + if ok, err := r.pickSendVote(ctx, ps, rs.LastCommit, voteCh); err != nil { return } else if ok { logger.Debug("picked rs.LastCommit to send", "height", prs.Height) @@ -794,7 +797,7 @@ func (r *Reactor) gossipVotesRoutine(ctx context.Context, ps *PeerState) { // Load the block commit for prs.Height, which contains precommit // signatures for prs.Height. if commit := r.state.blockStore.LoadBlockCommit(prs.Height); commit != nil { - if ok, err := r.pickSendVote(ctx, ps, commit); err != nil { + if ok, err := r.pickSendVote(ctx, ps, commit, voteCh); err != nil { return } else if ok { logger.Debug("picked Catchup commit to send", "height", prs.Height) @@ -803,20 +806,6 @@ func (r *Reactor) gossipVotesRoutine(ctx context.Context, ps *PeerState) { } } - if logThrottle == 0 { - // we sent nothing -- sleep - logThrottle = 1 - logger.Debug( - "no votes to send; sleeping", - "rs.Height", rs.Height, - "prs.Height", prs.Height, - "localPV", rs.Votes.Prevotes(rs.Round).BitArray(), "peerPV", prs.Prevotes, - "localPC", rs.Votes.Precommits(rs.Round).BitArray(), "peerPC", prs.Precommits, - ) - } else if logThrottle == 2 { - logThrottle = 1 - } - timer.Reset(r.state.config.PeerGossipSleepDuration) select { case <-ctx.Done(): @@ -828,7 +817,7 @@ func (r *Reactor) gossipVotesRoutine(ctx context.Context, ps *PeerState) { // NOTE: `queryMaj23Routine` has a simple crude design since it only comes // into play for liveness when there's a signature DDoS attack happening. -func (r *Reactor) queryMaj23Routine(ctx context.Context, ps *PeerState) { +func (r *Reactor) queryMaj23Routine(ctx context.Context, ps *PeerState, stateCh *p2p.Channel) { timer := time.NewTimer(0) defer timer.Stop() @@ -850,10 +839,10 @@ func (r *Reactor) queryMaj23Routine(ctx context.Context, ps *PeerState) { return } - rs := r.state.GetRoundState() - prs := ps.GetRoundState() - // TODO create more reliable coppies of these + // TODO create more reliable copies of these // structures so the following go routines don't race + rs := r.getRoundState() + prs := ps.GetRoundState() wg := &sync.WaitGroup{} @@ -864,7 +853,7 @@ func (r *Reactor) queryMaj23Routine(ctx context.Context, ps *PeerState) { // maybe send Height/Round/Prevotes if maj23, ok := rs.Votes.Prevotes(prs.Round).TwoThirdsMajority(); ok { - if err := r.stateCh.Send(ctx, p2p.Envelope{ + if err := stateCh.Send(ctx, p2p.Envelope{ To: ps.peerID, Message: &tmcons.VoteSetMaj23{ Height: prs.Height, @@ -885,7 +874,7 @@ func (r *Reactor) queryMaj23Routine(ctx context.Context, ps *PeerState) { // maybe send Height/Round/ProposalPOL if maj23, ok := rs.Votes.Prevotes(prs.ProposalPOLRound).TwoThirdsMajority(); ok { - if err := r.stateCh.Send(ctx, p2p.Envelope{ + if err := stateCh.Send(ctx, p2p.Envelope{ To: ps.peerID, Message: &tmcons.VoteSetMaj23{ Height: prs.Height, @@ -906,7 +895,7 @@ func (r *Reactor) queryMaj23Routine(ctx context.Context, ps *PeerState) { // maybe send Height/Round/Precommits if maj23, ok := rs.Votes.Precommits(prs.Round).TwoThirdsMajority(); ok { - if err := r.stateCh.Send(ctx, p2p.Envelope{ + if err := stateCh.Send(ctx, p2p.Envelope{ To: ps.peerID, Message: &tmcons.VoteSetMaj23{ Height: prs.Height, @@ -931,7 +920,7 @@ func (r *Reactor) queryMaj23Routine(ctx context.Context, ps *PeerState) { if prs.Height <= r.state.blockStore.Height() && prs.Height >= r.state.blockStore.Base() { // maybe send Height/CatchupCommitRound/CatchupCommit if commit := r.state.LoadCommit(prs.Height); commit != nil { - if err := r.stateCh.Send(ctx, p2p.Envelope{ + if err := stateCh.Send(ctx, p2p.Envelope{ To: ps.peerID, Message: &tmcons.VoteSetMaj23{ Height: prs.Height, @@ -964,7 +953,7 @@ func (r *Reactor) queryMaj23Routine(ctx context.Context, ps *PeerState) { // be the case, and we spawn all the relevant goroutine to broadcast messages to // the peer. During peer removal, we remove the peer for our set of peers and // signal to all spawned goroutines to gracefully exit in a non-blocking manner. -func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpdate) { +func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpdate, chans channelBundle) { r.logger.Debug("received peer update", "peer", peerUpdate.NodeID, "status", peerUpdate.Status) r.mtx.Lock() @@ -1005,14 +994,14 @@ func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpda return } // start goroutines for this peer - go r.gossipDataRoutine(ctx, ps) - go r.gossipVotesRoutine(ctx, ps) - go r.queryMaj23Routine(ctx, ps) + go r.gossipDataRoutine(ctx, ps, chans.data) + go r.gossipVotesRoutine(ctx, ps, chans.vote) + go r.queryMaj23Routine(ctx, ps, chans.state) // Send our state to the peer. If we're block-syncing, broadcast a // RoundStepMessage later upon SwitchToConsensus(). if !r.WaitSync() { - go func() { _ = r.sendNewRoundStepMessage(ctx, ps.peerID) }() + go func() { _ = r.sendNewRoundStepMessage(ctx, ps.peerID, chans.state) }() } }() @@ -1039,7 +1028,7 @@ func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpda // If we fail to find the peer state for the envelope sender, we perform a no-op // and return. This can happen when we process the envelope after the peer is // removed. -func (r *Reactor) handleStateMessage(ctx context.Context, envelope *p2p.Envelope, msgI Message) error { +func (r *Reactor) handleStateMessage(ctx context.Context, envelope *p2p.Envelope, msgI Message, voteSetCh *p2p.Channel) error { ps, ok := r.GetPeerState(envelope.From) if !ok || ps == nil { r.logger.Debug("failed to find peer state", "peer", envelope.From, "ch_id", "StateChannel") @@ -1109,7 +1098,7 @@ func (r *Reactor) handleStateMessage(ctx context.Context, envelope *p2p.Envelope eMsg.Votes = *votesProto } - if err := r.voteSetBitsCh.Send(ctx, p2p.Envelope{ + if err := voteSetCh.Send(ctx, p2p.Envelope{ To: envelope.From, Message: eMsg, }); err != nil { @@ -1277,7 +1266,7 @@ func (r *Reactor) handleVoteSetBitsMessage(ctx context.Context, envelope *p2p.En // the p2p channel. // // NOTE: We block on consensus state for proposals, block parts, and votes. -func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelope *p2p.Envelope) (err error) { +func (r *Reactor) handleMessage(ctx context.Context, envelope *p2p.Envelope, chans channelBundle) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("panic in processing message: %v", e) @@ -1295,32 +1284,29 @@ func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelop // and because a large part of the core business logic depends on these // domain types opposed to simply working with the Proto types. protoMsg := new(tmcons.Message) - if err := protoMsg.Wrap(envelope.Message); err != nil { + if err = protoMsg.Wrap(envelope.Message); err != nil { return err } - msgI, err := MsgFromProto(protoMsg) + var msgI Message + msgI, err = MsgFromProto(protoMsg) if err != nil { return err } - r.logger.Debug("received message", "ch_id", chID, "message", msgI, "peer", envelope.From) + r.logger.Debug("received message", "ch_id", envelope.ChannelID, "message", msgI, "peer", envelope.From) - switch chID { + switch envelope.ChannelID { case StateChannel: - err = r.handleStateMessage(ctx, envelope, msgI) - + err = r.handleStateMessage(ctx, envelope, msgI, chans.votSet) case DataChannel: err = r.handleDataMessage(ctx, envelope, msgI) - case VoteChannel: err = r.handleVoteMessage(ctx, envelope, msgI) - case VoteSetBitsChannel: err = r.handleVoteSetBitsMessage(ctx, envelope, msgI) - default: - err = fmt.Errorf("unknown channel ID (%d) for envelope (%v)", chID, envelope) + err = fmt.Errorf("unknown channel ID (%d) for envelope (%v)", envelope.ChannelID, envelope) } return err @@ -1331,13 +1317,13 @@ func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelop // execution will result in a PeerError being sent on the StateChannel. When // the reactor is stopped, we will catch the signal and close the p2p Channel // gracefully. -func (r *Reactor) processStateCh(ctx context.Context) { - iter := r.stateCh.Receive(ctx) +func (r *Reactor) processStateCh(ctx context.Context, chans channelBundle) { + iter := chans.state.Receive(ctx) for iter.Next(ctx) { envelope := iter.Envelope() - if err := r.handleMessage(ctx, r.stateCh.ID, envelope); err != nil { - r.logger.Error("failed to process message", "ch_id", r.stateCh.ID, "envelope", envelope, "err", err) - if serr := r.stateCh.SendError(ctx, p2p.PeerError{ + if err := r.handleMessage(ctx, envelope, chans); err != nil { + r.logger.Error("failed to process message", "ch_id", envelope.ChannelID, "envelope", envelope, "err", err) + if serr := chans.state.SendError(ctx, p2p.PeerError{ NodeID: envelope.From, Err: err, }); serr != nil { @@ -1352,13 +1338,13 @@ func (r *Reactor) processStateCh(ctx context.Context) { // execution will result in a PeerError being sent on the DataChannel. When // the reactor is stopped, we will catch the signal and close the p2p Channel // gracefully. -func (r *Reactor) processDataCh(ctx context.Context) { - iter := r.dataCh.Receive(ctx) +func (r *Reactor) processDataCh(ctx context.Context, chans channelBundle) { + iter := chans.data.Receive(ctx) for iter.Next(ctx) { envelope := iter.Envelope() - if err := r.handleMessage(ctx, r.dataCh.ID, envelope); err != nil { - r.logger.Error("failed to process message", "ch_id", r.dataCh.ID, "envelope", envelope, "err", err) - if serr := r.dataCh.SendError(ctx, p2p.PeerError{ + if err := r.handleMessage(ctx, envelope, chans); err != nil { + r.logger.Error("failed to process message", "ch_id", envelope.ChannelID, "envelope", envelope, "err", err) + if serr := chans.data.SendError(ctx, p2p.PeerError{ NodeID: envelope.From, Err: err, }); serr != nil { @@ -1373,13 +1359,13 @@ func (r *Reactor) processDataCh(ctx context.Context) { // execution will result in a PeerError being sent on the VoteChannel. When // the reactor is stopped, we will catch the signal and close the p2p Channel // gracefully. -func (r *Reactor) processVoteCh(ctx context.Context) { - iter := r.voteCh.Receive(ctx) +func (r *Reactor) processVoteCh(ctx context.Context, chans channelBundle) { + iter := chans.vote.Receive(ctx) for iter.Next(ctx) { envelope := iter.Envelope() - if err := r.handleMessage(ctx, r.voteCh.ID, envelope); err != nil { - r.logger.Error("failed to process message", "ch_id", r.voteCh.ID, "envelope", envelope, "err", err) - if serr := r.voteCh.SendError(ctx, p2p.PeerError{ + if err := r.handleMessage(ctx, envelope, chans); err != nil { + r.logger.Error("failed to process message", "ch_id", envelope.ChannelID, "envelope", envelope, "err", err) + if serr := chans.vote.SendError(ctx, p2p.PeerError{ NodeID: envelope.From, Err: err, }); serr != nil { @@ -1394,18 +1380,18 @@ func (r *Reactor) processVoteCh(ctx context.Context) { // execution will result in a PeerError being sent on the VoteSetBitsChannel. // When the reactor is stopped, we will catch the signal and close the p2p // Channel gracefully. -func (r *Reactor) processVoteSetBitsCh(ctx context.Context) { - iter := r.voteSetBitsCh.Receive(ctx) +func (r *Reactor) processVoteSetBitsCh(ctx context.Context, chans channelBundle) { + iter := chans.votSet.Receive(ctx) for iter.Next(ctx) { envelope := iter.Envelope() - if err := r.handleMessage(ctx, r.voteSetBitsCh.ID, envelope); err != nil { + if err := r.handleMessage(ctx, envelope, chans); err != nil { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { return } - r.logger.Error("failed to process message", "ch_id", r.voteSetBitsCh.ID, "envelope", envelope, "err", err) - if serr := r.voteSetBitsCh.SendError(ctx, p2p.PeerError{ + r.logger.Error("failed to process message", "ch_id", envelope.ChannelID, "envelope", envelope, "err", err) + if serr := chans.votSet.SendError(ctx, p2p.PeerError{ NodeID: envelope.From, Err: err, }); serr != nil { @@ -1418,18 +1404,18 @@ func (r *Reactor) processVoteSetBitsCh(ctx context.Context) { // processPeerUpdates initiates a blocking process where we listen for and handle // PeerUpdate messages. When the reactor is stopped, we will catch the signal and // close the p2p PeerUpdatesCh gracefully. -func (r *Reactor) processPeerUpdates(ctx context.Context) { +func (r *Reactor) processPeerUpdates(ctx context.Context, peerUpdates *p2p.PeerUpdates, chans channelBundle) { for { select { case <-ctx.Done(): return - case peerUpdate := <-r.peerUpdates.Updates(): - r.processPeerUpdate(ctx, peerUpdate) + case peerUpdate := <-peerUpdates.Updates(): + r.processPeerUpdate(ctx, peerUpdate, chans) } } } -func (r *Reactor) peerStatsRoutine(ctx context.Context) { +func (r *Reactor) peerStatsRoutine(ctx context.Context, peerUpdates *p2p.PeerUpdates) { for { if !r.IsRunning() { r.logger.Info("stopping peerStatsRoutine") @@ -1447,7 +1433,7 @@ func (r *Reactor) peerStatsRoutine(ctx context.Context) { switch msg.Msg.(type) { case *VoteMessage: if numVotes := ps.RecordVote(); numVotes%votesToContributeToBecomeGoodPeer == 0 { - r.peerUpdates.SendUpdate(ctx, p2p.PeerUpdate{ + peerUpdates.SendUpdate(ctx, p2p.PeerUpdate{ NodeID: msg.PeerID, Status: p2p.PeerStatusGood, }) @@ -1455,7 +1441,7 @@ func (r *Reactor) peerStatsRoutine(ctx context.Context) { case *BlockPartMessage: if numParts := ps.RecordBlockPart(); numParts%blocksToContributeToBecomeGoodPeer == 0 { - r.peerUpdates.SendUpdate(ctx, p2p.PeerUpdate{ + peerUpdates.SendUpdate(ctx, p2p.PeerUpdate{ NodeID: msg.PeerID, Status: p2p.PeerStatusGood, }) diff --git a/internal/consensus/reactor_test.go b/internal/consensus/reactor_test.go index ea9238a22..c6a8869db 100644 --- a/internal/consensus/reactor_test.go +++ b/internal/consensus/reactor_test.go @@ -4,8 +4,8 @@ import ( "context" "errors" "fmt" + "math/rand" "os" - "path" "sync" "testing" "time" @@ -105,17 +105,15 @@ func setup( for nodeID, node := range rts.network.Nodes { state := states[i] - reactor, err := NewReactor(ctx, + reactor := NewReactor( state.logger.With("node", nodeID), state, chCreator(nodeID), - node.MakePeerUpdates(ctx, t), + func(ctx context.Context) *p2p.PeerUpdates { return node.MakePeerUpdates(ctx, t) }, + state.eventBus, true, NopMetrics(), ) - require.NoError(t, err) - - reactor.SetEventBus(state.eventBus) blocksSub, err := state.eventBus.SubscribeWithArgs(ctx, tmpubsub.SubscribeArgs{ ClientID: testSubscriber, @@ -357,7 +355,7 @@ func TestReactorBasic(t *testing.T) { cfg := configSetup(t) - n := 4 + n := 2 states, cleanup := makeConsensusState(ctx, t, cfg, n, "consensus_reactor_test", newMockTickerFunc(true)) @@ -447,12 +445,12 @@ func TestReactorWithEvidence(t *testing.T) { cfg := configSetup(t) - n := 4 + n := 2 testName := "consensus_reactor_test" tickerFunc := newMockTickerFunc(true) valSet, privVals := factory.ValidatorSet(ctx, t, n, 30) - genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, nil) + genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, factory.ConsensusParams()) states := make([]*State, n) logger := consensusLogger() @@ -467,10 +465,10 @@ func TestReactorWithEvidence(t *testing.T) { defer os.RemoveAll(thisConfig.RootDir) - ensureDir(t, path.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal app := kvstore.NewApplication() vals := types.TM2PB.ValidatorUpdates(state.Validators) - app.InitChain(abci.RequestInitChain{Validators: vals}) + _, err = app.InitChain(ctx, &abci.RequestInitChain{Validators: vals}) + require.NoError(t, err) pv := privVals[i] blockDB := dbm.NewMemDB() @@ -481,7 +479,7 @@ func TestReactorWithEvidence(t *testing.T) { proxyAppConnCon := abciclient.NewLocalClient(logger, app) mempool := mempool.NewTxMempool( - log.TestingLogger().With("module", "mempool"), + log.NewNopLogger().With("module", "mempool"), thisConfig.Mempool, proxyAppConnMem, ) @@ -504,17 +502,16 @@ func TestReactorWithEvidence(t *testing.T) { evpool2 := sm.EmptyEvidencePool{} - blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyAppConnCon, mempool, evpool, blockStore) + eventBus := eventbus.NewDefault(log.NewNopLogger().With("module", "events")) + require.NoError(t, eventBus.Start(ctx)) - cs, err := NewState(ctx, logger.With("validator", i, "module", "consensus"), - thisConfig.Consensus, stateStore, blockExec, blockStore, mempool, evpool2) + blockExec := sm.NewBlockExecutor(stateStore, log.NewNopLogger(), proxyAppConnCon, mempool, evpool, blockStore, eventBus, sm.NopMetrics()) + + cs, err := NewState(logger.With("validator", i, "module", "consensus"), + thisConfig.Consensus, stateStore, blockExec, blockStore, mempool, evpool2, eventBus) require.NoError(t, err) cs.SetPrivValidator(ctx, pv) - eventBus := eventbus.NewDefault(log.TestingLogger().With("module", "events")) - require.NoError(t, eventBus.Start(ctx)) - cs.SetEventBus(eventBus) - cs.SetTimeoutTicker(tickerFunc()) states[i] = cs @@ -555,7 +552,7 @@ func TestReactorCreatesBlockWhenEmptyBlocksFalse(t *testing.T) { cfg := configSetup(t) - n := 4 + n := 2 states, cleanup := makeConsensusState(ctx, t, cfg, @@ -566,7 +563,6 @@ func TestReactorCreatesBlockWhenEmptyBlocksFalse(t *testing.T) { c.Consensus.CreateEmptyBlocks = false }, ) - t.Cleanup(cleanup) rts := setup(ctx, t, n, states, 100) // buffer must be large enough to not deadlock @@ -579,7 +575,7 @@ func TestReactorCreatesBlockWhenEmptyBlocksFalse(t *testing.T) { // send a tx require.NoError( t, - assertMempool(t, states[3].txNotifier).CheckTx( + assertMempool(t, states[1].txNotifier).CheckTx( ctx, []byte{1, 2, 3}, nil, @@ -610,7 +606,7 @@ func TestReactorRecordsVotesAndBlockParts(t *testing.T) { cfg := configSetup(t) - n := 4 + n := 2 states, cleanup := makeConsensusState(ctx, t, cfg, n, "consensus_reactor_test", newMockTickerFunc(true)) @@ -675,7 +671,7 @@ func TestReactorVotingPowerChange(t *testing.T) { cfg := configSetup(t) - n := 4 + n := 2 states, cleanup := makeConsensusState(ctx, t, cfg, @@ -781,8 +777,8 @@ func TestReactorValidatorSetChanges(t *testing.T) { cfg := configSetup(t) - nPeers := 7 - nVals := 4 + nPeers := 4 + nVals := 2 states, _, _, cleanup := randConsensusNetWithPeers( ctx, t, @@ -875,60 +871,27 @@ func TestReactorValidatorSetChanges(t *testing.T) { // it includes the commit for block 4, which should have the updated validator set waitForBlockWithUpdatedValsAndValidateIt(ctx, t, nPeers, activeVals, blocksSubs, states) - updateValidatorPubKey1, err := states[nVals].privValidator.GetPubKey(ctx) - require.NoError(t, err) + for i := 2; i <= 32; i *= 2 { + useState := rand.Intn(nVals) + t.Log(useState) + updateValidatorPubKey1, err := states[useState].privValidator.GetPubKey(ctx) + require.NoError(t, err) - updatePubKey1ABCI, err := encoding.PubKeyToProto(updateValidatorPubKey1) - require.NoError(t, err) + updatePubKey1ABCI, err := encoding.PubKeyToProto(updateValidatorPubKey1) + require.NoError(t, err) - updateValidatorTx1 := kvstore.MakeValSetChangeTx(updatePubKey1ABCI, 25) - previousTotalVotingPower := states[nVals].GetRoundState().LastValidators.TotalVotingPower() + previousTotalVotingPower := states[useState].GetRoundState().LastValidators.TotalVotingPower() + updateValidatorTx1 := kvstore.MakeValSetChangeTx(updatePubKey1ABCI, int64(i)) - waitForAndValidateBlock(ctx, t, nPeers, activeVals, blocksSubs, states, updateValidatorTx1) - waitForAndValidateBlockWithTx(ctx, t, nPeers, activeVals, blocksSubs, states, updateValidatorTx1) - waitForAndValidateBlock(ctx, t, nPeers, activeVals, blocksSubs, states) - waitForBlockWithUpdatedValsAndValidateIt(ctx, t, nPeers, activeVals, blocksSubs, states) + waitForAndValidateBlock(ctx, t, nPeers, activeVals, blocksSubs, states, updateValidatorTx1) + waitForAndValidateBlockWithTx(ctx, t, nPeers, activeVals, blocksSubs, states, updateValidatorTx1) + waitForAndValidateBlock(ctx, t, nPeers, activeVals, blocksSubs, states) + waitForBlockWithUpdatedValsAndValidateIt(ctx, t, nPeers, activeVals, blocksSubs, states) - require.NotEqualf( - t, states[nVals].GetRoundState().LastValidators.TotalVotingPower(), previousTotalVotingPower, - "expected voting power to change (before: %d, after: %d)", - previousTotalVotingPower, states[nVals].GetRoundState().LastValidators.TotalVotingPower(), - ) - - newValidatorPubKey2, err := states[nVals+1].privValidator.GetPubKey(ctx) - require.NoError(t, err) - - newVal2ABCI, err := encoding.PubKeyToProto(newValidatorPubKey2) - require.NoError(t, err) - - newValidatorTx2 := kvstore.MakeValSetChangeTx(newVal2ABCI, testMinPower) - - newValidatorPubKey3, err := states[nVals+2].privValidator.GetPubKey(ctx) - require.NoError(t, err) - - newVal3ABCI, err := encoding.PubKeyToProto(newValidatorPubKey3) - require.NoError(t, err) - - newValidatorTx3 := kvstore.MakeValSetChangeTx(newVal3ABCI, testMinPower) - - waitForAndValidateBlock(ctx, t, nPeers, activeVals, blocksSubs, states, newValidatorTx2, newValidatorTx3) - waitForAndValidateBlockWithTx(ctx, t, nPeers, activeVals, blocksSubs, states, newValidatorTx2, newValidatorTx3) - waitForAndValidateBlock(ctx, t, nPeers, activeVals, blocksSubs, states) - - activeVals[string(newValidatorPubKey2.Address())] = struct{}{} - activeVals[string(newValidatorPubKey3.Address())] = struct{}{} - - waitForBlockWithUpdatedValsAndValidateIt(ctx, t, nPeers, activeVals, blocksSubs, states) - - removeValidatorTx2 := kvstore.MakeValSetChangeTx(newVal2ABCI, 0) - removeValidatorTx3 := kvstore.MakeValSetChangeTx(newVal3ABCI, 0) - - waitForAndValidateBlock(ctx, t, nPeers, activeVals, blocksSubs, states, removeValidatorTx2, removeValidatorTx3) - waitForAndValidateBlockWithTx(ctx, t, nPeers, activeVals, blocksSubs, states, removeValidatorTx2, removeValidatorTx3) - waitForAndValidateBlock(ctx, t, nPeers, activeVals, blocksSubs, states) - - delete(activeVals, string(newValidatorPubKey2.Address())) - delete(activeVals, string(newValidatorPubKey3.Address())) - - waitForBlockWithUpdatedValsAndValidateIt(ctx, t, nPeers, activeVals, blocksSubs, states) + require.NotEqualf( + t, states[useState].GetRoundState().LastValidators.TotalVotingPower(), previousTotalVotingPower, + "expected voting power to change (before: %d, after: %d)", + previousTotalVotingPower, states[useState].GetRoundState().LastValidators.TotalVotingPower(), + ) + } } diff --git a/internal/consensus/replay.go b/internal/consensus/replay.go index 4034b2ddf..fac7c91ac 100644 --- a/internal/consensus/replay.go +++ b/internal/consensus/replay.go @@ -205,7 +205,7 @@ type Handshaker struct { stateStore sm.Store initialState sm.State store sm.BlockStore - eventBus types.BlockEventPublisher + eventBus *eventbus.EventBus genDoc *types.GenesisDoc logger log.Logger @@ -217,10 +217,9 @@ func NewHandshaker( stateStore sm.Store, state sm.State, store sm.BlockStore, - eventBus types.BlockEventPublisher, + eventBus *eventbus.EventBus, genDoc *types.GenesisDoc, ) *Handshaker { - return &Handshaker{ stateStore: stateStore, initialState: state, @@ -228,7 +227,6 @@ func NewHandshaker( eventBus: eventBus, genDoc: genDoc, logger: logger, - nBlocks: 0, } } @@ -241,7 +239,7 @@ func (h *Handshaker) NBlocks() int { func (h *Handshaker) Handshake(ctx context.Context, appClient abciclient.Client) error { // Handshake is done via ABCI Info on the query conn. - res, err := appClient.Info(ctx, proxy.RequestInfo) + res, err := appClient.Info(ctx, &proxy.RequestInfo) if err != nil { return fmt.Errorf("error calling Info: %w", err) } @@ -309,15 +307,14 @@ func (h *Handshaker) ReplayBlocks( validatorSet := types.NewValidatorSet(validators) nextVals := types.TM2PB.ValidatorUpdates(validatorSet) pbParams := h.genDoc.ConsensusParams.ToProto() - req := abci.RequestInitChain{ + res, err := appClient.InitChain(ctx, &abci.RequestInitChain{ Time: h.genDoc.GenesisTime, ChainId: h.genDoc.ChainID, InitialHeight: h.genDoc.InitialHeight, ConsensusParams: &pbParams, Validators: nextVals, AppStateBytes: h.genDoc.AppState, - } - res, err := appClient.InitChain(ctx, req) + }) if err != nil { return nil, err } @@ -359,7 +356,9 @@ func (h *Handshaker) ReplayBlocks( // First handle edge cases and constraints on the storeBlockHeight and storeBlockBase. switch { case storeBlockHeight == 0: - assertAppHashEqualsOneFromState(appHash, state) + if err := checkAppHashEqualsOneFromState(appHash, state); err != nil { + return nil, err + } return appHash, nil case appBlockHeight == 0 && state.InitialHeight < storeBlockBase: @@ -376,11 +375,11 @@ func (h *Handshaker) ReplayBlocks( case storeBlockHeight < stateBlockHeight: // the state should never be ahead of the store (this is under tendermint's control) - panic(fmt.Sprintf("StateBlockHeight (%d) > StoreBlockHeight (%d)", stateBlockHeight, storeBlockHeight)) + return nil, fmt.Errorf("StateBlockHeight (%d) > StoreBlockHeight (%d)", stateBlockHeight, storeBlockHeight) case storeBlockHeight > stateBlockHeight+1: // store should be at most one ahead of the state (this is under tendermint's control) - panic(fmt.Sprintf("StoreBlockHeight (%d) > StateBlockHeight + 1 (%d)", storeBlockHeight, stateBlockHeight+1)) + return nil, fmt.Errorf("StoreBlockHeight (%d) > StateBlockHeight + 1 (%d)", storeBlockHeight, stateBlockHeight+1) } var err error @@ -395,7 +394,9 @@ func (h *Handshaker) ReplayBlocks( } else if appBlockHeight == storeBlockHeight { // We're good! - assertAppHashEqualsOneFromState(appHash, state) + if err := checkAppHashEqualsOneFromState(appHash, state); err != nil { + return nil, err + } return appHash, nil } @@ -415,7 +416,11 @@ func (h *Handshaker) ReplayBlocks( // but we'd have to allow the WAL to replay a block that wrote it's #ENDHEIGHT h.logger.Info("Replay last block using real app") state, err = h.replayBlock(ctx, state, storeBlockHeight, appClient) - return state.AppHash, err + if err != nil { + return nil, err + + } + return state.AppHash, nil case appBlockHeight == storeBlockHeight: // We ran Commit, but didn't save the state, so replayBlock with mock app. @@ -423,7 +428,7 @@ func (h *Handshaker) ReplayBlocks( if err != nil { return nil, err } - mockApp, err := newMockProxyApp(ctx, h.logger, appHash, abciResponses) + mockApp, err := newMockProxyApp(h.logger, appHash, abciResponses) if err != nil { return nil, err } @@ -437,13 +442,13 @@ func (h *Handshaker) ReplayBlocks( return nil, err } - return state.AppHash, err + return state.AppHash, nil } } - panic(fmt.Sprintf("uncovered case! appHeight: %d, storeHeight: %d, stateHeight: %d", - appBlockHeight, storeBlockHeight, stateBlockHeight)) + return nil, fmt.Errorf("uncovered case! appHeight: %d, storeHeight: %d, stateHeight: %d", + appBlockHeight, storeBlockHeight, stateBlockHeight) } func (h *Handshaker) replayBlocks( @@ -452,7 +457,8 @@ func (h *Handshaker) replayBlocks( appClient abciclient.Client, appBlockHeight, storeBlockHeight int64, - mutateState bool) ([]byte, error) { + mutateState bool, +) ([]byte, error) { // App is further behind than it should be, so we need to replay blocks. // We replay all blocks from appBlockHeight+1. // @@ -478,15 +484,15 @@ func (h *Handshaker) replayBlocks( block := h.store.LoadBlock(i) // Extra check to ensure the app was not changed in a way it shouldn't have. if len(appHash) > 0 { - assertAppHashEqualsOneFromBlock(appHash, block) + if err := checkAppHashEqualsOneFromBlock(appHash, block); err != nil { + return nil, err + } } if i == finalBlock && !mutateState { // We emit events for the index services at the final block due to the sync issue when // the node shutdown during the block committing status. - blockExec := sm.NewBlockExecutor( - h.stateStore, h.logger, appClient, emptyMempool{}, sm.EmptyEvidencePool{}, h.store) - blockExec.SetEventBus(h.eventBus) + blockExec := sm.NewBlockExecutor(h.stateStore, h.logger, appClient, emptyMempool{}, sm.EmptyEvidencePool{}, h.store, h.eventBus, sm.NopMetrics()) appHash, err = sm.ExecCommitBlock(ctx, blockExec, appClient, block, h.logger, h.stateStore, h.genDoc.InitialHeight, state) if err != nil { @@ -512,7 +518,9 @@ func (h *Handshaker) replayBlocks( appHash = state.AppHash } - assertAppHashEqualsOneFromState(appHash, state) + if err := checkAppHashEqualsOneFromState(appHash, state); err != nil { + return nil, err + } return appHash, nil } @@ -528,8 +536,7 @@ func (h *Handshaker) replayBlock( // Use stubs for both mempool and evidence pool since no transactions nor // evidence are needed here - block already exists. - blockExec := sm.NewBlockExecutor(h.stateStore, h.logger, appClient, emptyMempool{}, sm.EmptyEvidencePool{}, h.store) - blockExec.SetEventBus(h.eventBus) + blockExec := sm.NewBlockExecutor(h.stateStore, h.logger, appClient, emptyMempool{}, sm.EmptyEvidencePool{}, h.store, h.eventBus, sm.NopMetrics()) var err error state, err = blockExec.ApplyBlock(ctx, state, meta.BlockID, block) @@ -542,24 +549,25 @@ func (h *Handshaker) replayBlock( return state, nil } -func assertAppHashEqualsOneFromBlock(appHash []byte, block *types.Block) { +func checkAppHashEqualsOneFromBlock(appHash []byte, block *types.Block) error { if !bytes.Equal(appHash, block.AppHash) { - panic(fmt.Sprintf(`block.AppHash does not match AppHash after replay. Got %X, expected %X. + return fmt.Errorf(`block.AppHash does not match AppHash after replay. Got '%X', expected '%X'. -Block: %v -`, - appHash, block.AppHash, block)) +Block: %v`, + appHash, block.AppHash, block) } + return nil } -func assertAppHashEqualsOneFromState(appHash []byte, state sm.State) { +func checkAppHashEqualsOneFromState(appHash []byte, state sm.State) error { if !bytes.Equal(appHash, state.AppHash) { - panic(fmt.Sprintf(`state.AppHash does not match AppHash after replay. Got -%X, expected %X. + return fmt.Errorf(`state.AppHash does not match AppHash after replay. Got '%X', expected '%X'. State: %v Did you reset Tendermint without resetting your application's data?`, - appHash, state.AppHash, state)) + appHash, state.AppHash, state) } + + return nil } diff --git a/internal/consensus/replay_file.go b/internal/consensus/replay_file.go index 3ab470d36..cd6d4b831 100644 --- a/internal/consensus/replay_file.go +++ b/internal/consensus/replay_file.go @@ -145,12 +145,11 @@ func (pb *playback) replayReset(ctx context.Context, count int, newStepSub event pb.cs.Stop() pb.cs.Wait() - newCS, err := NewState(ctx, pb.cs.logger, pb.cs.config, pb.stateStore, pb.cs.blockExec, - pb.cs.blockStore, pb.cs.txNotifier, pb.cs.evpool) + newCS, err := NewState(pb.cs.logger, pb.cs.config, pb.stateStore, pb.cs.blockExec, + pb.cs.blockStore, pb.cs.txNotifier, pb.cs.evpool, pb.cs.eventBus) if err != nil { return err } - newCS.SetEventBus(pb.cs.eventBus) newCS.startForReplay() if err := pb.fp.Close(); err != nil { @@ -349,13 +348,12 @@ func newConsensusStateForReplay( } mempool, evpool := emptyMempool{}, sm.EmptyEvidencePool{} - blockExec := sm.NewBlockExecutor(stateStore, logger, proxyApp, mempool, evpool, blockStore) + blockExec := sm.NewBlockExecutor(stateStore, logger, proxyApp, mempool, evpool, blockStore, eventBus, sm.NopMetrics()) - consensusState, err := NewState(ctx, logger, csConfig, stateStore, blockExec, - blockStore, mempool, evpool) + consensusState, err := NewState(logger, csConfig, stateStore, blockExec, + blockStore, mempool, evpool, eventBus) if err != nil { return nil, err } - consensusState.SetEventBus(eventBus) return consensusState, nil } diff --git a/internal/consensus/replay_stubs.go b/internal/consensus/replay_stubs.go index e479d344e..3cd5bdac0 100644 --- a/internal/consensus/replay_stubs.go +++ b/internal/consensus/replay_stubs.go @@ -57,7 +57,6 @@ func (emptyMempool) CloseWAL() {} // the real app. func newMockProxyApp( - ctx context.Context, logger log.Logger, appHash []byte, abciResponses *tmstate.ABCIResponses, @@ -76,15 +75,15 @@ type mockProxyApp struct { abciResponses *tmstate.ABCIResponses } -func (mock *mockProxyApp) FinalizeBlock(req abci.RequestFinalizeBlock) abci.ResponseFinalizeBlock { +func (mock *mockProxyApp) FinalizeBlock(_ context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { r := mock.abciResponses.FinalizeBlock mock.txCount++ if r == nil { - return abci.ResponseFinalizeBlock{} + return &abci.ResponseFinalizeBlock{}, nil } - return *r + return r, nil } -func (mock *mockProxyApp) Commit() abci.ResponseCommit { - return abci.ResponseCommit{Data: mock.appHash} +func (mock *mockProxyApp) Commit(context.Context) (*abci.ResponseCommit, error) { + return &abci.ResponseCommit{Data: mock.appHash}, nil } diff --git a/internal/consensus/replay_test.go b/internal/consensus/replay_test.go index 2bda63d74..f112f23e8 100644 --- a/internal/consensus/replay_test.go +++ b/internal/consensus/replay_test.go @@ -8,11 +8,11 @@ import ( "io" "math/rand" "os" - "path/filepath" "runtime" "testing" "time" + "github.com/fortytw2/leaktest" "github.com/gogo/protobuf/proto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -56,7 +56,7 @@ import ( func startNewStateAndWaitForBlock(ctx context.Context, t *testing.T, consensusReplayConfig *config.Config, lastBlockHeight int64, blockDB dbm.DB, stateStore sm.Store) { - logger := log.TestingLogger() + logger := log.NewNopLogger() state, err := sm.MakeGenesisStateFromFile(consensusReplayConfig.GenesisFile()) require.NoError(t, err) privValidator := loadPrivValidator(t, consensusReplayConfig) @@ -118,9 +118,6 @@ func sendTxs(ctx context.Context, t *testing.T, cs *State) { // TestWALCrash uses crashing WAL to test we can recover from any WAL failure. func TestWALCrash(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - testCases := []struct { name string initFn func(dbm.DB, *State, context.Context) @@ -139,6 +136,9 @@ func TestWALCrash(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + consensusReplayConfig, err := ResetConfig(t.TempDir(), tc.name) require.NoError(t, err) crashWALandCheckLiveness(ctx, t, consensusReplayConfig, tc.initFn, tc.heightToStop) @@ -338,13 +338,13 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite { nPeers, "replay_test", newMockTickerFunc(true), - newPersistentKVStore) + newEpehemeralKVStore) sim.Config = cfg + defer func() { t.Cleanup(cleanup) }() var err error sim.GenesisState, err = sm.MakeGenesisState(genDoc) require.NoError(t, err) - sim.CleanupFunc = cleanup partSize := types.BlockPartSizeBytes @@ -380,7 +380,7 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite { newValidatorTx1 := kvstore.MakeValSetChangeTx(valPubKey1ABCI, testMinPower) err = assertMempool(t, css[0].txNotifier).CheckTx(ctx, newValidatorTx1, nil, mempool.TxInfo{}) assert.NoError(t, err) - propBlock, _, err := css[0].createProposalBlock(ctx) // changeProposer(t, cs1, vs2) + propBlock, err := css[0].createProposalBlock(ctx) // changeProposer(t, cs1, vs2) require.NoError(t, err) propBlockParts, err := propBlock.MakePartSet(partSize) require.NoError(t, err) @@ -414,7 +414,7 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite { updateValidatorTx1 := kvstore.MakeValSetChangeTx(updatePubKey1ABCI, 25) err = assertMempool(t, css[0].txNotifier).CheckTx(ctx, updateValidatorTx1, nil, mempool.TxInfo{}) assert.NoError(t, err) - propBlock, _, err = css[0].createProposalBlock(ctx) // changeProposer(t, cs1, vs2) + propBlock, err = css[0].createProposalBlock(ctx) // changeProposer(t, cs1, vs2) require.NoError(t, err) propBlockParts, err = propBlock.MakePartSet(partSize) require.NoError(t, err) @@ -455,7 +455,7 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite { newValidatorTx3 := kvstore.MakeValSetChangeTx(newVal3ABCI, testMinPower) err = assertMempool(t, css[0].txNotifier).CheckTx(ctx, newValidatorTx3, nil, mempool.TxInfo{}) assert.NoError(t, err) - propBlock, _, err = css[0].createProposalBlock(ctx) // changeProposer(t, cs1, vs2) + propBlock, err = css[0].createProposalBlock(ctx) // changeProposer(t, cs1, vs2) require.NoError(t, err) propBlockParts, err = propBlock.MakePartSet(partSize) require.NoError(t, err) @@ -543,7 +543,7 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite { removeValidatorTx3 := kvstore.MakeValSetChangeTx(newVal3ABCI, 0) err = assertMempool(t, css[0].txNotifier).CheckTx(ctx, removeValidatorTx3, nil, mempool.TxInfo{}) assert.NoError(t, err) - propBlock, _, err = css[0].createProposalBlock(ctx) // changeProposer(t, cs1, vs2) + propBlock, err = css[0].createProposalBlock(ctx) // changeProposer(t, cs1, vs2) require.NoError(t, err) propBlockParts, err = propBlock.MakePartSet(partSize) require.NoError(t, err) @@ -584,9 +584,6 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite { sim.Chain = append(sim.Chain, css[0].blockStore.LoadBlock(int64(i))) sim.Commits = append(sim.Commits, css[0].blockStore.LoadBlockCommit(int64(i))) } - if sim.CleanupFunc != nil { - t.Cleanup(sim.CleanupFunc) - } return sim } @@ -598,6 +595,8 @@ func TestHandshakeReplayAll(t *testing.T) { sim := setupSimulator(ctx, t) + t.Cleanup(leaktest.Check(t)) + for _, m := range modes { testHandshakeReplay(ctx, t, sim, 0, m, false) } @@ -613,6 +612,8 @@ func TestHandshakeReplaySome(t *testing.T) { sim := setupSimulator(ctx, t) + t.Cleanup(leaktest.Check(t)) + for _, m := range modes { testHandshakeReplay(ctx, t, sim, 2, m, false) } @@ -643,6 +644,8 @@ func TestHandshakeReplayNone(t *testing.T) { sim := setupSimulator(ctx, t) + t.Cleanup(leaktest.Check(t)) + for _, m := range modes { testHandshakeReplay(ctx, t, sim, numBlocks, m, false) } @@ -656,12 +659,12 @@ func tempWALWithData(t *testing.T, data []byte) string { walFile, err := os.CreateTemp(t.TempDir(), "wal") require.NoError(t, err, "failed to create temp WAL file") + t.Cleanup(func() { _ = os.RemoveAll(walFile.Name()) }) _, err = walFile.Write(data) require.NoError(t, err, "failed to write to temp WAL file") require.NoError(t, walFile.Close(), "failed to close temp WAL file") - return walFile.Name() } @@ -686,7 +689,7 @@ func testHandshakeReplay( cfg := sim.Config - logger := log.TestingLogger() + logger := log.NewNopLogger() if testValidatorsChange { testConfig, err := ResetConfig(t.TempDir(), fmt.Sprintf("%s_%v_m", t.Name(), mode)) require.NoError(t, err) @@ -743,21 +746,19 @@ func testHandshakeReplay( ) latestAppHash := state.AppHash - // make a new client creator - kvstoreApp := kvstore.NewPersistentKVStoreApplication(logger, - filepath.Join(cfg.DBDir(), fmt.Sprintf("replay_test_%d_%d_a_r%d", nBlocks, mode, rand.Int()))) - t.Cleanup(func() { require.NoError(t, kvstoreApp.Close()) }) + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) - clientCreator2 := abciclient.NewLocalClient(logger, kvstoreApp) + client := abciclient.NewLocalClient(logger, kvstore.NewApplication()) if nBlocks > 0 { // run nBlocks against a new client to build up the app state. // use a throwaway tendermint state - proxyApp := proxy.New(clientCreator2, logger, proxy.NopMetrics()) + proxyApp := proxy.New(client, logger, proxy.NopMetrics()) stateDB1 := dbm.NewMemDB() stateStore := sm.NewStore(stateDB1) err := stateStore.Save(genesisState) require.NoError(t, err) - buildAppStateFromChain(ctx, t, proxyApp, stateStore, sim.Mempool, sim.Evpool, genesisState, chain, nBlocks, mode, store) + buildAppStateFromChain(ctx, t, proxyApp, stateStore, sim.Mempool, sim.Evpool, genesisState, chain, eventBus, nBlocks, mode, store) } // Prune block store if requested @@ -772,8 +773,8 @@ func testHandshakeReplay( // now start the app using the handshake - it should sync genDoc, err := sm.MakeGenesisDocFromFile(cfg.GenesisFile()) require.NoError(t, err) - handshaker := NewHandshaker(logger, stateStore, state, store, eventbus.NopEventBus{}, genDoc) - proxyApp := proxy.New(clientCreator2, logger, proxy.NopMetrics()) + handshaker := NewHandshaker(logger, stateStore, state, store, eventBus, genDoc) + proxyApp := proxy.New(client, logger, proxy.NopMetrics()) require.NoError(t, proxyApp.Start(ctx), "Error starting proxy app connections") require.True(t, proxyApp.IsRunning()) require.NotNil(t, proxyApp) @@ -787,7 +788,7 @@ func testHandshakeReplay( require.NoError(t, err, "Error on abci handshake") // get the latest app hash from the app - res, err := proxyApp.Info(ctx, abci.RequestInfo{Version: ""}) + res, err := proxyApp.Info(ctx, &abci.RequestInfo{Version: ""}) if err != nil { t.Fatal(err) } @@ -822,9 +823,10 @@ func applyBlock( blk *types.Block, appClient abciclient.Client, blockStore *mockBlockStore, + eventBus *eventbus.EventBus, ) sm.State { testPartSize := types.BlockPartSizeBytes - blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), appClient, mempool, evpool, blockStore) + blockExec := sm.NewBlockExecutor(stateStore, log.NewNopLogger(), appClient, mempool, evpool, blockStore, eventBus, sm.NopMetrics()) bps, err := blk.MakePartSet(testPartSize) require.NoError(t, err) @@ -843,6 +845,7 @@ func buildAppStateFromChain( evpool sm.EvidencePool, state sm.State, chain []*types.Block, + eventBus *eventbus.EventBus, nBlocks int, mode uint, blockStore *mockBlockStore, @@ -853,7 +856,7 @@ func buildAppStateFromChain( state.Version.Consensus.App = kvstore.ProtocolVersion // simulate handshake, receive app version validators := types.TM2PB.ValidatorUpdates(state.Validators) - _, err := appClient.InitChain(ctx, abci.RequestInitChain{ + _, err := appClient.InitChain(ctx, &abci.RequestInitChain{ Validators: validators, }) require.NoError(t, err) @@ -864,18 +867,18 @@ func buildAppStateFromChain( case 0: for i := 0; i < nBlocks; i++ { block := chain[i] - state = applyBlock(ctx, t, stateStore, mempool, evpool, state, block, appClient, blockStore) + state = applyBlock(ctx, t, stateStore, mempool, evpool, state, block, appClient, blockStore, eventBus) } case 1, 2, 3: for i := 0; i < nBlocks-1; i++ { block := chain[i] - state = applyBlock(ctx, t, stateStore, mempool, evpool, state, block, appClient, blockStore) + state = applyBlock(ctx, t, stateStore, mempool, evpool, state, block, appClient, blockStore, eventBus) } if mode == 2 || mode == 3 { // update the kvstore height and apphash // as if we ran commit but not - state = applyBlock(ctx, t, stateStore, mempool, evpool, state, chain[nBlocks-1], appClient, blockStore) + state = applyBlock(ctx, t, stateStore, mempool, evpool, state, chain[nBlocks-1], appClient, blockStore, eventBus) } default: require.Fail(t, "unknown mode %v", mode) @@ -900,40 +903,40 @@ func buildTMStateFromChain( t.Helper() // run the whole chain against this client to build up the tendermint state - kvstoreApp := kvstore.NewPersistentKVStoreApplication(logger, - filepath.Join(cfg.DBDir(), fmt.Sprintf("replay_test_%d_%d_t", nBlocks, mode))) - defer kvstoreApp.Close() - client := abciclient.NewLocalClient(logger, kvstoreApp) + client := abciclient.NewLocalClient(logger, kvstore.NewApplication()) proxyApp := proxy.New(client, logger, proxy.NopMetrics()) require.NoError(t, proxyApp.Start(ctx)) state.Version.Consensus.App = kvstore.ProtocolVersion // simulate handshake, receive app version validators := types.TM2PB.ValidatorUpdates(state.Validators) - _, err := proxyApp.InitChain(ctx, abci.RequestInitChain{ + _, err := proxyApp.InitChain(ctx, &abci.RequestInitChain{ Validators: validators, }) require.NoError(t, err) require.NoError(t, stateStore.Save(state)) + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) + switch mode { case 0: // sync right up for _, block := range chain { - state = applyBlock(ctx, t, stateStore, mempool, evpool, state, block, proxyApp, blockStore) + state = applyBlock(ctx, t, stateStore, mempool, evpool, state, block, proxyApp, blockStore, eventBus) } case 1, 2, 3: // sync up to the penultimate as if we stored the block. // whether we commit or not depends on the appHash for _, block := range chain[:len(chain)-1] { - state = applyBlock(ctx, t, stateStore, mempool, evpool, state, block, proxyApp, blockStore) + state = applyBlock(ctx, t, stateStore, mempool, evpool, state, block, proxyApp, blockStore, eventBus) } // apply the final block to a state copy so we can // get the right next appHash but keep the state back - applyBlock(ctx, t, stateStore, mempool, evpool, state, chain[len(chain)-1], proxyApp, blockStore) + applyBlock(ctx, t, stateStore, mempool, evpool, state, chain[len(chain)-1], proxyApp, blockStore, eventBus) default: require.Fail(t, "unknown mode %v", mode) } @@ -941,7 +944,7 @@ func buildTMStateFromChain( return state } -func TestHandshakePanicsIfAppReturnsWrongAppHash(t *testing.T) { +func TestHandshakeErrorsIfAppReturnsWrongAppHash(t *testing.T) { // 1. Initialize tendermint and commit 3 blocks with the following app hashes: // - 0x01 // - 0x02 @@ -968,7 +971,10 @@ func TestHandshakePanicsIfAppReturnsWrongAppHash(t *testing.T) { store.chain = blocks - logger := log.TestingLogger() + logger := log.NewNopLogger() + + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) // 2. Tendermint must panic if app returns wrong hash for the first block // - RANDOM HASH @@ -982,12 +988,8 @@ func TestHandshakePanicsIfAppReturnsWrongAppHash(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { cancel(); proxyApp.Wait() }) - assert.Panics(t, func() { - h := NewHandshaker(logger, stateStore, state, store, eventbus.NopEventBus{}, genDoc) - if err = h.Handshake(ctx, proxyApp); err != nil { - t.Log(err) - } - }) + h := NewHandshaker(logger, stateStore, state, store, eventBus, genDoc) + assert.Error(t, h.Handshake(ctx, proxyApp)) } // 3. Tendermint must panic if app returns wrong hash for the last block @@ -1002,12 +1004,8 @@ func TestHandshakePanicsIfAppReturnsWrongAppHash(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { cancel(); proxyApp.Wait() }) - assert.Panics(t, func() { - h := NewHandshaker(logger, stateStore, state, store, eventbus.NopEventBus{}, genDoc) - if err = h.Handshake(ctx, proxyApp); err != nil { - t.Log(err) - } - }) + h := NewHandshaker(logger, stateStore, state, store, eventBus, genDoc) + require.Error(t, h.Handshake(ctx, proxyApp)) } } @@ -1019,15 +1017,15 @@ type badApp struct { onlyLastHashIsWrong bool } -func (app *badApp) Commit() abci.ResponseCommit { +func (app *badApp) Commit(context.Context) (*abci.ResponseCommit, error) { app.height++ if app.onlyLastHashIsWrong { if app.height == app.numBlocks { - return abci.ResponseCommit{Data: tmrand.Bytes(8)} + return &abci.ResponseCommit{Data: tmrand.Bytes(8)}, nil } - return abci.ResponseCommit{Data: []byte{app.height}} + return &abci.ResponseCommit{Data: []byte{app.height}}, nil } else if app.allHashesAreWrong { - return abci.ResponseCommit{Data: tmrand.Bytes(8)} + return &abci.ResponseCommit{Data: tmrand.Bytes(8)}, nil } panic("either allHashesAreWrong or onlyLastHashIsWrong must be set") @@ -1235,6 +1233,9 @@ func TestHandshakeUpdatesValidators(t *testing.T) { app := &initChainApp{vals: types.TM2PB.ValidatorUpdates(vals)} client := abciclient.NewLocalClient(logger, app) + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) + cfg, err := ResetConfig(t.TempDir(), "handshake_test_") require.NoError(t, err) t.Cleanup(func() { _ = os.RemoveAll(cfg.RootDir) }) @@ -1252,7 +1253,7 @@ func TestHandshakeUpdatesValidators(t *testing.T) { genDoc, err := sm.MakeGenesisDocFromFile(cfg.GenesisFile()) require.NoError(t, err) - handshaker := NewHandshaker(logger, stateStore, state, store, eventbus.NopEventBus{}, genDoc) + handshaker := NewHandshaker(logger, stateStore, state, store, eventBus, genDoc) proxyApp := proxy.New(client, logger, proxy.NopMetrics()) require.NoError(t, proxyApp.Start(ctx), "Error starting proxy app connections") @@ -1274,8 +1275,6 @@ type initChainApp struct { vals []abci.ValidatorUpdate } -func (ica *initChainApp) InitChain(req abci.RequestInitChain) abci.ResponseInitChain { - return abci.ResponseInitChain{ - Validators: ica.vals, - } +func (ica *initChainApp) InitChain(_ context.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { + return &abci.ResponseInitChain{Validators: ica.vals}, nil } diff --git a/internal/consensus/state.go b/internal/consensus/state.go index ff6ffef3d..490801ad2 100644 --- a/internal/consensus/state.go +++ b/internal/consensus/state.go @@ -20,6 +20,7 @@ import ( cstypes "github.com/tendermint/tendermint/internal/consensus/types" "github.com/tendermint/tendermint/internal/eventbus" "github.com/tendermint/tendermint/internal/jsontypes" + "github.com/tendermint/tendermint/internal/libs/autofile" sm "github.com/tendermint/tendermint/internal/state" tmevents "github.com/tendermint/tendermint/libs/events" "github.com/tendermint/tendermint/libs/log" @@ -123,6 +124,7 @@ type State struct { stateStore sm.Store initialStatePopulated bool + skipBootstrapping bool // create and execute blocks blockExec *sm.BlockExecutor @@ -170,9 +172,6 @@ type State struct { doPrevote func(ctx context.Context, height int64, round int32) setProposal func(proposal *types.Proposal, t time.Time) error - // closed when we finish shutting down - done chan struct{} - // synchronous pubsub between consensus state and reactor. // state only emits EventNewRoundStep, EventValidBlock, and EventVote evsw tmevents.EventSwitch @@ -187,9 +186,14 @@ type State struct { // StateOption sets an optional parameter on the State. type StateOption func(*State) +// SkipStateStoreBootstrap is a state option forces the constructor to +// skip state bootstrapping during construction. +func SkipStateStoreBootstrap(sm *State) { + sm.skipBootstrapping = true +} + // NewState returns a new State. func NewState( - ctx context.Context, logger log.Logger, cfg *config.ConsensusConfig, store sm.Store, @@ -197,9 +201,11 @@ func NewState( blockStore sm.BlockStore, txNotifier txNotifier, evpool evidencePool, + eventBus *eventbus.EventBus, options ...StateOption, ) (*State, error) { cs := &State{ + eventBus: eventBus, logger: logger, config: cfg, blockExec: blockExec, @@ -210,11 +216,10 @@ func NewState( internalMsgQueue: make(chan msgInfo, msgQueueSize), timeoutTicker: NewTimeoutTicker(logger), statsMsgQueue: make(chan msgInfo, msgQueueSize), - done: make(chan struct{}), doWALCatchup: true, wal: nilWAL{}, evpool: evpool, - evsw: tmevents.NewEventSwitch(logger), + evsw: tmevents.NewEventSwitch(), metrics: NopMetrics(), onStopCh: make(chan *cstypes.RoundState), } @@ -224,20 +229,25 @@ func NewState( cs.doPrevote = cs.defaultDoPrevote cs.setProposal = cs.defaultSetProposal - if err := cs.updateStateFromStore(ctx); err != nil { - return nil, err - } - // NOTE: we do not call scheduleRound0 yet, we do that upon Start() cs.BaseService = *service.NewBaseService(logger, "State", cs) for _, option := range options { option(cs) } + // this is not ideal, but it lets the consensus tests start + // node-fragments gracefully while letting the nodes + // themselves avoid this. + if !cs.skipBootstrapping { + if err := cs.updateStateFromStore(); err != nil { + return nil, err + } + } + return cs, nil } -func (cs *State) updateStateFromStore(ctx context.Context) error { +func (cs *State) updateStateFromStore() error { if cs.initialStatePopulated { return nil } @@ -254,18 +264,12 @@ func (cs *State) updateStateFromStore(ctx context.Context) error { cs.reconstructLastCommit(state) } - cs.updateToState(ctx, state) + cs.updateToState(state) cs.initialStatePopulated = true return nil } -// SetEventBus sets event bus. -func (cs *State) SetEventBus(b *eventbus.EventBus) { - cs.eventBus = b - cs.blockExec.SetEventBus(b) -} - // StateMetrics sets the metrics. func StateMetrics(metrics *Metrics) StateOption { return func(cs *State) { cs.metrics = metrics } @@ -388,7 +392,7 @@ func (cs *State) LoadCommit(height int64) *types.Commit { // OnStart loads the latest state via the WAL, and starts the timeout and // receive routines. func (cs *State) OnStart(ctx context.Context) error { - if err := cs.updateStateFromStore(ctx); err != nil { + if err := cs.updateStateFromStore(); err != nil { return err } @@ -459,10 +463,6 @@ func (cs *State) OnStart(ctx context.Context) error { } } - if err := cs.evsw.Start(ctx); err != nil { - return err - } - // Double Signing Risk Reduction if err := cs.checkDoubleSigningRisk(cs.Height); err != nil { return err @@ -504,37 +504,30 @@ func (cs *State) loadWalFile(ctx context.Context) error { return nil } +func (cs *State) getOnStopCh() chan *cstypes.RoundState { + cs.mtx.RLock() + defer cs.mtx.RUnlock() + + return cs.onStopCh +} + // OnStop implements service.Service. func (cs *State) OnStop() { // If the node is committing a new block, wait until it is finished! if cs.GetRoundState().Step == cstypes.RoundStepCommit { select { - case <-cs.onStopCh: - case <-time.After(cs.config.TimeoutCommit): - cs.logger.Error("OnStop: timeout waiting for commit to finish", "time", cs.config.TimeoutCommit) + case <-cs.getOnStopCh(): + case <-time.After(cs.state.ConsensusParams.Timeout.Commit): + cs.logger.Error("OnStop: timeout waiting for commit to finish", "time", cs.state.ConsensusParams.Timeout.Commit) } } - close(cs.onStopCh) - - if cs.evsw.IsRunning() { - cs.evsw.Stop() - } - if cs.timeoutTicker.IsRunning() { cs.timeoutTicker.Stop() } // WAL is stopped in receiveRoutine. } -// Wait waits for the the main routine to return. -// NOTE: be sure to Stop() the event switch and drain -// any event channels or this may deadlock -func (cs *State) Wait() { - cs.evsw.Wait() - <-cs.done -} - // OpenWAL opens a file to log all consensus messages and timeouts for // deterministic accountability. func (cs *State) OpenWAL(ctx context.Context, walFile string) (WAL, error) { @@ -724,7 +717,7 @@ func (cs *State) reconstructLastCommit(state sm.State) { // Updates State and increments height to match that of state. // The round becomes 0 and cs.Step becomes cstypes.RoundStepNewHeight. -func (cs *State) updateToState(ctx context.Context, state sm.State) { +func (cs *State) updateToState(state sm.State) { if cs.CommitRound > -1 && 0 < cs.Height && cs.Height != state.LastBlockHeight { panic(fmt.Sprintf( "updateToState() expected state height of %v but found %v", @@ -759,7 +752,7 @@ func (cs *State) updateToState(ctx context.Context, state sm.State) { "new_height", state.LastBlockHeight+1, "old_height", cs.state.LastBlockHeight+1, ) - cs.newStep(ctx) + cs.newStep() return } } @@ -805,9 +798,9 @@ func (cs *State) updateToState(ctx context.Context, state sm.State) { // to be gathered for the first block. // And alternative solution that relies on clocks: // cs.StartTime = state.LastBlockTime.Add(timeoutCommit) - cs.StartTime = cs.config.Commit(tmtime.Now()) + cs.StartTime = cs.commitTime(tmtime.Now()) } else { - cs.StartTime = cs.config.Commit(cs.CommitTime) + cs.StartTime = cs.commitTime(cs.CommitTime) } cs.Validators = validators @@ -829,10 +822,10 @@ func (cs *State) updateToState(ctx context.Context, state sm.State) { cs.state = state // Finally, broadcast RoundState - cs.newStep(ctx) + cs.newStep() } -func (cs *State) newStep(ctx context.Context) { +func (cs *State) newStep() { rs := cs.RoundStateEvent() if err := cs.wal.Write(rs); err != nil { cs.logger.Error("failed writing to WAL", "err", err) @@ -842,11 +835,11 @@ func (cs *State) newStep(ctx context.Context) { // newStep is called by updateToState in NewState before the eventBus is set! if cs.eventBus != nil { - if err := cs.eventBus.PublishEventNewRoundStep(ctx, rs); err != nil { + if err := cs.eventBus.PublishEventNewRoundStep(rs); err != nil { cs.logger.Error("failed publishing new round step", "err", err) } - cs.evsw.FireEvent(ctx, types.EventNewRoundStepValue, &cs.RoundState) + cs.evsw.FireEvent(types.EventNewRoundStepValue, &cs.RoundState) } } @@ -867,21 +860,48 @@ func (cs *State) receiveRoutine(ctx context.Context, maxSteps int) { // close wal now that we're done writing to it cs.wal.Stop() cs.wal.Wait() - close(cs.done) } defer func() { if r := recover(); r != nil { cs.logger.Error("CONSENSUS FAILURE!!!", "err", r, "stack", string(debug.Stack())) - // stop gracefully - // - // NOTE: We most probably shouldn't be running any further when there is - // some unexpected panic. Some unknown error happened, and so we don't - // know if that will result in the validator signing an invalid thing. It - // might be worthwhile to explore a mechanism for manual resuming via - // some console or secure RPC system, but for now, halting the chain upon - // unexpected consensus bugs sounds like the better option. + + // Make a best-effort attempt to close the WAL, but otherwise do not + // attempt to gracefully terminate. Once consensus has irrecoverably + // failed, any additional progress we permit the node to make may + // complicate diagnosing and recovering from the failure. onExit(cs) + + // There are a couple of cases where the we + // panic with an error from deeper within the + // state machine and in these cases, typically + // during a normal shutdown, we can continue + // with normal shutdown with safety. These + // cases are: + if err, ok := r.(error); ok { + // TODO(creachadair): In ordinary operation, the WAL autofile should + // never be closed. This only happens during shutdown and production + // nodes usually halt by panicking. Many existing tests, however, + // assume a clean shutdown is possible. Prior to #8111, we were + // swallowing the panic in receiveRoutine, making that appear to + // work. Filtering this specific error is slightly risky, but should + // affect only unit tests. In any case, not re-panicking here only + // preserves the pre-existing behavior for this one error type. + if errors.Is(err, autofile.ErrAutoFileClosed) { + return + } + + // don't re-panic if the panic is just an + // error and we're already trying to shut down + if ctx.Err() != nil { + return + + } + } + + // Re-panic to ensure the node terminates. + // + panic(r) } }() @@ -902,7 +922,6 @@ func (cs *State) receiveRoutine(ctx context.Context, maxSteps int) { if err := cs.wal.Write(mi); err != nil { cs.logger.Error("failed writing to WAL", "err", err) } - // handles proposals, block parts, votes // may generate internal events (votes, complete proposals, 2/3 majorities) cs.handleMsg(ctx, mi) @@ -910,8 +929,8 @@ func (cs *State) receiveRoutine(ctx context.Context, maxSteps int) { case mi := <-cs.internalMsgQueue: err := cs.wal.WriteSync(mi) // NOTE: fsync if err != nil { - panic(fmt.Sprintf( - "failed to write %v msg to consensus WAL due to %v; check your file system and restart the node", + panic(fmt.Errorf( + "failed to write %v msg to consensus WAL due to %w; check your file system and restart the node", mi, err, )) } @@ -941,7 +960,6 @@ func (cs *State) receiveRoutine(ctx context.Context, maxSteps int) { func (cs *State) handleMsg(ctx context.Context, mi msgInfo) { cs.mtx.Lock() defer cs.mtx.Unlock() - var ( added bool err error @@ -957,7 +975,25 @@ func (cs *State) handleMsg(ctx context.Context, mi msgInfo) { case *BlockPartMessage: // if the proposal is complete, we'll enterPrevote or tryFinalizeCommit - added, err = cs.addProposalBlockPart(ctx, msg, peerID) + added, err = cs.addProposalBlockPart(msg, peerID) + + // We unlock here to yield to any routines that need to read the the RoundState. + // Previously, this code held the lock from the point at which the final block + // part was received until the block executed against the application. + // This prevented the reactor from being able to retrieve the most updated + // version of the RoundState. The reactor needs the updated RoundState to + // gossip the now completed block. + // + // This code can be further improved by either always operating on a copy + // of RoundState and only locking when switching out State's copy of + // RoundState with the updated copy or by emitting RoundState events in + // more places for routines depending on it to listen for. + cs.mtx.Unlock() + + cs.mtx.Lock() + if added && cs.ProposalBlockParts.IsComplete() { + cs.handleCompleteProposal(ctx, msg.Height) + } if added { select { case cs.statsMsgQueue <- mi: @@ -988,13 +1024,11 @@ func (cs *State) handleMsg(ctx context.Context, mi msgInfo) { } } - // if err == ErrAddingVote { // TODO: punish peer // We probably don't want to stop the peer here. The vote does not // necessarily comes from a malicious peer but can be just broadcasted by // a typical peer. // https://github.com/tendermint/tendermint/issues/1281 - // } // NOTE: the vote is broadcast to peers by the reactor listening // for vote events @@ -1047,21 +1081,21 @@ func (cs *State) handleTimeout( cs.enterPropose(ctx, ti.Height, 0) case cstypes.RoundStepPropose: - if err := cs.eventBus.PublishEventTimeoutPropose(ctx, cs.RoundStateEvent()); err != nil { + if err := cs.eventBus.PublishEventTimeoutPropose(cs.RoundStateEvent()); err != nil { cs.logger.Error("failed publishing timeout propose", "err", err) } cs.enterPrevote(ctx, ti.Height, ti.Round) case cstypes.RoundStepPrevoteWait: - if err := cs.eventBus.PublishEventTimeoutWait(ctx, cs.RoundStateEvent()); err != nil { + if err := cs.eventBus.PublishEventTimeoutWait(cs.RoundStateEvent()); err != nil { cs.logger.Error("failed publishing timeout wait", "err", err) } cs.enterPrecommit(ctx, ti.Height, ti.Round) case cstypes.RoundStepPrecommitWait: - if err := cs.eventBus.PublishEventTimeoutWait(ctx, cs.RoundStateEvent()); err != nil { + if err := cs.eventBus.PublishEventTimeoutWait(cs.RoundStateEvent()); err != nil { cs.logger.Error("failed publishing timeout wait", "err", err) } @@ -1164,7 +1198,7 @@ func (cs *State) enterNewRound(ctx context.Context, height int64, round int32) { cs.Votes.SetRound(r) // also track next round (round+1) to allow round-skipping cs.TriggeredTimeoutPrecommit = false - if err := cs.eventBus.PublishEventNewRound(ctx, cs.NewRoundEvent()); err != nil { + if err := cs.eventBus.PublishEventNewRound(cs.NewRoundEvent()); err != nil { cs.logger.Error("failed publishing new round", "err", err) } // Wait for txs to be available in the mempool @@ -1227,7 +1261,7 @@ func (cs *State) enterPropose(ctx context.Context, height int64, round int32) { defer func() { // Done enterPropose: cs.updateRoundStep(round, cstypes.RoundStepPropose) - cs.newStep(ctx) + cs.newStep() // If we have the whole proposal + POL, then goto Prevote now. // else, we'll enterPrevote when the rest of the proposal is received (in AddProposalBlockPart), @@ -1238,7 +1272,7 @@ func (cs *State) enterPropose(ctx context.Context, height int64, round int32) { }() // If we don't get the proposal and all block parts quick enough, enterPrevote - cs.scheduleTimeout(cs.config.Propose(round), height, round, cstypes.RoundStepPropose) + cs.scheduleTimeout(cs.proposeTimeout(round), height, round, cstypes.RoundStepPropose) // Nothing more to do if we're not a validator if cs.privValidator == nil { @@ -1293,8 +1327,16 @@ func (cs *State) defaultDecideProposal(ctx context.Context, height int64, round } else { // Create a new proposal block from state/txs from the mempool. var err error - block, blockParts, err = cs.createProposalBlock(ctx) - if block == nil || err != nil { + block, err = cs.createProposalBlock(ctx) + if err != nil { + cs.logger.Error("unable to create proposal block", "error", err) + return + } else if block == nil { + return + } + blockParts, err = block.MakePartSet(types.BlockPartSizeBytes) + if err != nil { + cs.logger.Error("unable to create proposal block part set", "error", err) return } } @@ -1311,7 +1353,7 @@ func (cs *State) defaultDecideProposal(ctx context.Context, height int64, round p := proposal.ToProto() // wait the max amount we would wait for a proposal - ctxto, cancel := context.WithTimeout(ctx, cs.config.TimeoutPropose) + ctxto, cancel := context.WithTimeout(ctx, cs.state.ConsensusParams.Timeout.Propose) defer cancel() if err := cs.privValidator.SignProposal(ctxto, cs.state.ChainID, p); err == nil { proposal.Signature = p.Signature @@ -1353,13 +1395,12 @@ func (cs *State) isProposalComplete() bool { // // NOTE: keep it side-effect free for clarity. // CONTRACT: cs.privValidator is not nil. -func (cs *State) createProposalBlock(ctx context.Context) (block *types.Block, blockParts *types.PartSet, err error) { +func (cs *State) createProposalBlock(ctx context.Context) (*types.Block, error) { if cs.privValidator == nil { - return nil, nil, errors.New("entered createProposalBlock with privValidator being nil") + return nil, errors.New("entered createProposalBlock with privValidator being nil") } var commit *types.Commit - var votes []*types.Vote switch { case cs.Height == cs.state.InitialHeight: // We're creating a proposal for the first block. @@ -1369,23 +1410,26 @@ func (cs *State) createProposalBlock(ctx context.Context) (block *types.Block, b case cs.LastCommit.HasTwoThirdsMajority(): // Make the commit from LastCommit commit = cs.LastCommit.MakeCommit() - votes = cs.LastCommit.GetVotes() default: // This shouldn't happen. cs.logger.Error("propose step; cannot propose anything without commit for the previous block") - return + return nil, nil } if cs.privValidatorPubKey == nil { // If this node is a validator & proposer in the current round, it will // miss the opportunity to create a block. cs.logger.Error("propose step; empty priv validator public key", "err", errPubKeyIsNotSet) - return + return nil, nil } proposerAddr := cs.privValidatorPubKey.Address() - return cs.blockExec.CreateProposalBlock(ctx, cs.Height, cs.state, commit, proposerAddr, votes) + ret, err := cs.blockExec.CreateProposalBlock(ctx, cs.Height, cs.state, commit, proposerAddr, cs.LastCommit.GetVotes()) + if err != nil { + panic(err) + } + return ret, nil } // Enter: `timeoutPropose` after entering Propose. @@ -1409,7 +1453,7 @@ func (cs *State) enterPrevote(ctx context.Context, height int64, round int32) { defer func() { // Done enterPrevote: cs.updateRoundStep(round, cstypes.RoundStepPrevote) - cs.newStep(ctx) + cs.newStep() }() logger.Debug("entering prevote step", "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step)) @@ -1422,11 +1466,7 @@ func (cs *State) enterPrevote(ctx context.Context, height int64, round int32) { } func (cs *State) proposalIsTimely() bool { - sp := types.SynchronyParams{ - Precision: cs.state.ConsensusParams.Synchrony.Precision, - MessageDelay: cs.state.ConsensusParams.Synchrony.MessageDelay, - } - + sp := cs.state.ConsensusParams.Synchrony.SynchronyParamsOrDefaults() return cs.Proposal.IsTimely(cs.ProposalReceiveTime, sp, cs.Round) } @@ -1452,6 +1492,7 @@ func (cs *State) defaultDoPrevote(ctx context.Context, height int64, round int32 return } + sp := cs.state.ConsensusParams.Synchrony.SynchronyParamsOrDefaults() if cs.Proposal.POLRound == -1 && cs.LockedRound == -1 && !cs.proposalIsTimely() { logger.Debug("prevote step: Proposal is not timely; prevoting nil", "proposed", @@ -1459,9 +1500,9 @@ func (cs *State) defaultDoPrevote(ctx context.Context, height int64, round int32 "received", tmtime.Canonical(cs.ProposalReceiveTime).Format(time.RFC3339Nano), "msg_delay", - cs.state.ConsensusParams.Synchrony.MessageDelay, + sp.MessageDelay, "precision", - cs.state.ConsensusParams.Synchrony.Precision) + sp.Precision) cs.signAddVote(ctx, tmproto.PrevoteType, nil, types.PartSetHeader{}) return } @@ -1563,7 +1604,7 @@ func (cs *State) defaultDoPrevote(ctx context.Context, height int64, round int32 } // Enter: any +2/3 prevotes at next round. -func (cs *State) enterPrevoteWait(ctx context.Context, height int64, round int32) { +func (cs *State) enterPrevoteWait(height int64, round int32) { logger := cs.logger.With("height", height, "round", round) if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPrevoteWait <= cs.Step) { @@ -1586,11 +1627,11 @@ func (cs *State) enterPrevoteWait(ctx context.Context, height int64, round int32 defer func() { // Done enterPrevoteWait: cs.updateRoundStep(round, cstypes.RoundStepPrevoteWait) - cs.newStep(ctx) + cs.newStep() }() // Wait for some more prevotes; enterPrecommit - cs.scheduleTimeout(cs.config.Prevote(round), height, round, cstypes.RoundStepPrevoteWait) + cs.scheduleTimeout(cs.voteTimeout(round), height, round, cstypes.RoundStepPrevoteWait) } // Enter: `timeoutPrevote` after any +2/3 prevotes. @@ -1614,7 +1655,7 @@ func (cs *State) enterPrecommit(ctx context.Context, height int64, round int32) defer func() { // Done enterPrecommit: cs.updateRoundStep(round, cstypes.RoundStepPrecommit) - cs.newStep(ctx) + cs.newStep() }() // check for a polka @@ -1633,7 +1674,7 @@ func (cs *State) enterPrecommit(ctx context.Context, height int64, round int32) } // At this point +2/3 prevoted for a particular block or nil. - if err := cs.eventBus.PublishEventPolka(ctx, cs.RoundStateEvent()); err != nil { + if err := cs.eventBus.PublishEventPolka(cs.RoundStateEvent()); err != nil { logger.Error("failed publishing polka", "err", err) } @@ -1670,7 +1711,7 @@ func (cs *State) enterPrecommit(ctx context.Context, height int64, round int32) logger.Debug("precommit step: +2/3 prevoted locked block; relocking") cs.LockedRound = round - if err := cs.eventBus.PublishEventRelock(ctx, cs.RoundStateEvent()); err != nil { + if err := cs.eventBus.PublishEventRelock(cs.RoundStateEvent()); err != nil { logger.Error("precommit step: failed publishing event relock", "err", err) } @@ -1693,7 +1734,7 @@ func (cs *State) enterPrecommit(ctx context.Context, height int64, round int32) cs.LockedBlock = cs.ProposalBlock cs.LockedBlockParts = cs.ProposalBlockParts - if err := cs.eventBus.PublishEventLock(ctx, cs.RoundStateEvent()); err != nil { + if err := cs.eventBus.PublishEventLock(cs.RoundStateEvent()); err != nil { logger.Error("precommit step: failed publishing event lock", "err", err) } @@ -1715,7 +1756,7 @@ func (cs *State) enterPrecommit(ctx context.Context, height int64, round int32) } // Enter: any +2/3 precommits for next round. -func (cs *State) enterPrecommitWait(ctx context.Context, height int64, round int32) { +func (cs *State) enterPrecommitWait(height int64, round int32) { logger := cs.logger.With("height", height, "round", round) if cs.Height != height || round < cs.Round || (cs.Round == round && cs.TriggeredTimeoutPrecommit) { @@ -1739,11 +1780,11 @@ func (cs *State) enterPrecommitWait(ctx context.Context, height int64, round int defer func() { // Done enterPrecommitWait: cs.TriggeredTimeoutPrecommit = true - cs.newStep(ctx) + cs.newStep() }() // wait for some more precommits; enterNewRound - cs.scheduleTimeout(cs.config.Precommit(round), height, round, cstypes.RoundStepPrecommitWait) + cs.scheduleTimeout(cs.voteTimeout(round), height, round, cstypes.RoundStepPrecommitWait) } // Enter: +2/3 precommits for block @@ -1766,7 +1807,7 @@ func (cs *State) enterCommit(ctx context.Context, height int64, commitRound int3 cs.updateRoundStep(cs.Round, cstypes.RoundStepCommit) cs.CommitRound = commitRound cs.CommitTime = tmtime.Now() - cs.newStep(ctx) + cs.newStep() // Maybe finalize immediately. cs.tryFinalizeCommit(ctx, height) @@ -1801,11 +1842,11 @@ func (cs *State) enterCommit(ctx context.Context, height int64, commitRound int3 cs.metrics.MarkBlockGossipStarted() cs.ProposalBlockParts = types.NewPartSetFromHeader(blockID.PartSetHeader) - if err := cs.eventBus.PublishEventValidBlock(ctx, cs.RoundStateEvent()); err != nil { + if err := cs.eventBus.PublishEventValidBlock(cs.RoundStateEvent()); err != nil { logger.Error("failed publishing valid block", "err", err) } - cs.evsw.FireEvent(ctx, types.EventValidBlockValue, &cs.RoundState) + cs.evsw.FireEvent(types.EventValidBlockValue, &cs.RoundState) } } } @@ -1904,8 +1945,8 @@ func (cs *State) finalizeCommit(ctx context.Context, height int64) { // restart). endMsg := EndHeightMessage{height} if err := cs.wal.WriteSync(endMsg); err != nil { // NOTE: fsync - panic(fmt.Sprintf( - "failed to write %v msg to consensus WAL due to %v; check your file system and restart the node", + panic(fmt.Errorf( + "failed to write %v msg to consensus WAL due to %w; check your file system and restart the node", endMsg, err, )) } @@ -1932,7 +1973,7 @@ func (cs *State) finalizeCommit(ctx context.Context, height int64) { cs.RecordMetrics(height, block) // NewHeightStep! - cs.updateToState(ctx, stateCopy) + cs.updateToState(stateCopy) // Private validator might have changed it's key pair => refetch pubkey. if err := cs.updatePrivValidatorPubKey(ctx); err != nil { @@ -2087,7 +2128,6 @@ func (cs *State) defaultSetProposal(proposal *types.Proposal, recvTime time.Time // Asynchronously triggers either enterPrevote (before we timeout of propose) or tryFinalizeCommit, // once we have the full block. func (cs *State) addProposalBlockPart( - ctx context.Context, msg *BlockPartMessage, peerID types.NodeID, ) (added bool, err error) { @@ -2153,48 +2193,47 @@ func (cs *State) addProposalBlockPart( // NOTE: it's possible to receive complete proposal blocks for future rounds without having the proposal cs.logger.Info("received complete proposal block", "height", cs.ProposalBlock.Height, "hash", cs.ProposalBlock.Hash()) - if err := cs.eventBus.PublishEventCompleteProposal(ctx, cs.CompleteProposalEvent()); err != nil { + if err := cs.eventBus.PublishEventCompleteProposal(cs.CompleteProposalEvent()); err != nil { cs.logger.Error("failed publishing event complete proposal", "err", err) } - - // Update Valid* if we can. - prevotes := cs.Votes.Prevotes(cs.Round) - blockID, hasTwoThirds := prevotes.TwoThirdsMajority() - if hasTwoThirds && !blockID.IsNil() && (cs.ValidRound < cs.Round) { - if cs.ProposalBlock.HashesTo(blockID.Hash) { - cs.logger.Debug( - "updating valid block to new proposal block", - "valid_round", cs.Round, - "valid_block_hash", cs.ProposalBlock.Hash(), - ) - - cs.ValidRound = cs.Round - cs.ValidBlock = cs.ProposalBlock - cs.ValidBlockParts = cs.ProposalBlockParts - } - // TODO: In case there is +2/3 majority in Prevotes set for some - // block and cs.ProposalBlock contains different block, either - // proposer is faulty or voting power of faulty processes is more - // than 1/3. We should trigger in the future accountability - // procedure at this point. - } - - if cs.Step <= cstypes.RoundStepPropose && cs.isProposalComplete() { - // Move onto the next step - cs.enterPrevote(ctx, height, cs.Round) - if hasTwoThirds { // this is optimisation as this will be triggered when prevote is added - cs.enterPrecommit(ctx, height, cs.Round) - } - } else if cs.Step == cstypes.RoundStepCommit { - // If we're waiting on the proposal block... - cs.tryFinalizeCommit(ctx, height) - } - - return added, nil } return added, nil } +func (cs *State) handleCompleteProposal(ctx context.Context, height int64) { + // Update Valid* if we can. + prevotes := cs.Votes.Prevotes(cs.Round) + blockID, hasTwoThirds := prevotes.TwoThirdsMajority() + if hasTwoThirds && !blockID.IsNil() && (cs.ValidRound < cs.Round) { + if cs.ProposalBlock.HashesTo(blockID.Hash) { + cs.logger.Debug( + "updating valid block to new proposal block", + "valid_round", cs.Round, + "valid_block_hash", cs.ProposalBlock.Hash(), + ) + + cs.ValidRound = cs.Round + cs.ValidBlock = cs.ProposalBlock + cs.ValidBlockParts = cs.ProposalBlockParts + } + // TODO: In case there is +2/3 majority in Prevotes set for some + // block and cs.ProposalBlock contains different block, either + // proposer is faulty or voting power of faulty processes is more + // than 1/3. We should trigger in the future accountability + // procedure at this point. + } + + if cs.Step <= cstypes.RoundStepPropose && cs.isProposalComplete() { + // Move onto the next step + cs.enterPrevote(ctx, height, cs.Round) + if hasTwoThirds { // this is optimisation as this will be triggered when prevote is added + cs.enterPrecommit(ctx, height, cs.Round) + } + } else if cs.Step == cstypes.RoundStepCommit { + // If we're waiting on the proposal block... + cs.tryFinalizeCommit(ctx, height) + } +} // Attempt to add the vote. if its a duplicate signature, dupeout the validator func (cs *State) tryAddVote(ctx context.Context, vote *types.Vote, peerID types.NodeID) (bool, error) { @@ -2273,14 +2312,14 @@ func (cs *State) addVote( } cs.logger.Debug("added vote to last precommits", "last_commit", cs.LastCommit.StringShort()) - if err := cs.eventBus.PublishEventVote(ctx, types.EventDataVote{Vote: vote}); err != nil { + if err := cs.eventBus.PublishEventVote(types.EventDataVote{Vote: vote}); err != nil { return added, err } - cs.evsw.FireEvent(ctx, types.EventVoteValue, vote) + cs.evsw.FireEvent(types.EventVoteValue, vote) // if we can skip timeoutCommit and have all the votes now, - if cs.config.SkipTimeoutCommit && cs.LastCommit.HasAll() { + if cs.bypassCommitTimeout() && cs.LastCommit.HasAll() { // go straight to new round (skip timeout commit) // cs.scheduleTimeout(time.Duration(0), cs.Height, 0, cstypes.RoundStepNewHeight) cs.enterNewRound(ctx, cs.Height, 0) @@ -2310,10 +2349,10 @@ func (cs *State) addVote( return } - if err := cs.eventBus.PublishEventVote(ctx, types.EventDataVote{Vote: vote}); err != nil { + if err := cs.eventBus.PublishEventVote(types.EventDataVote{Vote: vote}); err != nil { return added, err } - cs.evsw.FireEvent(ctx, types.EventVoteValue, vote) + cs.evsw.FireEvent(types.EventVoteValue, vote) switch vote.Type { case tmproto.PrevoteType: @@ -2348,8 +2387,8 @@ func (cs *State) addVote( cs.ProposalBlockParts = types.NewPartSetFromHeader(blockID.PartSetHeader) } - cs.evsw.FireEvent(ctx, types.EventValidBlockValue, &cs.RoundState) - if err := cs.eventBus.PublishEventValidBlock(ctx, cs.RoundStateEvent()); err != nil { + cs.evsw.FireEvent(types.EventValidBlockValue, &cs.RoundState) + if err := cs.eventBus.PublishEventValidBlock(cs.RoundStateEvent()); err != nil { return added, err } } @@ -2366,7 +2405,7 @@ func (cs *State) addVote( if ok && (cs.isProposalComplete() || blockID.IsNil()) { cs.enterPrecommit(ctx, height, vote.Round) } else if prevotes.HasTwoThirdsAny() { - cs.enterPrevoteWait(ctx, height, vote.Round) + cs.enterPrevoteWait(height, vote.Round) } case cs.Proposal != nil && 0 <= cs.Proposal.POLRound && cs.Proposal.POLRound == vote.Round: @@ -2393,15 +2432,15 @@ func (cs *State) addVote( if !blockID.IsNil() { cs.enterCommit(ctx, height, vote.Round) - if cs.config.SkipTimeoutCommit && precommits.HasAll() { + if cs.bypassCommitTimeout() && precommits.HasAll() { cs.enterNewRound(ctx, cs.Height, 0) } } else { - cs.enterPrecommitWait(ctx, height, vote.Round) + cs.enterPrecommitWait(height, vote.Round) } } else if cs.Round <= vote.Round && precommits.HasTwoThirdsAny() { cs.enterNewRound(ctx, height, vote.Round) - cs.enterPrecommitWait(ctx, height, vote.Round) + cs.enterPrecommitWait(height, vote.Round) } default: @@ -2443,19 +2482,16 @@ func (cs *State) signVote( // If the signedMessageType is for precommit, // use our local precommit Timeout as the max wait time for getting a singed commit. The same goes for prevote. - var timeout time.Duration + timeout := cs.voteTimeout(cs.Round) switch msgType { case tmproto.PrecommitType: - timeout = cs.config.TimeoutPrecommit // if the signedMessage type is for a precommit, add VoteExtension ext, err := cs.blockExec.ExtendVote(ctx, vote) if err != nil { return nil, err } - vote.VoteExtension = ext - case tmproto.PrevoteType: - timeout = cs.config.TimeoutPrevote + vote.Extension = ext default: timeout = time.Second } @@ -2467,6 +2503,7 @@ func (cs *State) signVote( err := cs.privValidator.SignVote(ctxto, cs.state.ChainID, v) vote.Signature = v.Signature + vote.ExtensionSignature = v.ExtensionSignature vote.Timestamp = v.Timestamp return vote, err @@ -2514,12 +2551,7 @@ func (cs *State) updatePrivValidatorPubKey(rctx context.Context) error { return nil } - var timeout time.Duration - if cs.config.TimeoutPrecommit > cs.config.TimeoutPrevote { - timeout = cs.config.TimeoutPrecommit - } else { - timeout = cs.config.TimeoutPrevote - } + timeout := cs.voteTimeout(cs.Round) // no GetPubKey retry beyond the proposal/voting in RetrySignerClient if cs.Step >= cstypes.RoundStepPrecommit && cs.privValidatorType == types.RetrySignerClient { @@ -2579,12 +2611,12 @@ func (cs *State) calculatePrevoteMessageDelayMetrics() { _, val := cs.Validators.GetByAddress(v.ValidatorAddress) votingPowerSeen += val.VotingPower if votingPowerSeen >= cs.Validators.TotalVotingPower()*2/3+1 { - cs.metrics.QuorumPrevoteMessageDelay.With("proposer_address", cs.Validators.GetProposer().Address.String()).Set(v.Timestamp.Sub(cs.Proposal.Timestamp).Seconds()) + cs.metrics.QuorumPrevoteDelay.With("proposer_address", cs.Validators.GetProposer().Address.String()).Set(v.Timestamp.Sub(cs.Proposal.Timestamp).Seconds()) break } } if ps.HasAll() { - cs.metrics.FullPrevoteMessageDelay.With("proposer_address", cs.Validators.GetProposer().Address.String()).Set(pl[len(pl)-1].Timestamp.Sub(cs.Proposal.Timestamp).Seconds()) + cs.metrics.FullPrevoteDelay.With("proposer_address", cs.Validators.GetProposer().Address.String()).Set(pl[len(pl)-1].Timestamp.Sub(cs.Proposal.Timestamp).Seconds()) } } @@ -2645,14 +2677,55 @@ func repairWalFile(src, dst string) error { return nil } +func (cs *State) proposeTimeout(round int32) time.Duration { + tp := cs.state.ConsensusParams.Timeout.TimeoutParamsOrDefaults() + p := tp.Propose + if cs.config.UnsafeProposeTimeoutOverride != 0 { + p = cs.config.UnsafeProposeTimeoutOverride + } + pd := tp.ProposeDelta + if cs.config.UnsafeProposeTimeoutDeltaOverride != 0 { + pd = cs.config.UnsafeProposeTimeoutDeltaOverride + } + return time.Duration( + p.Nanoseconds()+pd.Nanoseconds()*int64(round), + ) * time.Nanosecond +} + +func (cs *State) voteTimeout(round int32) time.Duration { + tp := cs.state.ConsensusParams.Timeout.TimeoutParamsOrDefaults() + v := tp.Vote + if cs.config.UnsafeVoteTimeoutOverride != 0 { + v = cs.config.UnsafeVoteTimeoutOverride + } + vd := tp.VoteDelta + if cs.config.UnsafeVoteTimeoutDeltaOverride != 0 { + vd = cs.config.UnsafeVoteTimeoutDeltaOverride + } + return time.Duration( + v.Nanoseconds()+vd.Nanoseconds()*int64(round), + ) * time.Nanosecond +} + +func (cs *State) commitTime(t time.Time) time.Time { + c := cs.state.ConsensusParams.Timeout.Commit + if cs.config.UnsafeCommitTimeoutOverride != 0 { + c = cs.config.UnsafeProposeTimeoutOverride + } + return t.Add(c) +} + +func (cs *State) bypassCommitTimeout() bool { + if cs.config.UnsafeBypassCommitTimeoutOverride != nil { + return *cs.config.UnsafeBypassCommitTimeoutOverride + } + return cs.state.ConsensusParams.Timeout.BypassCommitTimeout +} + func (cs *State) calculateProposalTimestampDifferenceMetric() { if cs.Proposal != nil && cs.Proposal.POLRound == -1 { - tp := types.SynchronyParams{ - Precision: cs.state.ConsensusParams.Synchrony.Precision, - MessageDelay: cs.state.ConsensusParams.Synchrony.MessageDelay, - } - - isTimely := cs.Proposal.IsTimely(cs.ProposalReceiveTime, tp, cs.Round) + sp := cs.state.ConsensusParams.Synchrony.SynchronyParamsOrDefaults() + isTimely := cs.Proposal.IsTimely(cs.ProposalReceiveTime, sp, cs.Round) cs.metrics.ProposalTimestampDifference.With("is_timely", fmt.Sprintf("%t", isTimely)). Observe(cs.ProposalReceiveTime.Sub(cs.Proposal.Timestamp).Seconds()) } diff --git a/internal/consensus/state_test.go b/internal/consensus/state_test.go index 9e67adf31..93aa4a49d 100644 --- a/internal/consensus/state_test.go +++ b/internal/consensus/state_test.go @@ -3,7 +3,6 @@ package consensus import ( "bytes" "context" - "fmt" "testing" "time" @@ -12,9 +11,9 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/abci/example/kvstore" - abcitypes "github.com/tendermint/tendermint/abci/types" + abci "github.com/tendermint/tendermint/abci/types" abcimocks "github.com/tendermint/tendermint/abci/types/mocks" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" cstypes "github.com/tendermint/tendermint/internal/consensus/types" "github.com/tendermint/tendermint/internal/eventbus" tmpubsub "github.com/tendermint/tendermint/internal/pubsub" @@ -164,7 +163,7 @@ func TestStateEnterProposeNoPrivValidator(t *testing.T) { startTestRound(ctx, cs, height, round) // if we're not a validator, EnterPropose should timeout - ensureNewTimeout(t, timeoutCh, height, round, cs.config.TimeoutPropose.Nanoseconds()) + ensureNewTimeout(t, timeoutCh, height, round, cs.state.ConsensusParams.Timeout.ProposeTimeout(round).Nanoseconds()) if cs.GetRoundState().Proposal != nil { t.Error("Expected to make no proposal, since no privValidator") @@ -203,7 +202,7 @@ func TestStateEnterProposeYesPrivValidator(t *testing.T) { } // if we're a validator, enterPropose should not timeout - ensureNoNewTimeout(t, timeoutCh, cs.config.TimeoutPropose.Nanoseconds()) + ensureNoNewTimeout(t, timeoutCh, cs.state.ConsensusParams.Timeout.ProposeTimeout(round).Nanoseconds()) } func TestStateBadProposal(t *testing.T) { @@ -220,7 +219,7 @@ func TestStateBadProposal(t *testing.T) { proposalCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryCompleteProposal) voteCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryVote) - propBlock, _, err := cs1.createProposalBlock(ctx) // changeProposer(t, cs1, vs2) + propBlock, err := cs1.createProposalBlock(ctx) // changeProposer(t, cs1, vs2) require.NoError(t, err) // make the second validator the proposer by incrementing round @@ -282,7 +281,7 @@ func TestStateOversizedBlock(t *testing.T) { timeoutProposeCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryTimeoutPropose) voteCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryVote) - propBlock, _, err := cs1.createProposalBlock(ctx) + propBlock, err := cs1.createProposalBlock(ctx) require.NoError(t, err) propBlock.Data.Txs = []types.Tx{tmrand.Bytes(2001)} propBlock.Header.DataHash = propBlock.Data.Hash() @@ -312,11 +311,9 @@ func TestStateOversizedBlock(t *testing.T) { // start the machine startTestRound(ctx, cs1, height, round) - t.Log("Block Sizes", "Limit", cs1.state.ConsensusParams.Block.MaxBytes, "Current", totalBytes) - // c1 should log an error with the block part message as it exceeds the consensus params. The // block is not added to cs.ProposalBlock so the node timeouts. - ensureNewTimeout(t, timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds()) + ensureNewTimeout(t, timeoutProposeCh, height, round, cs1.proposeTimeout(round).Nanoseconds()) // and then should send nil prevote and precommit regardless of whether other validators prevote and // precommit on it @@ -484,13 +481,12 @@ func TestStateLock_NoPOL(t *testing.T) { // (note we're entering precommit for a second time this round) // but with invalid args. then we enterPrecommitWait, and the timeout to new round - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) /// round++ // moving to the next round ensureNewRound(t, newRoundCh, height, round) - t.Log("#### ONTO ROUND 1") /* Round2 (cs1, B) // B B2 */ @@ -498,7 +494,7 @@ func TestStateLock_NoPOL(t *testing.T) { incrementRound(vs2) // now we're on a new round and not the proposer, so wait for timeout - ensureNewTimeout(t, timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds()) + ensureNewTimeout(t, timeoutProposeCh, height, round, cs1.proposeTimeout(round).Nanoseconds()) rs := cs1.GetRoundState() @@ -517,7 +513,7 @@ func TestStateLock_NoPOL(t *testing.T) { // now we're going to enter prevote again, but with invalid args // and then prevote wait, which should timeout. then wait for precommit - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Prevote(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) // the proposed block should still be locked block. // we should precommit nil and be locked on the proposal. ensurePrecommit(t, voteCh, height, round) @@ -529,11 +525,10 @@ func TestStateLock_NoPOL(t *testing.T) { // (note we're entering precommit for a second time this round, but with invalid args // then we enterPrecommitWait and timeout into NewRound - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) round++ // entering new round ensureNewRound(t, newRoundCh, height, round) - t.Log("#### ONTO ROUND 2") /* Round3 (vs2, _) // B, B2 */ @@ -557,7 +552,7 @@ func TestStateLock_NoPOL(t *testing.T) { signAddVotes(ctx, t, cs1, tmproto.PrevoteType, config.ChainID(), newBlockID, vs2) ensurePrevote(t, voteCh, height, round) - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Prevote(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) ensurePrecommit(t, voteCh, height, round) // precommit validatePrecommit(ctx, t, cs1, round, 0, vss[0], nil, initialBlockID.Hash) // precommit nil but be locked on proposal @@ -572,7 +567,7 @@ func TestStateLock_NoPOL(t *testing.T) { vs2) // NOTE: conflicting precommits at same height ensurePrecommit(t, voteCh, height, round) - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) // cs1 is locked on a block at this point, so we must generate a new consensus // state to force a new proposal block to be generated. @@ -590,7 +585,6 @@ func TestStateLock_NoPOL(t *testing.T) { round++ // entering new round ensureNewRound(t, newRoundCh, height, round) - t.Log("#### ONTO ROUND 3") /* Round4 (vs2, C) // B C // B C */ @@ -612,7 +606,7 @@ func TestStateLock_NoPOL(t *testing.T) { signAddVotes(ctx, t, cs1, tmproto.PrevoteType, config.ChainID(), propBlockID, vs2) ensurePrevote(t, voteCh, height, round) - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Prevote(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) ensurePrecommit(t, voteCh, height, round) validatePrecommit(ctx, t, cs1, round, 0, vss[0], nil, initialBlockID.Hash) // precommit nil but locked on proposal @@ -662,7 +656,6 @@ func TestStateLock_POLUpdateLock(t *testing.T) { This ensures that cs1 will lock on B in this round but not precommit it. */ - t.Log("### Starting Round 0") // start round and wait for propose and prevote startTestRound(ctx, cs1, height, round) @@ -690,7 +683,7 @@ func TestStateLock_POLUpdateLock(t *testing.T) { signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), types.BlockID{}, vs2, vs3, vs4) // timeout to new round. - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) /* Round 1: @@ -700,7 +693,6 @@ func TestStateLock_POLUpdateLock(t *testing.T) { Check that cs1 is now locked on the new block, D and no longer on the old block. */ - t.Log("### Starting Round 1") incrementRound(vs2, vs3, vs4) round++ @@ -769,7 +761,6 @@ func TestStateLock_POLRelock(t *testing.T) { Send a precommit for nil from all of the validators to cs1. This ensures that cs1 will lock on B in this round but not precommit it. */ - t.Log("### Starting Round 0") startTestRound(ctx, cs1, height, round) @@ -798,7 +789,7 @@ func TestStateLock_POLRelock(t *testing.T) { signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), types.BlockID{}, vs2, vs3, vs4) // timeout to new round. - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) /* Round 1: @@ -808,7 +799,6 @@ func TestStateLock_POLRelock(t *testing.T) { Check that cs1 updates its 'locked round' value to the current round. */ - t.Log("### Starting Round 1") incrementRound(vs2, vs3, vs4) round++ propR1 := types.NewProposal(height, round, cs1.ValidRound, blockID, theBlock.Header.Time) @@ -868,7 +858,6 @@ func TestStateLock_PrevoteNilWhenLockedAndMissProposal(t *testing.T) { This ensures that cs1 will lock on B in this round but not precommit it. */ - t.Log("### Starting Round 0") startTestRound(ctx, cs1, height, round) @@ -895,7 +884,7 @@ func TestStateLock_PrevoteNilWhenLockedAndMissProposal(t *testing.T) { signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), types.BlockID{}, vs2, vs3, vs4) // timeout to new round. - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) /* Round 1: @@ -905,7 +894,6 @@ func TestStateLock_PrevoteNilWhenLockedAndMissProposal(t *testing.T) { Check that cs1 prevotes nil instead of its locked block, but ensure that it maintains its locked block. */ - t.Log("### Starting Round 1") incrementRound(vs2, vs3, vs4) round++ @@ -957,7 +945,6 @@ func TestStateLock_PrevoteNilWhenLockedAndDifferentProposal(t *testing.T) { This ensures that cs1 will lock on B in this round but not precommit it. */ - t.Log("### Starting Round 0") startTestRound(ctx, cs1, height, round) ensureNewRound(t, newRoundCh, height, round) @@ -983,7 +970,7 @@ func TestStateLock_PrevoteNilWhenLockedAndDifferentProposal(t *testing.T) { signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), types.BlockID{}, vs2, vs3, vs4) // timeout to new round. - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) /* Round 1: @@ -994,7 +981,6 @@ func TestStateLock_PrevoteNilWhenLockedAndDifferentProposal(t *testing.T) { Check that cs1 prevotes nil instead of its locked block, but ensure that it maintains its locked block. */ - t.Log("### Starting Round 1") incrementRound(vs2, vs3, vs4) round++ cs2 := newState(ctx, t, logger, cs1.state, vs2, kvstore.NewApplication()) @@ -1059,7 +1045,6 @@ func TestStateLock_POLDoesNotUnlock(t *testing.T) { This ensures that cs1 will lock on B in this round. */ - t.Log("#### ONTO ROUND 0") // start round and wait for propose and prevote startTestRound(ctx, cs1, height, round) @@ -1093,7 +1078,7 @@ func TestStateLock_POLDoesNotUnlock(t *testing.T) { signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), blockID, vs3) // timeout to new round - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) /* Round 1: @@ -1101,7 +1086,6 @@ func TestStateLock_POLDoesNotUnlock(t *testing.T) { Check that cs1 maintains its lock on B but precommits nil. Send a precommit for nil from >2/3 of the validators to `cs1`. */ - t.Log("#### ONTO ROUND 1") round++ incrementRound(vs2, vs3, vs4) cs2 := newState(ctx, t, logger, cs1.state, vs2, kvstore.NewApplication()) @@ -1128,7 +1112,7 @@ func TestStateLock_POLDoesNotUnlock(t *testing.T) { validatePrecommit(ctx, t, cs1, round, 0, vss[0], nil, blockID.Hash) signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), types.BlockID{}, vs2, vs3, vs4) - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) /* Round 2: @@ -1136,7 +1120,6 @@ func TestStateLock_POLDoesNotUnlock(t *testing.T) { Send the validator >2/3 prevotes for nil and ensure that it did not unlock its block at the end of the previous round. */ - t.Log("#### ONTO ROUND 2") round++ incrementRound(vs2, vs3, vs4) cs3 := newState(ctx, t, logger, cs1.state, vs2, kvstore.NewApplication()) @@ -1193,7 +1176,6 @@ func TestStateLock_MissingProposalWhenPOLSeenDoesNotUpdateLock(t *testing.T) { This ensures that cs1 will lock on B in this round but not precommit it. */ - t.Log("### Starting Round 0") startTestRound(ctx, cs1, height, round) ensureNewRound(t, newRoundCh, height, round) @@ -1216,7 +1198,7 @@ func TestStateLock_MissingProposalWhenPOLSeenDoesNotUpdateLock(t *testing.T) { signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), types.BlockID{}, vs2, vs3, vs4) // timeout to new round - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) /* Round 1: @@ -1225,7 +1207,6 @@ func TestStateLock_MissingProposalWhenPOLSeenDoesNotUpdateLock(t *testing.T) { Check that cs1 does not update its locked block to this missed block D. */ - t.Log("### Starting Round 1") incrementRound(vs2, vs3, vs4) round++ cs2 := newState(ctx, t, logger, cs1.state, vs2, kvstore.NewApplication()) @@ -1281,7 +1262,6 @@ func TestStateLock_DoesNotLockOnOldProposal(t *testing.T) { This ensures that cs1 will not lock on B. */ - t.Log("### Starting Round 0") startTestRound(ctx, cs1, height, round) ensureNewRound(t, newRoundCh, height, round) @@ -1305,7 +1285,7 @@ func TestStateLock_DoesNotLockOnOldProposal(t *testing.T) { incrementRound(vs2, vs3, vs4) // timeout to new round - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) /* Round 1: @@ -1315,7 +1295,6 @@ func TestStateLock_DoesNotLockOnOldProposal(t *testing.T) { cs1 saw a POL for the block it saw in round 0. We ensure that it does not lock on this block, since it did not see a proposal for it in this round. */ - t.Log("### Starting Round 1") round++ ensureNewRound(t, newRoundCh, height, round) @@ -1377,9 +1356,8 @@ func TestStateLock_POLSafety1(t *testing.T) { // cs1 precommit nil ensurePrecommit(t, voteCh, height, round) - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) - t.Log("### ONTO ROUND 1") incrementRound(vs2, vs3, vs4) round++ // moving to the next round cs2 := newState(ctx, t, logger, cs1.state, vs2, kvstore.NewApplication()) @@ -1407,8 +1385,6 @@ func TestStateLock_POLSafety1(t *testing.T) { require.Nil(t, rs.LockedBlock, "we should not be locked!") - t.Logf("new prop hash %v", fmt.Sprintf("%X", propBlock.Hash())) - // go to prevote, prevote for proposal block ensurePrevoteMatch(t, voteCh, height, round, r2BlockID.Hash) @@ -1421,20 +1397,19 @@ func TestStateLock_POLSafety1(t *testing.T) { signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), types.BlockID{}, vs2, vs3, vs4) - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) incrementRound(vs2, vs3, vs4) round++ // moving to the next round ensureNewRound(t, newRoundCh, height, round) - t.Log("### ONTO ROUND 2") /*Round3 we see the polka from round 1 but we shouldn't unlock! */ // timeout of propose - ensureNewTimeout(t, timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds()) + ensureNewTimeout(t, timeoutProposeCh, height, round, cs1.proposeTimeout(round).Nanoseconds()) // finish prevote ensurePrevoteMatch(t, voteCh, height, round, nil) @@ -1494,7 +1469,7 @@ func TestStateLock_POLSafety2(t *testing.T) { incrementRound(vs2, vs3, vs4) round++ // moving to the next round - t.Log("### ONTO Round 1") + // jump in at round 1 startTestRound(ctx, cs1, height, round) ensureNewRound(t, newRoundCh, height, round) @@ -1518,7 +1493,7 @@ func TestStateLock_POLSafety2(t *testing.T) { incrementRound(vs2, vs3, vs4) // timeout of precommit wait to new round - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) round++ // moving to the next round // in round 2 we see the polkad block from round 0 @@ -1536,7 +1511,7 @@ func TestStateLock_POLSafety2(t *testing.T) { addVotes(cs1, prevotes...) ensureNewRound(t, newRoundCh, height, round) - t.Log("### ONTO Round 2") + /*Round2 // now we see the polka from round 1, but we shouldnt unlock */ @@ -1579,7 +1554,6 @@ func TestState_PrevotePOLFromPreviousRound(t *testing.T) { This ensures that cs1 will lock on B in this round but not precommit it. */ - t.Log("### Starting Round 0") startTestRound(ctx, cs1, height, round) @@ -1606,7 +1580,7 @@ func TestState_PrevotePOLFromPreviousRound(t *testing.T) { signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), types.BlockID{}, vs2, vs3, vs4) // timeout to new round. - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) /* Round 1: @@ -1617,14 +1591,16 @@ func TestState_PrevotePOLFromPreviousRound(t *testing.T) { cs1 has now seen greater than 2/3 of the voting power prevote D in this round but cs1 did not see the proposal for D in this round so it will not prevote or precommit it. */ - t.Log("### Starting Round 1") + incrementRound(vs2, vs3, vs4) round++ // Generate a new proposal block. cs2 := newState(ctx, t, logger, cs1.state, vs2, kvstore.NewApplication()) cs2.ValidRound = 1 propR1, propBlockR1 := decideProposal(ctx, t, cs2, vs2, vs2.Height, round) - t.Log(propR1.POLRound) + + assert.EqualValues(t, 1, propR1.POLRound) + propBlockR1Parts, err := propBlockR1.MakePartSet(partSize) require.NoError(t, err) r1BlockID := types.BlockID{ @@ -1645,7 +1621,7 @@ func TestState_PrevotePOLFromPreviousRound(t *testing.T) { ensurePrecommit(t, voteCh, height, round) // timeout to new round. - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) /* Create a new proposal for D, the same block from Round 1. @@ -1658,7 +1634,6 @@ func TestState_PrevotePOLFromPreviousRound(t *testing.T) { Send cs1 prevotes for nil and check that it still prevotes its locked block and not the block that it prevoted. */ - t.Log("### Starting Round 2") incrementRound(vs2, vs3, vs4) round++ propR2 := types.NewProposal(height, round, 1, r1BlockID, propBlockR1.Header.Time) @@ -1739,16 +1714,15 @@ func TestProposeValidBlock(t *testing.T) { signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), types.BlockID{}, vs2, vs3, vs4) - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) incrementRound(vs2, vs3, vs4) round++ // moving to the next round ensureNewRound(t, newRoundCh, height, round) - t.Log("### ONTO ROUND 1") // timeout of propose - ensureNewTimeout(t, timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds()) + ensureNewTimeout(t, timeoutProposeCh, height, round, cs1.proposeTimeout(round).Nanoseconds()) // We did not see a valid proposal within this round, so prevote nil. ensurePrevoteMatch(t, voteCh, height, round, nil) @@ -1768,9 +1742,8 @@ func TestProposeValidBlock(t *testing.T) { round += 2 // increment by multiple rounds ensureNewRound(t, newRoundCh, height, round) - t.Log("### ONTO ROUND 3") - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) round++ // moving to the next round @@ -1829,7 +1802,7 @@ func TestSetValidBlockOnDelayedPrevote(t *testing.T) { // vs3 send prevote nil signAddVotes(ctx, t, cs1, tmproto.PrevoteType, config.ChainID(), types.BlockID{}, vs3) - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Prevote(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) ensurePrecommit(t, voteCh, height, round) // we should have precommitted @@ -1883,7 +1856,7 @@ func TestSetValidBlockOnDelayedProposal(t *testing.T) { startTestRound(ctx, cs1, cs1.Height, round) ensureNewRound(t, newRoundCh, height, round) - ensureNewTimeout(t, timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds()) + ensureNewTimeout(t, timeoutProposeCh, height, round, cs1.proposeTimeout(round).Nanoseconds()) ensurePrevoteMatch(t, voteCh, height, round, nil) @@ -1899,7 +1872,7 @@ func TestSetValidBlockOnDelayedProposal(t *testing.T) { signAddVotes(ctx, t, cs1, tmproto.PrevoteType, config.ChainID(), blockID, vs2, vs3, vs4) ensureNewValidBlock(t, validBlockCh, height, round) - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Prevote(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) ensurePrecommit(t, voteCh, height, round) validatePrecommit(ctx, t, cs1, round, -1, vss[0], nil, nil) @@ -1939,8 +1912,13 @@ func TestProcessProposalAccept(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - m := abcimocks.NewBaseMock() - m.On("ProcessProposal", mock.Anything).Return(abcitypes.ResponseProcessProposal{Accept: testCase.accept}) + m := abcimocks.NewApplication(t) + status := abci.ResponseProcessProposal_REJECT + if testCase.accept { + status = abci.ResponseProcessProposal_ACCEPT + } + m.On("ProcessProposal", mock.Anything, mock.Anything).Return(&abci.ResponseProcessProposal{Status: status}, nil) + m.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{}, nil).Maybe() cs1, _ := makeState(ctx, t, makeStateArgs{config: config, application: m}) height, round := cs1.Height, cs1.Round @@ -1987,12 +1965,18 @@ func TestFinalizeBlockCalled(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - m := abcimocks.NewBaseMock() - m.On("ProcessProposal", mock.Anything).Return(abcitypes.ResponseProcessProposal{Accept: true}) - m.On("VerifyVoteExtension", mock.Anything).Return(abcitypes.ResponseVerifyVoteExtension{ - Result: abcitypes.ResponseVerifyVoteExtension_ACCEPT, - }) - m.On("FinalizeBlock", mock.Anything).Return(abcitypes.ResponseFinalizeBlock{}).Maybe() + m := abcimocks.NewApplication(t) + m.On("ProcessProposal", mock.Anything, mock.Anything).Return(&abci.ResponseProcessProposal{ + Status: abci.ResponseProcessProposal_ACCEPT, + }, nil) + m.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{}, nil) + m.On("VerifyVoteExtension", mock.Anything, mock.Anything).Return(&abci.ResponseVerifyVoteExtension{ + Status: abci.ResponseVerifyVoteExtension_ACCEPT, + }, nil) + m.On("FinalizeBlock", mock.Anything, mock.Anything).Return(&abci.ResponseFinalizeBlock{}, nil).Maybe() + m.On("ExtendVote", mock.Anything, mock.Anything).Return(&abci.ResponseExtendVote{}, nil) + m.On("Commit", mock.Anything).Return(&abci.ResponseCommit{}, nil).Maybe() + cs1, vss := makeState(ctx, t, makeStateArgs{config: config, application: m}) height, round := cs1.Height, cs1.Round @@ -2030,14 +2014,254 @@ func TestFinalizeBlockCalled(t *testing.T) { m.AssertExpectations(t) if !testCase.expectCalled { - m.AssertNotCalled(t, "FinalizeBlock", mock.Anything) + m.AssertNotCalled(t, "FinalizeBlock", ctx, mock.Anything) } else { - m.AssertCalled(t, "FinalizeBlock", mock.Anything) + m.AssertCalled(t, "FinalizeBlock", ctx, mock.Anything) } }) } } +// TestExtendVoteCalled tests that the vote extension methods are called at the +// correct point in the consensus algorithm. +func TestExtendVoteCalled(t *testing.T) { + config := configSetup(t) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + m := abcimocks.NewApplication(t) + m.On("ProcessProposal", mock.Anything, mock.Anything).Return(&abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil) + m.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{}, nil) + m.On("ExtendVote", mock.Anything, mock.Anything).Return(&abci.ResponseExtendVote{ + VoteExtension: []byte("extension"), + }, nil) + m.On("VerifyVoteExtension", mock.Anything, mock.Anything).Return(&abci.ResponseVerifyVoteExtension{ + Status: abci.ResponseVerifyVoteExtension_ACCEPT, + }, nil) + m.On("Commit", mock.Anything).Return(&abci.ResponseCommit{}, nil).Maybe() + m.On("FinalizeBlock", mock.Anything, mock.Anything).Return(&abci.ResponseFinalizeBlock{}, nil).Maybe() + cs1, vss := makeState(ctx, t, makeStateArgs{config: config, application: m}) + height, round := cs1.Height, cs1.Round + + proposalCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryCompleteProposal) + newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound) + pv1, err := cs1.privValidator.GetPubKey(ctx) + require.NoError(t, err) + addr := pv1.Address() + voteCh := subscribeToVoter(ctx, t, cs1, addr) + + startTestRound(ctx, cs1, cs1.Height, round) + ensureNewRound(t, newRoundCh, height, round) + ensureNewProposal(t, proposalCh, height, round) + + m.AssertNotCalled(t, "ExtendVote", mock.Anything, mock.Anything) + + rs := cs1.GetRoundState() + + blockID := types.BlockID{ + Hash: rs.ProposalBlock.Hash(), + PartSetHeader: rs.ProposalBlockParts.Header(), + } + signAddVotes(ctx, t, cs1, tmproto.PrevoteType, config.ChainID(), blockID, vss[1:]...) + ensurePrevoteMatch(t, voteCh, height, round, blockID.Hash) + + ensurePrecommit(t, voteCh, height, round) + + m.AssertCalled(t, "ExtendVote", ctx, &abci.RequestExtendVote{ + Height: height, + Hash: blockID.Hash, + }) + + m.AssertCalled(t, "VerifyVoteExtension", ctx, &abci.RequestVerifyVoteExtension{ + Hash: blockID.Hash, + ValidatorAddress: addr, + Height: height, + VoteExtension: []byte("extension"), + }) + signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), blockID, vss[1:]...) + ensureNewRound(t, newRoundCh, height+1, 0) + m.AssertExpectations(t) + + // Only 3 of the vote extensions are seen, as consensus proceeds as soon as the +2/3 threshold + // is observed by the consensus engine. + for _, pv := range vss[:3] { + pv, err := pv.GetPubKey(ctx) + require.NoError(t, err) + addr := pv.Address() + m.AssertCalled(t, "VerifyVoteExtension", ctx, &abci.RequestVerifyVoteExtension{ + Hash: blockID.Hash, + ValidatorAddress: addr, + Height: height, + VoteExtension: []byte("extension"), + }) + } + +} + +// TestVerifyVoteExtensionNotCalledOnAbsentPrecommit tests that the VerifyVoteExtension +// method is not called for a validator's vote that is never delivered. +func TestVerifyVoteExtensionNotCalledOnAbsentPrecommit(t *testing.T) { + config := configSetup(t) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + m := abcimocks.NewApplication(t) + m.On("ProcessProposal", mock.Anything, mock.Anything).Return(&abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil) + m.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{}, nil) + m.On("ExtendVote", mock.Anything, mock.Anything).Return(&abci.ResponseExtendVote{ + VoteExtension: []byte("extension"), + }, nil) + m.On("VerifyVoteExtension", mock.Anything, mock.Anything).Return(&abci.ResponseVerifyVoteExtension{ + Status: abci.ResponseVerifyVoteExtension_ACCEPT, + }, nil) + m.On("FinalizeBlock", mock.Anything, mock.Anything).Return(&abci.ResponseFinalizeBlock{}, nil).Maybe() + cs1, vss := makeState(ctx, t, makeStateArgs{config: config, application: m}) + height, round := cs1.Height, cs1.Round + + proposalCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryCompleteProposal) + newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound) + pv1, err := cs1.privValidator.GetPubKey(ctx) + require.NoError(t, err) + addr := pv1.Address() + voteCh := subscribeToVoter(ctx, t, cs1, addr) + + startTestRound(ctx, cs1, cs1.Height, round) + ensureNewRound(t, newRoundCh, height, round) + ensureNewProposal(t, proposalCh, height, round) + rs := cs1.GetRoundState() + + blockID := types.BlockID{ + Hash: rs.ProposalBlock.Hash(), + PartSetHeader: rs.ProposalBlockParts.Header(), + } + signAddVotes(ctx, t, cs1, tmproto.PrevoteType, config.ChainID(), blockID, vss[2:]...) + ensurePrevoteMatch(t, voteCh, height, round, blockID.Hash) + + ensurePrecommit(t, voteCh, height, round) + + m.AssertCalled(t, "ExtendVote", mock.Anything, &abci.RequestExtendVote{ + Height: height, + Hash: blockID.Hash, + }) + + m.AssertCalled(t, "VerifyVoteExtension", mock.Anything, &abci.RequestVerifyVoteExtension{ + Hash: blockID.Hash, + ValidatorAddress: addr, + Height: height, + VoteExtension: []byte("extension"), + }) + + m.On("Commit", mock.Anything).Return(&abci.ResponseCommit{}, nil).Maybe() + signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), blockID, vss[2:]...) + ensureNewRound(t, newRoundCh, height+1, 0) + m.AssertExpectations(t) + + // vss[1] did not issue a precommit for the block, ensure that a vote extension + // for its address was not sent to the application. + pv, err := vss[1].GetPubKey(ctx) + require.NoError(t, err) + addr = pv.Address() + + m.AssertNotCalled(t, "VerifyVoteExtension", ctx, &abci.RequestVerifyVoteExtension{ + Hash: blockID.Hash, + ValidatorAddress: addr, + Height: height, + VoteExtension: []byte("extension"), + }) + +} + +// TestPrepareProposalReceivesVoteExtensions tests that the PrepareProposal method +// is called with the vote extensions from the previous height. The test functions +// be completing a consensus height with a mock application as the proposer. The +// test then proceeds to fail sever rounds of consensus until the mock application +// is the proposer again and ensures that the mock application receives the set of +// vote extensions from the previous consensus instance. +func TestPrepareProposalReceivesVoteExtensions(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + config := configSetup(t) + + // create a list of vote extensions, one for each validator. + voteExtensions := [][]byte{ + []byte("extension 0"), + []byte("extension 1"), + []byte("extension 2"), + []byte("extension 3"), + } + + // m := abcimocks.NewApplication(t) + m := &abcimocks.Application{} + m.On("ExtendVote", mock.Anything, mock.Anything).Return(&abci.ResponseExtendVote{ + VoteExtension: voteExtensions[0], + }, nil) + m.On("ProcessProposal", mock.Anything, mock.Anything).Return(&abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil) + + // capture the prepare proposal request. + rpp := &abci.RequestPrepareProposal{} + m.On("PrepareProposal", mock.Anything, mock.MatchedBy(func(r *abci.RequestPrepareProposal) bool { + rpp = r + return true + })).Return(&abci.ResponsePrepareProposal{}, nil) + + m.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{}, nil).Once() + m.On("VerifyVoteExtension", mock.Anything, mock.Anything).Return(&abci.ResponseVerifyVoteExtension{Status: abci.ResponseVerifyVoteExtension_ACCEPT}, nil) + m.On("Commit", mock.Anything).Return(&abci.ResponseCommit{}, nil).Maybe() + m.On("FinalizeBlock", mock.Anything, mock.Anything).Return(&abci.ResponseFinalizeBlock{}, nil) + + cs1, vss := makeState(ctx, t, makeStateArgs{config: config, application: m}) + height, round := cs1.Height, cs1.Round + + newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound) + proposalCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryCompleteProposal) + pv1, err := cs1.privValidator.GetPubKey(ctx) + require.NoError(t, err) + addr := pv1.Address() + voteCh := subscribeToVoter(ctx, t, cs1, addr) + + startTestRound(ctx, cs1, height, round) + ensureNewRound(t, newRoundCh, height, round) + ensureNewProposal(t, proposalCh, height, round) + + rs := cs1.GetRoundState() + blockID := types.BlockID{ + Hash: rs.ProposalBlock.Hash(), + PartSetHeader: rs.ProposalBlockParts.Header(), + } + signAddVotes(ctx, t, cs1, tmproto.PrevoteType, config.ChainID(), blockID, vss[1:]...) + + // create a precommit for each validator with the associated vote extension. + for i, vs := range vss[1:] { + signAddPrecommitWithExtension(ctx, t, cs1, config.ChainID(), blockID, voteExtensions[i+1], vs) + } + + ensurePrevote(t, voteCh, height, round) + + // ensure that the height is committed. + ensurePrecommitMatch(t, voteCh, height, round, blockID.Hash) + incrementHeight(vss[1:]...) + + height++ + round = 0 + ensureNewRound(t, newRoundCh, height, round) + incrementRound(vss[1:]...) + incrementRound(vss[1:]...) + incrementRound(vss[1:]...) + round = 3 + + signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), types.BlockID{}, vss[1:]...) + ensureNewRound(t, newRoundCh, height, round) + ensureNewProposal(t, proposalCh, height, round) + + // ensure that the proposer received the list of vote extensions from the + // previous height. + require.Len(t, rpp.LocalLastCommit.Votes, len(vss)) + for i := range vss { + require.Equal(t, rpp.LocalLastCommit.Votes[i].VoteExtension, voteExtensions[i]) + } +} + // 4 vals, 3 Nil Precommits at P0 // What we want: // P0 waits for timeoutPrecommit before starting next round @@ -2059,7 +2283,7 @@ func TestWaitingTimeoutOnNilPolka(t *testing.T) { signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), types.BlockID{}, vs2, vs3, vs4) - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) ensureNewRound(t, newRoundCh, height, round+1) } @@ -2097,7 +2321,7 @@ func TestWaitingTimeoutProposeOnNewRound(t *testing.T) { rs := cs1.GetRoundState() assert.True(t, rs.Step == cstypes.RoundStepPropose) // P0 does not prevote before timeoutPropose expires - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Propose(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.proposeTimeout(round).Nanoseconds()) ensurePrevoteMatch(t, voteCh, height, round, nil) } @@ -2136,7 +2360,7 @@ func TestRoundSkipOnNilPolkaFromHigherRound(t *testing.T) { ensurePrecommit(t, voteCh, height, round) validatePrecommit(ctx, t, cs1, round, -1, vss[0], nil, nil) - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) round++ // moving to the next round ensureNewRound(t, newRoundCh, height, round) @@ -2168,7 +2392,7 @@ func TestWaitTimeoutProposeOnNilPolkaForTheCurrentRound(t *testing.T) { incrementRound(vss[1:]...) signAddVotes(ctx, t, cs1, tmproto.PrevoteType, config.ChainID(), types.BlockID{}, vs2, vs3, vs4) - ensureNewTimeout(t, timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds()) + ensureNewTimeout(t, timeoutProposeCh, height, round, cs1.proposeTimeout(round).Nanoseconds()) ensurePrevoteMatch(t, voteCh, height, round, nil) } @@ -2283,8 +2507,8 @@ func TestStartNextHeightCorrectlyAfterTimeout(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - config.Consensus.SkipTimeoutCommit = false cs1, vss := makeState(ctx, t, makeStateArgs{config: config}) + cs1.state.ConsensusParams.Timeout.BypassCommitTimeout = false cs1.txNotifier = &fakeTxNotifier{ch: make(chan struct{})} vs2, vs3, vs4 := vss[1], vss[2], vss[3] @@ -2325,7 +2549,7 @@ func TestStartNextHeightCorrectlyAfterTimeout(t *testing.T) { signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), blockID, vs3) // wait till timeout occurs - ensureNewTimeout(t, precommitTimeoutCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) + ensureNewTimeout(t, precommitTimeoutCh, height, round, cs1.voteTimeout(round).Nanoseconds()) ensureNewRound(t, newRoundCh, height, round+1) @@ -2336,7 +2560,7 @@ func TestStartNextHeightCorrectlyAfterTimeout(t *testing.T) { cs1.txNotifier.(*fakeTxNotifier).Notify() - ensureNewTimeout(t, timeoutProposeCh, height+1, round, cs1.config.Propose(round).Nanoseconds()) + ensureNewTimeout(t, timeoutProposeCh, height+1, round, cs1.proposeTimeout(round).Nanoseconds()) rs = cs1.GetRoundState() assert.False( t, @@ -2349,8 +2573,8 @@ func TestResetTimeoutPrecommitUponNewHeight(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - config.Consensus.SkipTimeoutCommit = false cs1, vss := makeState(ctx, t, makeStateArgs{config: config}) + cs1.state.ConsensusParams.Timeout.BypassCommitTimeout = false vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -2464,13 +2688,12 @@ func TestStateHalt1(t *testing.T) { incrementRound(vs2, vs3, vs4) // timeout to new round - ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) + ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.voteTimeout(round).Nanoseconds()) round++ // moving to the next round ensureNewRound(t, newRoundCh, height, round) - t.Log("### ONTO ROUND 1") /*Round2 // we timeout and prevote // a polka happened but we didn't see it! @@ -2546,7 +2769,7 @@ func TestStateOutputVoteStats(t *testing.T) { peerID, err := types.NewNodeID("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") require.NoError(t, err) - randBytes := tmrand.Bytes(tmhash.Size) + randBytes := tmrand.Bytes(crypto.HashSize) blockID := types.BlockID{ Hash: randBytes, } @@ -2584,7 +2807,7 @@ func TestSignSameVoteTwice(t *testing.T) { _, vss := makeState(ctx, t, makeStateArgs{config: config, validators: 2}) - randBytes := tmrand.Bytes(tmhash.Size) + randBytes := tmrand.Bytes(crypto.HashSize) vote := signVote( ctx, @@ -2632,7 +2855,7 @@ func TestStateTimestamp_ProposalNotMatch(t *testing.T) { addr := pv1.Address() voteCh := subscribeToVoter(ctx, t, cs1, addr) - propBlock, _, err := cs1.createProposalBlock(ctx) + propBlock, err := cs1.createProposalBlock(ctx) require.NoError(t, err) round++ incrementRound(vss[1:]...) @@ -2680,7 +2903,7 @@ func TestStateTimestamp_ProposalMatch(t *testing.T) { addr := pv1.Address() voteCh := subscribeToVoter(ctx, t, cs1, addr) - propBlock, _, err := cs1.createProposalBlock(ctx) + propBlock, err := cs1.createProposalBlock(ctx) require.NoError(t, err) round++ incrementRound(vss[1:]...) @@ -2743,3 +2966,15 @@ func subscribe( }() return ch } + +func signAddPrecommitWithExtension(ctx context.Context, + t *testing.T, + cs *State, + chainID string, + blockID types.BlockID, + extension []byte, + stub *validatorStub) { + v, err := stub.signVote(ctx, tmproto.PrecommitType, chainID, blockID, extension) + require.NoError(t, err, "failed to sign vote") + addVotes(cs, v) +} diff --git a/internal/consensus/types/height_vote_set.go b/internal/consensus/types/height_vote_set.go index dd8630725..661c5120e 100644 --- a/internal/consensus/types/height_vote_set.go +++ b/internal/consensus/types/height_vote_set.go @@ -199,7 +199,7 @@ func (hvs *HeightVoteSet) SetPeerMaj23( if voteSet == nil { return nil // something we don't know about yet } - return voteSet.SetPeerMaj23(types.P2PID(peerID), blockID) + return voteSet.SetPeerMaj23(string(peerID), blockID) } //--------------------------------------------------------- diff --git a/internal/consensus/types/height_vote_set_test.go b/internal/consensus/types/height_vote_set_test.go index 671c5f214..acffa794c 100644 --- a/internal/consensus/types/height_vote_set_test.go +++ b/internal/consensus/types/height_vote_set_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/config" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/internal/test/factory" tmrand "github.com/tendermint/tendermint/libs/rand" tmtime "github.com/tendermint/tendermint/libs/time" @@ -71,7 +71,7 @@ func makeVoteHR( pubKey, err := privVal.GetPubKey(ctx) require.NoError(t, err) - randBytes := tmrand.Bytes(tmhash.Size) + randBytes := tmrand.Bytes(crypto.HashSize) vote := &types.Vote{ ValidatorAddress: pubKey.Address(), @@ -88,6 +88,7 @@ func makeVoteHR( require.NoError(t, err, "Error signing vote") vote.Signature = v.Signature + vote.ExtensionSignature = v.ExtensionSignature return vote } diff --git a/internal/consensus/wal_generator.go b/internal/consensus/wal_generator.go index 9608365c1..0e678db3a 100644 --- a/internal/consensus/wal_generator.go +++ b/internal/consensus/wal_generator.go @@ -7,7 +7,6 @@ import ( "fmt" "io" mrand "math/rand" - "path/filepath" "testing" "time" @@ -26,18 +25,17 @@ import ( "github.com/tendermint/tendermint/types" ) -// WALGenerateNBlocks generates a consensus WAL. It does this by spinning up a -// stripped down version of node (proxy app, event bus, consensus state) with a -// persistent kvstore application and special consensus wal instance -// (byteBufferWAL) and waits until numBlocks are created. +// WALGenerateNBlocks generates a consensus WAL. It does this by +// spinning up a stripped down version of node (proxy app, event bus, +// consensus state) with a kvstore application and special consensus +// wal instance (byteBufferWAL) and waits until numBlocks are created. // If the node fails to produce given numBlocks, it fails the test. func WALGenerateNBlocks(ctx context.Context, t *testing.T, logger log.Logger, wr io.Writer, numBlocks int) { t.Helper() cfg := getConfig(t) - app := kvstore.NewPersistentKVStoreApplication(logger, filepath.Join(cfg.DBDir(), "wal_generator")) - t.Cleanup(func() { require.NoError(t, app.Close()) }) + app := kvstore.NewApplication() logger.Info("generating WAL (last height msg excluded)", "numBlocks", numBlocks) @@ -82,13 +80,12 @@ func WALGenerateNBlocks(ctx context.Context, t *testing.T, logger log.Logger, wr mempool := emptyMempool{} evpool := sm.EmptyEvidencePool{} - blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp, mempool, evpool, blockStore) - consensusState, err := NewState(ctx, logger, cfg.Consensus, stateStore, blockExec, blockStore, mempool, evpool) + blockExec := sm.NewBlockExecutor(stateStore, log.NewNopLogger(), proxyApp, mempool, evpool, blockStore, eventBus, sm.NopMetrics()) + consensusState, err := NewState(logger, cfg.Consensus, stateStore, blockExec, blockStore, mempool, evpool, eventBus) if err != nil { t.Fatal(err) } - consensusState.SetEventBus(eventBus) if privValidator != nil && privValidator != (*privval.FilePV)(nil) { consensusState.SetPrivValidator(ctx, privValidator) } diff --git a/internal/consensus/wal_test.go b/internal/consensus/wal_test.go index 169b7c327..3aec25093 100644 --- a/internal/consensus/wal_test.go +++ b/internal/consensus/wal_test.go @@ -26,7 +26,7 @@ const walTestFlushInterval = 100 * time.Millisecond func TestWALTruncate(t *testing.T) { walDir := t.TempDir() walFile := filepath.Join(walDir, "wal") - logger := log.TestingLogger() + logger := log.NewNopLogger() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -108,7 +108,7 @@ func TestWALWrite(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - wal, err := NewWAL(ctx, log.TestingLogger(), walFile) + wal, err := NewWAL(ctx, log.NewNopLogger(), walFile) require.NoError(t, err) err = wal.Start(ctx) require.NoError(t, err) @@ -177,7 +177,7 @@ func TestWALPeriodicSync(t *testing.T) { walFile := filepath.Join(walDir, "wal") defer os.RemoveAll(walFile) - wal, err := NewWAL(ctx, log.TestingLogger(), walFile, autofile.GroupCheckDuration(250*time.Millisecond)) + wal, err := NewWAL(ctx, log.NewNopLogger(), walFile, autofile.GroupCheckDuration(250*time.Millisecond)) require.NoError(t, err) wal.SetFlushInterval(walTestFlushInterval) diff --git a/internal/eventbus/event_bus.go b/internal/eventbus/event_bus.go index 1d2d510e3..c01c37de7 100644 --- a/internal/eventbus/event_bus.go +++ b/internal/eventbus/event_bus.go @@ -66,7 +66,7 @@ func (b *EventBus) Observe(ctx context.Context, observe func(tmpubsub.Message) e return b.pubsub.Observe(ctx, observe, queries...) } -func (b *EventBus) Publish(ctx context.Context, eventValue string, eventData types.EventData) error { +func (b *EventBus) Publish(eventValue string, eventData types.EventData) error { tokens := strings.Split(types.EventTypeKey, ".") event := abci.Event{ Type: tokens[0], @@ -78,19 +78,19 @@ func (b *EventBus) Publish(ctx context.Context, eventValue string, eventData typ }, } - return b.pubsub.PublishWithEvents(ctx, eventData, []abci.Event{event}) + return b.pubsub.PublishWithEvents(eventData, []abci.Event{event}) } -func (b *EventBus) PublishEventNewBlock(ctx context.Context, data types.EventDataNewBlock) error { +func (b *EventBus) PublishEventNewBlock(data types.EventDataNewBlock) error { events := data.ResultFinalizeBlock.Events // add Tendermint-reserved new block event events = append(events, types.EventNewBlock) - return b.pubsub.PublishWithEvents(ctx, data, events) + return b.pubsub.PublishWithEvents(data, events) } -func (b *EventBus) PublishEventNewBlockHeader(ctx context.Context, data types.EventDataNewBlockHeader) error { +func (b *EventBus) PublishEventNewBlockHeader(data types.EventDataNewBlockHeader) error { // no explicit deadline for publishing events events := data.ResultFinalizeBlock.Events @@ -98,33 +98,33 @@ func (b *EventBus) PublishEventNewBlockHeader(ctx context.Context, data types.Ev // add Tendermint-reserved new block header event events = append(events, types.EventNewBlockHeader) - return b.pubsub.PublishWithEvents(ctx, data, events) + return b.pubsub.PublishWithEvents(data, events) } -func (b *EventBus) PublishEventNewEvidence(ctx context.Context, evidence types.EventDataNewEvidence) error { - return b.Publish(ctx, types.EventNewEvidenceValue, evidence) +func (b *EventBus) PublishEventNewEvidence(evidence types.EventDataNewEvidence) error { + return b.Publish(types.EventNewEvidenceValue, evidence) } -func (b *EventBus) PublishEventVote(ctx context.Context, data types.EventDataVote) error { - return b.Publish(ctx, types.EventVoteValue, data) +func (b *EventBus) PublishEventVote(data types.EventDataVote) error { + return b.Publish(types.EventVoteValue, data) } -func (b *EventBus) PublishEventValidBlock(ctx context.Context, data types.EventDataRoundState) error { - return b.Publish(ctx, types.EventValidBlockValue, data) +func (b *EventBus) PublishEventValidBlock(data types.EventDataRoundState) error { + return b.Publish(types.EventValidBlockValue, data) } -func (b *EventBus) PublishEventBlockSyncStatus(ctx context.Context, data types.EventDataBlockSyncStatus) error { - return b.Publish(ctx, types.EventBlockSyncStatusValue, data) +func (b *EventBus) PublishEventBlockSyncStatus(data types.EventDataBlockSyncStatus) error { + return b.Publish(types.EventBlockSyncStatusValue, data) } -func (b *EventBus) PublishEventStateSyncStatus(ctx context.Context, data types.EventDataStateSyncStatus) error { - return b.Publish(ctx, types.EventStateSyncStatusValue, data) +func (b *EventBus) PublishEventStateSyncStatus(data types.EventDataStateSyncStatus) error { + return b.Publish(types.EventStateSyncStatusValue, data) } // PublishEventTx publishes tx event with events from Result. Note it will add // predefined keys (EventTypeKey, TxHashKey). Existing events with the same keys // will be overwritten. -func (b *EventBus) PublishEventTx(ctx context.Context, data types.EventDataTx) error { +func (b *EventBus) PublishEventTx(data types.EventDataTx) error { events := data.Result.Events // add Tendermint-reserved events @@ -152,70 +152,45 @@ func (b *EventBus) PublishEventTx(ctx context.Context, data types.EventDataTx) e }, }) - return b.pubsub.PublishWithEvents(ctx, data, events) + return b.pubsub.PublishWithEvents(data, events) } -func (b *EventBus) PublishEventNewRoundStep(ctx context.Context, data types.EventDataRoundState) error { - return b.Publish(ctx, types.EventNewRoundStepValue, data) +func (b *EventBus) PublishEventNewRoundStep(data types.EventDataRoundState) error { + return b.Publish(types.EventNewRoundStepValue, data) } -func (b *EventBus) PublishEventTimeoutPropose(ctx context.Context, data types.EventDataRoundState) error { - return b.Publish(ctx, types.EventTimeoutProposeValue, data) +func (b *EventBus) PublishEventTimeoutPropose(data types.EventDataRoundState) error { + return b.Publish(types.EventTimeoutProposeValue, data) } -func (b *EventBus) PublishEventTimeoutWait(ctx context.Context, data types.EventDataRoundState) error { - return b.Publish(ctx, types.EventTimeoutWaitValue, data) +func (b *EventBus) PublishEventTimeoutWait(data types.EventDataRoundState) error { + return b.Publish(types.EventTimeoutWaitValue, data) } -func (b *EventBus) PublishEventNewRound(ctx context.Context, data types.EventDataNewRound) error { - return b.Publish(ctx, types.EventNewRoundValue, data) +func (b *EventBus) PublishEventNewRound(data types.EventDataNewRound) error { + return b.Publish(types.EventNewRoundValue, data) } -func (b *EventBus) PublishEventCompleteProposal(ctx context.Context, data types.EventDataCompleteProposal) error { - return b.Publish(ctx, types.EventCompleteProposalValue, data) +func (b *EventBus) PublishEventCompleteProposal(data types.EventDataCompleteProposal) error { + return b.Publish(types.EventCompleteProposalValue, data) } -func (b *EventBus) PublishEventPolka(ctx context.Context, data types.EventDataRoundState) error { - return b.Publish(ctx, types.EventPolkaValue, data) +func (b *EventBus) PublishEventPolka(data types.EventDataRoundState) error { + return b.Publish(types.EventPolkaValue, data) } -func (b *EventBus) PublishEventRelock(ctx context.Context, data types.EventDataRoundState) error { - return b.Publish(ctx, types.EventRelockValue, data) +func (b *EventBus) PublishEventRelock(data types.EventDataRoundState) error { + return b.Publish(types.EventRelockValue, data) } -func (b *EventBus) PublishEventLock(ctx context.Context, data types.EventDataRoundState) error { - return b.Publish(ctx, types.EventLockValue, data) +func (b *EventBus) PublishEventLock(data types.EventDataRoundState) error { + return b.Publish(types.EventLockValue, data) } -func (b *EventBus) PublishEventValidatorSetUpdates(ctx context.Context, data types.EventDataValidatorSetUpdates) error { - return b.Publish(ctx, types.EventValidatorSetUpdatesValue, data) +func (b *EventBus) PublishEventValidatorSetUpdates(data types.EventDataValidatorSetUpdates) error { + return b.Publish(types.EventValidatorSetUpdatesValue, data) } -func (b *EventBus) PublishEventEvidenceValidated(ctx context.Context, evidence types.EventDataEvidenceValidated) error { - return b.Publish(ctx, types.EventEvidenceValidatedValue, evidence) -} - -//----------------------------------------------------------------------------- - -// NopEventBus implements a types.BlockEventPublisher that discards all events. -type NopEventBus struct{} - -func (NopEventBus) PublishEventNewBlock(context.Context, types.EventDataNewBlock) error { - return nil -} - -func (NopEventBus) PublishEventNewBlockHeader(context.Context, types.EventDataNewBlockHeader) error { - return nil -} - -func (NopEventBus) PublishEventNewEvidence(context.Context, types.EventDataNewEvidence) error { - return nil -} - -func (NopEventBus) PublishEventTx(context.Context, types.EventDataTx) error { - return nil -} - -func (NopEventBus) PublishEventValidatorSetUpdates(context.Context, types.EventDataValidatorSetUpdates) error { - return nil +func (b *EventBus) PublishEventEvidenceValidated(evidence types.EventDataEvidenceValidated) error { + return b.Publish(types.EventEvidenceValidatedValue, evidence) } diff --git a/internal/eventbus/event_bus_test.go b/internal/eventbus/event_bus_test.go index 3ef96b80b..6fd8a2d72 100644 --- a/internal/eventbus/event_bus_test.go +++ b/internal/eventbus/event_bus_test.go @@ -22,7 +22,7 @@ func TestEventBusPublishEventTx(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - eventBus := eventbus.NewDefault(log.TestingLogger()) + eventBus := eventbus.NewDefault(log.NewNopLogger()) err := eventBus.Start(ctx) require.NoError(t, err) @@ -55,7 +55,7 @@ func TestEventBusPublishEventTx(t *testing.T) { assert.Equal(t, result, edt.Result) }() - err = eventBus.PublishEventTx(ctx, types.EventDataTx{ + err = eventBus.PublishEventTx(types.EventDataTx{ TxResult: abci.TxResult{ Height: 1, Index: 0, @@ -75,7 +75,7 @@ func TestEventBusPublishEventTx(t *testing.T) { func TestEventBusPublishEventNewBlock(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - eventBus := eventbus.NewDefault(log.TestingLogger()) + eventBus := eventbus.NewDefault(log.NewNopLogger()) err := eventBus.Start(ctx) require.NoError(t, err) @@ -112,7 +112,7 @@ func TestEventBusPublishEventNewBlock(t *testing.T) { assert.Equal(t, resultFinalizeBlock, edt.ResultFinalizeBlock) }() - err = eventBus.PublishEventNewBlock(ctx, types.EventDataNewBlock{ + err = eventBus.PublishEventNewBlock(types.EventDataNewBlock{ Block: block, BlockID: blockID, ResultFinalizeBlock: resultFinalizeBlock, @@ -129,7 +129,7 @@ func TestEventBusPublishEventNewBlock(t *testing.T) { func TestEventBusPublishEventTxDuplicateKeys(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - eventBus := eventbus.NewDefault(log.TestingLogger()) + eventBus := eventbus.NewDefault(log.NewNopLogger()) err := eventBus.Start(ctx) require.NoError(t, err) @@ -223,7 +223,7 @@ func TestEventBusPublishEventTxDuplicateKeys(t *testing.T) { } }() - assert.NoError(t, eventBus.PublishEventTx(ctx, types.EventDataTx{ + assert.NoError(t, eventBus.PublishEventTx(types.EventDataTx{ TxResult: abci.TxResult{ Height: 1, Index: 0, @@ -247,7 +247,7 @@ func TestEventBusPublishEventNewBlockHeader(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - eventBus := eventbus.NewDefault(log.TestingLogger()) + eventBus := eventbus.NewDefault(log.NewNopLogger()) err := eventBus.Start(ctx) require.NoError(t, err) @@ -280,7 +280,7 @@ func TestEventBusPublishEventNewBlockHeader(t *testing.T) { assert.Equal(t, resultFinalizeBlock, edt.ResultFinalizeBlock) }() - err = eventBus.PublishEventNewBlockHeader(ctx, types.EventDataNewBlockHeader{ + err = eventBus.PublishEventNewBlockHeader(types.EventDataNewBlockHeader{ Header: block.Header, ResultFinalizeBlock: resultFinalizeBlock, }) @@ -297,7 +297,7 @@ func TestEventBusPublishEventEvidenceValidated(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - eventBus := eventbus.NewDefault(log.TestingLogger()) + eventBus := eventbus.NewDefault(log.NewNopLogger()) err := eventBus.Start(ctx) require.NoError(t, err) @@ -322,7 +322,7 @@ func TestEventBusPublishEventEvidenceValidated(t *testing.T) { assert.Equal(t, int64(1), edt.Height) }() - err = eventBus.PublishEventEvidenceValidated(ctx, types.EventDataEvidenceValidated{ + err = eventBus.PublishEventEvidenceValidated(types.EventDataEvidenceValidated{ Evidence: ev, Height: int64(1), }) @@ -339,7 +339,7 @@ func TestEventBusPublishEventNewEvidence(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - eventBus := eventbus.NewDefault(log.TestingLogger()) + eventBus := eventbus.NewDefault(log.NewNopLogger()) err := eventBus.Start(ctx) require.NoError(t, err) @@ -364,7 +364,7 @@ func TestEventBusPublishEventNewEvidence(t *testing.T) { assert.Equal(t, int64(4), edt.Height) }() - err = eventBus.PublishEventNewEvidence(ctx, types.EventDataNewEvidence{ + err = eventBus.PublishEventNewEvidence(types.EventDataNewEvidence{ Evidence: ev, Height: 4, }) @@ -381,7 +381,7 @@ func TestEventBusPublish(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - eventBus := eventbus.NewDefault(log.TestingLogger()) + eventBus := eventbus.NewDefault(log.NewNopLogger()) err := eventBus.Start(ctx) require.NoError(t, err) @@ -408,22 +408,22 @@ func TestEventBusPublish(t *testing.T) { } }() - require.NoError(t, eventBus.Publish(ctx, types.EventNewBlockHeaderValue, + require.NoError(t, eventBus.Publish(types.EventNewBlockHeaderValue, types.EventDataNewBlockHeader{})) - require.NoError(t, eventBus.PublishEventNewBlock(ctx, types.EventDataNewBlock{})) - require.NoError(t, eventBus.PublishEventNewBlockHeader(ctx, types.EventDataNewBlockHeader{})) - require.NoError(t, eventBus.PublishEventVote(ctx, types.EventDataVote{})) - require.NoError(t, eventBus.PublishEventNewRoundStep(ctx, types.EventDataRoundState{})) - require.NoError(t, eventBus.PublishEventTimeoutPropose(ctx, types.EventDataRoundState{})) - require.NoError(t, eventBus.PublishEventTimeoutWait(ctx, types.EventDataRoundState{})) - require.NoError(t, eventBus.PublishEventNewRound(ctx, types.EventDataNewRound{})) - require.NoError(t, eventBus.PublishEventCompleteProposal(ctx, types.EventDataCompleteProposal{})) - require.NoError(t, eventBus.PublishEventPolka(ctx, types.EventDataRoundState{})) - require.NoError(t, eventBus.PublishEventRelock(ctx, types.EventDataRoundState{})) - require.NoError(t, eventBus.PublishEventLock(ctx, types.EventDataRoundState{})) - require.NoError(t, eventBus.PublishEventValidatorSetUpdates(ctx, types.EventDataValidatorSetUpdates{})) - require.NoError(t, eventBus.PublishEventBlockSyncStatus(ctx, types.EventDataBlockSyncStatus{})) - require.NoError(t, eventBus.PublishEventStateSyncStatus(ctx, types.EventDataStateSyncStatus{})) + require.NoError(t, eventBus.PublishEventNewBlock(types.EventDataNewBlock{})) + require.NoError(t, eventBus.PublishEventNewBlockHeader(types.EventDataNewBlockHeader{})) + require.NoError(t, eventBus.PublishEventVote(types.EventDataVote{})) + require.NoError(t, eventBus.PublishEventNewRoundStep(types.EventDataRoundState{})) + require.NoError(t, eventBus.PublishEventTimeoutPropose(types.EventDataRoundState{})) + require.NoError(t, eventBus.PublishEventTimeoutWait(types.EventDataRoundState{})) + require.NoError(t, eventBus.PublishEventNewRound(types.EventDataNewRound{})) + require.NoError(t, eventBus.PublishEventCompleteProposal(types.EventDataCompleteProposal{})) + require.NoError(t, eventBus.PublishEventPolka(types.EventDataRoundState{})) + require.NoError(t, eventBus.PublishEventRelock(types.EventDataRoundState{})) + require.NoError(t, eventBus.PublishEventLock(types.EventDataRoundState{})) + require.NoError(t, eventBus.PublishEventValidatorSetUpdates(types.EventDataValidatorSetUpdates{})) + require.NoError(t, eventBus.PublishEventBlockSyncStatus(types.EventDataBlockSyncStatus{})) + require.NoError(t, eventBus.PublishEventStateSyncStatus(types.EventDataStateSyncStatus{})) require.GreaterOrEqual(t, <-count, numEventsExpected) } @@ -467,7 +467,7 @@ func benchmarkEventBus(numClients int, randQueries bool, randEvents bool, b *tes ctx, cancel := context.WithCancel(context.Background()) defer cancel() - eventBus := eventbus.NewDefault(log.TestingLogger()) // set buffer capacity to 0 so we are not testing cache + eventBus := eventbus.NewDefault(log.NewNopLogger()) // set buffer capacity to 0 so we are not testing cache err := eventBus.Start(ctx) if err != nil { b.Error(err) @@ -505,7 +505,7 @@ func benchmarkEventBus(numClients int, randQueries bool, randEvents bool, b *tes eventValue = randEventValue() } - err := eventBus.Publish(ctx, eventValue, types.EventDataString("Gamora")) + err := eventBus.Publish(eventValue, types.EventDataString("Gamora")) if err != nil { b.Error(err) } diff --git a/internal/evidence/mocks/block_store.go b/internal/evidence/mocks/block_store.go index ef3346b2a..e45b281b9 100644 --- a/internal/evidence/mocks/block_store.go +++ b/internal/evidence/mocks/block_store.go @@ -3,7 +3,10 @@ package mocks import ( + testing "testing" + mock "github.com/stretchr/testify/mock" + types "github.com/tendermint/tendermint/types" ) @@ -57,3 +60,13 @@ func (_m *BlockStore) LoadBlockMeta(height int64) *types.BlockMeta { return r0 } + +// NewBlockStore creates a new instance of BlockStore. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewBlockStore(t testing.TB) *BlockStore { + mock := &BlockStore{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/evidence/pool.go b/internal/evidence/pool.go index f4afb1f8c..132c61f23 100644 --- a/internal/evidence/pool.go +++ b/internal/evidence/pool.go @@ -36,14 +36,14 @@ type Pool struct { evidenceList *clist.CList // concurrent linked-list of evidence evidenceSize uint32 // amount of pending evidence - // needed to load validators to verify evidence - stateDB sm.Store // needed to load headers and commits to verify evidence blockStore BlockStore + stateDB sm.Store mtx sync.Mutex // latest state - state sm.State + state sm.State + isStarted bool // evidence from consensus is buffered to this slice, awaiting until the next height // before being flushed to the pool. This prevents broadcasting and proposing of // evidence before the height with which the evidence happened is finished. @@ -60,46 +60,19 @@ type Pool struct { Metrics *Metrics } -func (evpool *Pool) SetEventBus(e *eventbus.EventBus) { - evpool.eventBus = e -} - // NewPool creates an evidence pool. If using an existing evidence store, // it will add all pending evidence to the concurrent list. -func NewPool(logger log.Logger, evidenceDB dbm.DB, stateDB sm.Store, blockStore BlockStore, metrics *Metrics) (*Pool, error) { - state, err := stateDB.Load() - if err != nil { - return nil, fmt.Errorf("failed to load state: %w", err) - } - - pool := &Pool{ - stateDB: stateDB, +func NewPool(logger log.Logger, evidenceDB dbm.DB, stateStore sm.Store, blockStore BlockStore, metrics *Metrics, eventBus *eventbus.EventBus) *Pool { + return &Pool{ blockStore: blockStore, - state: state, + stateDB: stateStore, logger: logger, evidenceStore: evidenceDB, evidenceList: clist.New(), consensusBuffer: make([]duplicateVoteSet, 0), Metrics: metrics, + eventBus: eventBus, } - - // If pending evidence already in db, in event of prior failure, then check - // for expiration, update the size and load it back to the evidenceList. - pool.pruningHeight, pool.pruningTime = pool.removeExpiredPendingEvidence() - evList, _, err := pool.listEvidence(prefixPending, -1) - if err != nil { - return nil, err - } - - atomic.StoreUint32(&pool.evidenceSize, uint32(len(evList))) - pool.Metrics.NumEvidence.Set(float64(pool.evidenceSize)) - - for _, ev := range evList { - pool.evidenceList.PushBack(ev) - } - pool.eventBus = nil - - return pool, nil } // PendingEvidence is used primarily as part of block proposal and returns up to @@ -277,6 +250,31 @@ func (evpool *Pool) State() sm.State { return evpool.state } +func (evpool *Pool) Start(state sm.State) error { + if evpool.isStarted { + return errors.New("pool is already running") + } + + evpool.state = state + + // If pending evidence already in db, in event of prior failure, then check + // for expiration, update the size and load it back to the evidenceList. + evpool.pruningHeight, evpool.pruningTime = evpool.removeExpiredPendingEvidence() + evList, _, err := evpool.listEvidence(prefixPending, -1) + if err != nil { + return err + } + + atomic.StoreUint32(&evpool.evidenceSize, uint32(len(evList))) + evpool.Metrics.NumEvidence.Set(float64(evpool.evidenceSize)) + + for _, ev := range evList { + evpool.evidenceList.PushBack(ev) + } + + return nil +} + func (evpool *Pool) Close() error { return evpool.evidenceStore.Close() } @@ -340,7 +338,7 @@ func (evpool *Pool) addPendingEvidence(ctx context.Context, ev types.Evidence) e return nil } - return evpool.eventBus.PublishEventEvidenceValidated(ctx, types.EventDataEvidenceValidated{ + return evpool.eventBus.PublishEventEvidenceValidated(types.EventDataEvidenceValidated{ Evidence: ev, Height: ev.Height(), }) @@ -449,6 +447,7 @@ func (evpool *Pool) listEvidence(prefixKey int64, maxBytes int64) ([]types.Evide } func (evpool *Pool) removeExpiredPendingEvidence() (int64, time.Time) { + batch := evpool.evidenceStore.NewBatch() defer batch.Close() @@ -473,7 +472,6 @@ func (evpool *Pool) removeExpiredPendingEvidence() (int64, time.Time) { // remove evidence from the clist evpool.removeEvidenceFromList(blockEvidenceMap) - // update the evidence size atomic.AddUint32(&evpool.evidenceSize, ^uint32(len(blockEvidenceMap)-1)) diff --git a/internal/evidence/pool_test.go b/internal/evidence/pool_test.go index 51f785221..dcf44a5df 100644 --- a/internal/evidence/pool_test.go +++ b/internal/evidence/pool_test.go @@ -34,6 +34,18 @@ var ( defaultEvidenceMaxBytes int64 = 1000 ) +func startPool(t *testing.T, pool *evidence.Pool, store sm.Store) { + t.Helper() + state, err := store.Load() + if err != nil { + t.Fatalf("cannot load state: %v", err) + } + if err := pool.Start(state); err != nil { + t.Fatalf("cannot start state pool: %v", err) + } + +} + func TestEvidencePoolBasic(t *testing.T) { var ( height = int64(1) @@ -51,9 +63,13 @@ func TestEvidencePoolBasic(t *testing.T) { stateStore.On("LoadValidators", mock.AnythingOfType("int64")).Return(valSet, nil) stateStore.On("Load").Return(createState(height+1, valSet), nil) - pool, err := evidence.NewPool(log.TestingLogger(), evidenceDB, stateStore, blockStore, evidence.NopMetrics()) - require.NoError(t, err) - require.NoError(t, setupEventBus(ctx, pool)) + logger := log.NewNopLogger() + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) + + pool := evidence.NewPool(logger, evidenceDB, stateStore, blockStore, evidence.NopMetrics(), eventBus) + startPool(t, pool, stateStore) + // evidence not seen yet: evs, size := pool.PendingEvidence(defaultEvidenceMaxBytes) require.Equal(t, 0, len(evs)) @@ -115,10 +131,12 @@ func TestAddExpiredEvidence(t *testing.T) { return &types.BlockMeta{Header: types.Header{Time: expiredEvidenceTime}} }) - pool, err := evidence.NewPool(log.TestingLogger(), evidenceDB, stateStore, blockStore, evidence.NopMetrics()) - require.NoError(t, err) + logger := log.NewNopLogger() + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) - require.NoError(t, setupEventBus(ctx, pool)) + pool := evidence.NewPool(logger, evidenceDB, stateStore, blockStore, evidence.NopMetrics(), eventBus) + startPool(t, pool, stateStore) testCases := []struct { evHeight int64 @@ -159,9 +177,7 @@ func TestReportConflictingVotes(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - pool, pv := defaultTestPool(ctx, t, height) - - require.NoError(t, setupEventBus(ctx, pool)) + pool, pv, _ := defaultTestPool(ctx, t, height) val := types.NewValidator(pv.PrivKey.PubKey(), 10) @@ -201,9 +217,7 @@ func TestEvidencePoolUpdate(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - pool, val := defaultTestPool(ctx, t, height) - - require.NoError(t, setupEventBus(ctx, pool)) + pool, val, _ := defaultTestPool(ctx, t, height) state := pool.State() @@ -273,9 +287,7 @@ func TestVerifyPendingEvidencePasses(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - pool, val := defaultTestPool(ctx, t, height) - - require.NoError(t, setupEventBus(ctx, pool)) + pool, val, _ := defaultTestPool(ctx, t, height) ev, err := types.NewMockDuplicateVoteEvidenceWithValidator( ctx, @@ -295,9 +307,7 @@ func TestVerifyDuplicatedEvidenceFails(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - pool, val := defaultTestPool(ctx, t, height) - - require.NoError(t, setupEventBus(ctx, pool)) + pool, val, _ := defaultTestPool(ctx, t, height) ev, err := types.NewMockDuplicateVoteEvidenceWithValidator( ctx, @@ -321,7 +331,7 @@ func TestEventOnEvidenceValidated(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - pool, val := defaultTestPool(ctx, t, height) + pool, val, eventBus := defaultTestPool(ctx, t, height) ev, err := types.NewMockDuplicateVoteEvidenceWithValidator( ctx, @@ -332,11 +342,6 @@ func TestEventOnEvidenceValidated(t *testing.T) { ) require.NoError(t, err) - eventBus := eventbus.NewDefault(log.TestingLogger()) - require.NoError(t, eventBus.Start(ctx)) - - pool.SetEventBus(eventBus) - const query = `tm.event='EvidenceValidated'` evSub, err := eventBus.SubscribeWithArgs(ctx, tmpubsub.SubscribeArgs{ ClientID: "test", @@ -348,6 +353,9 @@ func TestEventOnEvidenceValidated(t *testing.T) { go func() { defer close(done) msg, err := evSub.Next(ctx) + if ctx.Err() != nil { + return + } assert.NoError(t, err) edt := msg.Data().(types.EventDataEvidenceValidated) @@ -394,14 +402,15 @@ func TestLightClientAttackEvidenceLifecycle(t *testing.T) { blockStore.On("LoadBlockCommit", height).Return(trusted.Commit) blockStore.On("LoadBlockCommit", commonHeight).Return(common.Commit) - pool, err := evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics()) - require.NoError(t, err) + logger := log.NewNopLogger() + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) - require.NoError(t, setupEventBus(ctx, pool)) + pool := evidence.NewPool(logger, dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics(), eventBus) hash := ev.Hash() - err = pool.AddEvidence(ctx, ev) + err := pool.AddEvidence(ctx, ev) require.NoError(t, err) err = pool.AddEvidence(ctx, ev) require.NoError(t, err) @@ -449,11 +458,13 @@ func TestRecoverPendingEvidence(t *testing.T) { blockStore, err := initializeBlockStore(dbm.NewMemDB(), state, valAddress) require.NoError(t, err) - // create previous pool and populate it - pool, err := evidence.NewPool(log.TestingLogger(), evidenceDB, stateStore, blockStore, evidence.NopMetrics()) - require.NoError(t, err) + logger := log.NewNopLogger() + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) - require.NoError(t, setupEventBus(ctx, pool)) + // create previous pool and populate it + pool := evidence.NewPool(logger, evidenceDB, stateStore, blockStore, evidence.NopMetrics(), eventBus) + startPool(t, pool, stateStore) goodEvidence, err := types.NewMockDuplicateVoteEvidenceWithValidator( ctx, @@ -495,9 +506,8 @@ func TestRecoverPendingEvidence(t *testing.T) { }, }, nil) - newPool, err := evidence.NewPool(log.TestingLogger(), evidenceDB, newStateStore, blockStore, evidence.NopMetrics()) - require.NoError(t, err) - + newPool := evidence.NewPool(logger, evidenceDB, newStateStore, blockStore, evidence.NopMetrics(), nil) + startPool(t, newPool, newStateStore) evList, _ := newPool.PendingEvidence(defaultEvidenceMaxBytes) require.Equal(t, 1, len(evList)) @@ -559,10 +569,7 @@ func initializeBlockStore(db dbm.DB, state sm.State, valAddr []byte) (*store.Blo for i := int64(1); i <= state.LastBlockHeight; i++ { lastCommit := makeCommit(i-1, valAddr) - block, err := sf.MakeBlock(state, i, lastCommit) - if err != nil { - return nil, err - } + block := sf.MakeBlock(state, i, lastCommit) block.Header.Time = defaultEvidenceTime.Add(time.Duration(i) * time.Minute) block.Header.Version = version.Consensus{Block: version.BlockProtocol, App: 1} @@ -590,7 +597,7 @@ func makeCommit(height int64, valAddr []byte) *types.Commit { return types.NewCommit(height, 0, types.BlockID{}, commitSigs) } -func defaultTestPool(ctx context.Context, t *testing.T, height int64) (*evidence.Pool, types.MockPV) { +func defaultTestPool(ctx context.Context, t *testing.T, height int64) (*evidence.Pool, types.MockPV, *eventbus.EventBus) { t.Helper() val := types.NewMockPV() valAddress := val.PrivKey.PubKey().Address() @@ -601,10 +608,14 @@ func defaultTestPool(ctx context.Context, t *testing.T, height int64) (*evidence blockStore, err := initializeBlockStore(dbm.NewMemDB(), state, valAddress) require.NoError(t, err) - pool, err := evidence.NewPool(log.TestingLogger(), evidenceDB, stateStore, blockStore, evidence.NopMetrics()) - require.NoError(t, err, "test evidence pool could not be created") + logger := log.NewNopLogger() - return pool, val + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) + + pool := evidence.NewPool(logger, evidenceDB, stateStore, blockStore, evidence.NopMetrics(), eventBus) + startPool(t, pool, stateStore) + return pool, val, eventBus } func createState(height int64, valSet *types.ValidatorSet) sm.State { @@ -616,12 +627,3 @@ func createState(height int64, valSet *types.ValidatorSet) sm.State { ConsensusParams: *types.DefaultConsensusParams(), } } - -func setupEventBus(ctx context.Context, evpool *evidence.Pool) error { - eventBus := eventbus.NewDefault(log.TestingLogger()) - if err := eventBus.Start(ctx); err != nil { - return err - } - evpool.SetEventBus(eventBus) - return nil -} diff --git a/internal/evidence/reactor.go b/internal/evidence/reactor.go index 1011f732f..1d952d30e 100644 --- a/internal/evidence/reactor.go +++ b/internal/evidence/reactor.go @@ -38,6 +38,7 @@ func GetChannelDescriptor() *p2p.ChannelDescriptor { Priority: 6, RecvMessageCapacity: maxMsgSize, RecvBufferCapacity: 32, + Name: "evidence", } } @@ -46,9 +47,9 @@ type Reactor struct { service.BaseService logger log.Logger - evpool *Pool - evidenceCh *p2p.Channel - peerUpdates *p2p.PeerUpdates + evpool *Pool + chCreator p2p.ChannelCreator + peerEvents p2p.PeerEventSubscriber mtx sync.Mutex @@ -59,28 +60,22 @@ type Reactor struct { // service.Service interface. It accepts a p2p Channel dedicated for handling // envelopes with EvidenceList messages. func NewReactor( - ctx context.Context, logger log.Logger, chCreator p2p.ChannelCreator, - peerUpdates *p2p.PeerUpdates, + peerEvents p2p.PeerEventSubscriber, evpool *Pool, -) (*Reactor, error) { - evidenceCh, err := chCreator(ctx, GetChannelDescriptor()) - if err != nil { - return nil, err - } - +) *Reactor { r := &Reactor{ logger: logger, evpool: evpool, - evidenceCh: evidenceCh, - peerUpdates: peerUpdates, + chCreator: chCreator, + peerEvents: peerEvents, peerRoutines: make(map[types.NodeID]context.CancelFunc), } r.BaseService = *service.NewBaseService(logger, "Evidence", r) - return r, err + return r } // OnStart starts separate go routines for each p2p Channel and listens for @@ -88,18 +83,20 @@ func NewReactor( // messages on that p2p channel accordingly. The caller must be sure to execute // OnStop to ensure the outbound p2p Channels are closed. No error is returned. func (r *Reactor) OnStart(ctx context.Context) error { - go r.processEvidenceCh(ctx) - go r.processPeerUpdates(ctx) + ch, err := r.chCreator(ctx, GetChannelDescriptor()) + if err != nil { + return err + } + + go r.processEvidenceCh(ctx, ch) + go r.processPeerUpdates(ctx, r.peerEvents(ctx), ch) return nil } // OnStop stops the reactor by signaling to all spawned goroutines to exit and // blocking until they all exit. -func (r *Reactor) OnStop() { - // Close the evidence db - r.evpool.Close() -} +func (r *Reactor) OnStop() { r.evpool.Close() } // handleEvidenceMessage handles envelopes sent from peers on the EvidenceChannel. // It returns an error only if the Envelope.Message is unknown for this channel @@ -136,7 +133,7 @@ func (r *Reactor) handleEvidenceMessage(ctx context.Context, envelope *p2p.Envel // handleMessage handles an Envelope sent from a peer on a specific p2p Channel. // It will handle errors and any possible panics gracefully. A caller can handle // any error returned by sending a PeerError on the respective channel. -func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelope *p2p.Envelope) (err error) { +func (r *Reactor) handleMessage(ctx context.Context, envelope *p2p.Envelope) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("panic in processing message: %v", e) @@ -150,26 +147,25 @@ func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelop r.logger.Debug("received message", "message", envelope.Message, "peer", envelope.From) - switch chID { + switch envelope.ChannelID { case EvidenceChannel: err = r.handleEvidenceMessage(ctx, envelope) - default: - err = fmt.Errorf("unknown channel ID (%d) for envelope (%v)", chID, envelope) + err = fmt.Errorf("unknown channel ID (%d) for envelope (%v)", envelope.ChannelID, envelope) } - return err + return } // processEvidenceCh implements a blocking event loop where we listen for p2p // Envelope messages from the evidenceCh. -func (r *Reactor) processEvidenceCh(ctx context.Context) { - iter := r.evidenceCh.Receive(ctx) +func (r *Reactor) processEvidenceCh(ctx context.Context, evidenceCh *p2p.Channel) { + iter := evidenceCh.Receive(ctx) for iter.Next(ctx) { envelope := iter.Envelope() - if err := r.handleMessage(ctx, r.evidenceCh.ID, envelope); err != nil { - r.logger.Error("failed to process message", "ch_id", r.evidenceCh.ID, "envelope", envelope, "err", err) - if serr := r.evidenceCh.SendError(ctx, p2p.PeerError{ + if err := r.handleMessage(ctx, envelope); err != nil { + r.logger.Error("failed to process message", "ch_id", envelope.ChannelID, "envelope", envelope, "err", err) + if serr := evidenceCh.SendError(ctx, p2p.PeerError{ NodeID: envelope.From, Err: err, }); serr != nil { @@ -190,7 +186,7 @@ func (r *Reactor) processEvidenceCh(ctx context.Context) { // connects/disconnects frequently from the broadcasting peer(s). // // REF: https://github.com/tendermint/tendermint/issues/4727 -func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpdate) { +func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpdate, evidenceCh *p2p.Channel) { r.logger.Debug("received peer update", "peer", peerUpdate.NodeID, "status", peerUpdate.Status) r.mtx.Lock() @@ -213,7 +209,7 @@ func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpda if !ok { pctx, pcancel := context.WithCancel(ctx) r.peerRoutines[peerUpdate.NodeID] = pcancel - go r.broadcastEvidenceLoop(pctx, peerUpdate.NodeID) + go r.broadcastEvidenceLoop(pctx, peerUpdate.NodeID, evidenceCh) } case p2p.PeerStatusDown: @@ -231,11 +227,11 @@ func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpda // processPeerUpdates initiates a blocking process where we listen for and handle // PeerUpdate messages. When the reactor is stopped, we will catch the signal and // close the p2p PeerUpdatesCh gracefully. -func (r *Reactor) processPeerUpdates(ctx context.Context) { +func (r *Reactor) processPeerUpdates(ctx context.Context, peerUpdates *p2p.PeerUpdates, evidenceCh *p2p.Channel) { for { select { - case peerUpdate := <-r.peerUpdates.Updates(): - r.processPeerUpdate(ctx, peerUpdate) + case peerUpdate := <-peerUpdates.Updates(): + r.processPeerUpdate(ctx, peerUpdate, evidenceCh) case <-ctx.Done(): return } @@ -253,7 +249,7 @@ func (r *Reactor) processPeerUpdates(ctx context.Context) { // that the peer has already received or may not be ready for. // // REF: https://github.com/tendermint/tendermint/issues/4727 -func (r *Reactor) broadcastEvidenceLoop(ctx context.Context, peerID types.NodeID) { +func (r *Reactor) broadcastEvidenceLoop(ctx context.Context, peerID types.NodeID, evidenceCh *p2p.Channel) { var next *clist.CElement defer func() { @@ -300,7 +296,7 @@ func (r *Reactor) broadcastEvidenceLoop(ctx context.Context, peerID types.NodeID // peer may receive this piece of evidence multiple times if it added and // removed frequently from the broadcasting peer. - if err := r.evidenceCh.Send(ctx, p2p.Envelope{ + if err := evidenceCh.Send(ctx, p2p.Envelope{ To: peerID, Message: evProto, }); err != nil { diff --git a/internal/evidence/reactor_test.go b/internal/evidence/reactor_test.go index d0863acc1..f23195fae 100644 --- a/internal/evidence/reactor_test.go +++ b/internal/evidence/reactor_test.go @@ -16,7 +16,6 @@ import ( dbm "github.com/tendermint/tm-db" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/eventbus" "github.com/tendermint/tendermint/internal/evidence" "github.com/tendermint/tendermint/internal/evidence/mocks" @@ -46,7 +45,7 @@ type reactorTestSuite struct { numStateStores int } -func setup(ctx context.Context, t *testing.T, stateStores []sm.Store, chBuf uint) *reactorTestSuite { +func setup(ctx context.Context, t *testing.T, stateStores []sm.Store) *reactorTestSuite { t.Helper() pID := make([]byte, 16) @@ -56,7 +55,7 @@ func setup(ctx context.Context, t *testing.T, stateStores []sm.Store, chBuf uint numStateStores := len(stateStores) rts := &reactorTestSuite{ numStateStores: numStateStores, - logger: log.TestingLogger().With("testCase", t.Name()), + logger: log.NewNopLogger().With("testCase", t.Name()), network: p2ptest.MakeNetwork(ctx, t, p2ptest.NetworkOptions{NumNodes: numStateStores}), reactors: make(map[types.NodeID]*evidence.Reactor, numStateStores), pools: make(map[types.NodeID]*evidence.Pool, numStateStores), @@ -82,30 +81,30 @@ func setup(ctx context.Context, t *testing.T, stateStores []sm.Store, chBuf uint } return nil }) - rts.pools[nodeID], err = evidence.NewPool(logger, evidenceDB, stateStores[idx], blockStore, evidence.NopMetrics()) - - require.NoError(t, err) eventBus := eventbus.NewDefault(logger) err = eventBus.Start(ctx) require.NoError(t, err) - rts.pools[nodeID].SetEventBus(eventBus) + + rts.pools[nodeID] = evidence.NewPool(logger, evidenceDB, stateStores[idx], blockStore, evidence.NopMetrics(), eventBus) + startPool(t, rts.pools[nodeID], stateStores[idx]) + + require.NoError(t, err) rts.peerChans[nodeID] = make(chan p2p.PeerUpdate) - rts.peerUpdates[nodeID] = p2p.NewPeerUpdates(rts.peerChans[nodeID], 1) - rts.network.Nodes[nodeID].PeerManager.Register(ctx, rts.peerUpdates[nodeID]) + pu := p2p.NewPeerUpdates(rts.peerChans[nodeID], 1) + rts.peerUpdates[nodeID] = pu + rts.network.Nodes[nodeID].PeerManager.Register(ctx, pu) rts.nodes = append(rts.nodes, rts.network.Nodes[nodeID]) chCreator := func(ctx context.Context, chdesc *p2p.ChannelDescriptor) (*p2p.Channel, error) { return rts.evidenceChannels[nodeID], nil } - rts.reactors[nodeID], err = evidence.NewReactor( - ctx, + rts.reactors[nodeID] = evidence.NewReactor( logger, chCreator, - rts.peerUpdates[nodeID], + func(ctx context.Context) *p2p.PeerUpdates { return pu }, rts.pools[nodeID]) - require.NoError(t, err) require.NoError(t, rts.reactors[nodeID].Start(ctx)) require.True(t, rts.reactors[nodeID].IsRunning()) @@ -245,7 +244,7 @@ func TestReactorMultiDisconnect(t *testing.T) { stateDB1 := initializeValidatorState(ctx, t, val, height) stateDB2 := initializeValidatorState(ctx, t, val, height) - rts := setup(ctx, t, []sm.Store{stateDB1, stateDB2}, 20) + rts := setup(ctx, t, []sm.Store{stateDB1, stateDB2}) primary := rts.nodes[0] secondary := rts.nodes[1] @@ -290,7 +289,7 @@ func TestReactorBroadcastEvidence(t *testing.T) { stateDBs[i] = initializeValidatorState(ctx, t, val, height) } - rts := setup(ctx, t, stateDBs, 0) + rts := setup(ctx, t, stateDBs) rts.start(ctx, t) @@ -348,7 +347,7 @@ func TestReactorBroadcastEvidence_Lagging(t *testing.T) { stateDB1 := initializeValidatorState(ctx, t, val, height1) stateDB2 := initializeValidatorState(ctx, t, val, height2) - rts := setup(ctx, t, []sm.Store{stateDB1, stateDB2}, 100) + rts := setup(ctx, t, []sm.Store{stateDB1, stateDB2}) rts.start(ctx, t) primary := rts.nodes[0] @@ -382,7 +381,7 @@ func TestReactorBroadcastEvidence_Pending(t *testing.T) { stateDB1 := initializeValidatorState(ctx, t, val, height) stateDB2 := initializeValidatorState(ctx, t, val, height) - rts := setup(ctx, t, []sm.Store{stateDB1, stateDB2}, 100) + rts := setup(ctx, t, []sm.Store{stateDB1, stateDB2}) primary := rts.nodes[0] secondary := rts.nodes[1] @@ -423,7 +422,7 @@ func TestReactorBroadcastEvidence_Committed(t *testing.T) { stateDB1 := initializeValidatorState(ctx, t, val, height) stateDB2 := initializeValidatorState(ctx, t, val, height) - rts := setup(ctx, t, []sm.Store{stateDB1, stateDB2}, 0) + rts := setup(ctx, t, []sm.Store{stateDB1, stateDB2}) primary := rts.nodes[0] secondary := rts.nodes[1] @@ -482,7 +481,7 @@ func TestReactorBroadcastEvidence_FullyConnected(t *testing.T) { stateDBs[i] = initializeValidatorState(ctx, t, val, height) } - rts := setup(ctx, t, stateDBs, 0) + rts := setup(ctx, t, stateDBs) rts.start(ctx, t) evList := createEvidenceList(ctx, t, rts.pools[rts.network.RandomNode().NodeID], val, numEvidence) @@ -523,10 +522,10 @@ func TestEvidenceListSerialization(t *testing.T) { Round: 2, Timestamp: stamp, BlockID: types.BlockID{ - Hash: tmhash.Sum([]byte("blockID_hash")), + Hash: crypto.Checksum([]byte("blockID_hash")), PartSetHeader: types.PartSetHeader{ Total: 1000000, - Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), + Hash: crypto.Checksum([]byte("blockID_part_set_header_hash")), }, }, ValidatorAddress: crypto.AddressHash([]byte("validator_address")), diff --git a/internal/evidence/verify_test.go b/internal/evidence/verify_test.go index 607c8fd50..b2056186f 100644 --- a/internal/evidence/verify_test.go +++ b/internal/evidence/verify_test.go @@ -11,7 +11,7 @@ import ( dbm "github.com/tendermint/tm-db" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/internal/eventbus" "github.com/tendermint/tendermint/internal/evidence" "github.com/tendermint/tendermint/internal/evidence/mocks" sm "github.com/tendermint/tendermint/internal/state" @@ -76,6 +76,7 @@ func TestVerify_LunaticAttackAgainstState(t *testing.T) { ) ctx, cancel := context.WithCancel(context.Background()) defer cancel() + logger := log.NewNopLogger() attackTime := defaultEvidenceTime.Add(1 * time.Hour) // create valid lunatic evidence @@ -96,8 +97,7 @@ func TestVerify_LunaticAttackAgainstState(t *testing.T) { blockStore.On("LoadBlockMeta", height).Return(&types.BlockMeta{Header: *trusted.Header}) blockStore.On("LoadBlockCommit", commonHeight).Return(common.Commit) blockStore.On("LoadBlockCommit", height).Return(trusted.Commit) - pool, err := evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics()) - require.NoError(t, err) + pool := evidence.NewPool(log.NewNopLogger(), dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics(), nil) evList := types.EvidenceList{ev} // check that the evidence pool correctly verifies the evidence @@ -111,32 +111,29 @@ func TestVerify_LunaticAttackAgainstState(t *testing.T) { // if we submit evidence only against a single byzantine validator when we see there are more validators then this // should return an error ev.ByzantineValidators = ev.ByzantineValidators[:1] - t.Log(evList) assert.Error(t, pool.CheckEvidence(ctx, evList)) // restore original byz vals ev.ByzantineValidators = ev.GetByzantineValidators(common.ValidatorSet, trusted.SignedHeader) // duplicate evidence should be rejected evList = types.EvidenceList{ev, ev} - pool, err = evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics()) - require.NoError(t, err) + pool = evidence.NewPool(logger, dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics(), nil) assert.Error(t, pool.CheckEvidence(ctx, evList)) // If evidence is submitted with an altered timestamp it should return an error + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) + ev.Timestamp = defaultEvidenceTime.Add(1 * time.Minute) - pool, err = evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics()) - require.NoError(t, err) + pool = evidence.NewPool(logger, dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics(), eventBus) - require.NoError(t, setupEventBus(ctx, pool)) - - err = pool.AddEvidence(ctx, ev) + err := pool.AddEvidence(ctx, ev) assert.Error(t, err) ev.Timestamp = defaultEvidenceTime // Evidence submitted with a different validator power should fail ev.TotalVotingPower = 1 - pool, err = evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics()) - require.NoError(t, err) + pool = evidence.NewPool(logger, dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics(), nil) err = pool.AddEvidence(ctx, ev) assert.Error(t, err) ev.TotalVotingPower = common.ValidatorSet.TotalVotingPower() @@ -154,6 +151,9 @@ func TestVerify_ForwardLunaticAttack(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + + logger := log.NewNopLogger() + // create a forward lunatic attack ev, trusted, common := makeLunaticEvidence(ctx, t, attackHeight, commonHeight, totalVals, byzVals, totalVals-byzVals, defaultEvidenceTime, attackTime) @@ -179,10 +179,11 @@ func TestVerify_ForwardLunaticAttack(t *testing.T) { blockStore.On("LoadBlockCommit", commonHeight).Return(common.Commit) blockStore.On("LoadBlockCommit", nodeHeight).Return(trusted.Commit) blockStore.On("Height").Return(nodeHeight) - pool, err := evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics()) - require.NoError(t, err) - require.NoError(t, setupEventBus(ctx, pool)) + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) + + pool := evidence.NewPool(logger, dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics(), eventBus) // check that the evidence pool correctly verifies the evidence assert.NoError(t, pool.CheckEvidence(ctx, types.EvidenceList{ev})) @@ -199,8 +200,7 @@ func TestVerify_ForwardLunaticAttack(t *testing.T) { oldBlockStore.On("Height").Return(nodeHeight) require.Equal(t, defaultEvidenceTime, oldBlockStore.LoadBlockMeta(nodeHeight).Header.Time) - pool, err = evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, oldBlockStore, evidence.NopMetrics()) - require.NoError(t, err) + pool = evidence.NewPool(logger, dbm.NewMemDB(), stateStore, oldBlockStore, evidence.NopMetrics(), nil) assert.Error(t, pool.CheckEvidence(ctx, types.EvidenceList{ev})) } @@ -208,6 +208,8 @@ func TestVerifyLightClientAttack_Equivocation(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + logger := log.NewNopLogger() + conflictingVals, conflictingPrivVals := factory.ValidatorSet(ctx, t, 5, 10) conflictingHeader := factory.MakeHeader(t, &types.Header{ @@ -270,7 +272,7 @@ func TestVerifyLightClientAttack_Equivocation(t *testing.T) { // conflicting header has different next validators hash which should have been correctly derived from // the previous round - ev.ConflictingBlock.Header.NextValidatorsHash = crypto.CRandBytes(tmhash.Size) + ev.ConflictingBlock.Header.NextValidatorsHash = crypto.CRandBytes(crypto.HashSize) assert.Error(t, evidence.VerifyLightClientAttack(ev, trustedSignedHeader, trustedSignedHeader, nil, defaultEvidenceTime.Add(1*time.Minute), 2*time.Hour)) @@ -289,10 +291,10 @@ func TestVerifyLightClientAttack_Equivocation(t *testing.T) { blockStore.On("LoadBlockMeta", int64(10)).Return(&types.BlockMeta{Header: *trustedHeader}) blockStore.On("LoadBlockCommit", int64(10)).Return(trustedCommit) - pool, err := evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics()) - require.NoError(t, err) + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) - require.NoError(t, setupEventBus(ctx, pool)) + pool := evidence.NewPool(logger, dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics(), eventBus) evList := types.EvidenceList{ev} err = pool.CheckEvidence(ctx, evList) @@ -305,6 +307,9 @@ func TestVerifyLightClientAttack_Equivocation(t *testing.T) { func TestVerifyLightClientAttack_Amnesia(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + + logger := log.NewNopLogger() + var height int64 = 10 conflictingVals, conflictingPrivVals := factory.ValidatorSet(ctx, t, 5, 10) @@ -378,10 +383,10 @@ func TestVerifyLightClientAttack_Amnesia(t *testing.T) { blockStore.On("LoadBlockMeta", int64(10)).Return(&types.BlockMeta{Header: *trustedHeader}) blockStore.On("LoadBlockCommit", int64(10)).Return(trustedCommit) - pool, err := evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics()) - require.NoError(t, err) + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) - require.NoError(t, setupEventBus(ctx, pool)) + pool := evidence.NewPool(logger, dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics(), eventBus) evList := types.EvidenceList{ev} err = pool.CheckEvidence(ctx, evList) @@ -401,6 +406,7 @@ func TestVerifyDuplicateVoteEvidence(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + logger := log.NewNopLogger() val := types.NewMockPV() val2 := types.NewMockPV() valSet := types.NewValidatorSet([]*types.Validator{val.ExtractIntoValidator(ctx, 1)}) @@ -478,10 +484,11 @@ func TestVerifyDuplicateVoteEvidence(t *testing.T) { blockStore := &mocks.BlockStore{} blockStore.On("LoadBlockMeta", int64(10)).Return(&types.BlockMeta{Header: types.Header{Time: defaultEvidenceTime}}) - pool, err := evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics()) - require.NoError(t, err) + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) - require.NoError(t, setupEventBus(ctx, pool)) + pool := evidence.NewPool(logger, dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics(), eventBus) + startPool(t, pool, stateStore) evList := types.EvidenceList{goodEv} err = pool.CheckEvidence(ctx, evList) @@ -610,8 +617,8 @@ func makeVote( func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.BlockID { var ( - h = make([]byte, tmhash.Size) - psH = make([]byte, tmhash.Size) + h = make([]byte, crypto.HashSize) + psH = make([]byte, crypto.HashSize) ) copy(h, hash) copy(psH, partSetHash) diff --git a/internal/inspect/inspect_test.go b/internal/inspect/inspect_test.go index 36bbda802..511dbc814 100644 --- a/internal/inspect/inspect_test.go +++ b/internal/inspect/inspect_test.go @@ -31,7 +31,7 @@ import ( func TestInspectConstructor(t *testing.T) { cfg, err := config.ResetTestRoot(t.TempDir(), "test") require.NoError(t, err) - testLogger := log.TestingLogger() + testLogger := log.NewNopLogger() t.Cleanup(leaktest.Check(t)) defer func() { _ = os.RemoveAll(cfg.RootDir) }() t.Run("from config", func(t *testing.T) { @@ -47,7 +47,7 @@ func TestInspectRun(t *testing.T) { cfg, err := config.ResetTestRoot(t.TempDir(), "test") require.NoError(t, err) - testLogger := log.TestingLogger() + testLogger := log.NewNopLogger() t.Cleanup(leaktest.Check(t)) defer func() { _ = os.RemoveAll(cfg.RootDir) }() t.Run("from config", func(t *testing.T) { @@ -85,7 +85,7 @@ func TestBlock(t *testing.T) { eventSinkMock.On("Type").Return(indexer.EventSinkType("Mock")) rpcConfig := config.TestRPCConfig() - l := log.TestingLogger() + l := log.NewNopLogger() d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l) ctx, cancel := context.WithCancel(context.Background()) wg := &sync.WaitGroup{} @@ -132,7 +132,7 @@ func TestTxSearch(t *testing.T) { Return([]*abcitypes.TxResult{testTxResult}, nil) rpcConfig := config.TestRPCConfig() - l := log.TestingLogger() + l := log.NewNopLogger() d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l) ctx, cancel := context.WithCancel(context.Background()) wg := &sync.WaitGroup{} @@ -179,7 +179,7 @@ func TestTx(t *testing.T) { }, nil) rpcConfig := config.TestRPCConfig() - l := log.TestingLogger() + l := log.NewNopLogger() d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l) ctx, cancel := context.WithCancel(context.Background()) wg := &sync.WaitGroup{} @@ -227,7 +227,7 @@ func TestConsensusParams(t *testing.T) { eventSinkMock.On("Type").Return(indexer.EventSinkType("Mock")) rpcConfig := config.TestRPCConfig() - l := log.TestingLogger() + l := log.NewNopLogger() d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l) ctx, cancel := context.WithCancel(context.Background()) @@ -280,7 +280,7 @@ func TestBlockResults(t *testing.T) { eventSinkMock.On("Type").Return(indexer.EventSinkType("Mock")) rpcConfig := config.TestRPCConfig() - l := log.TestingLogger() + l := log.NewNopLogger() d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l) ctx, cancel := context.WithCancel(context.Background()) @@ -328,7 +328,7 @@ func TestCommit(t *testing.T) { eventSinkMock.On("Type").Return(indexer.EventSinkType("Mock")) rpcConfig := config.TestRPCConfig() - l := log.TestingLogger() + l := log.NewNopLogger() d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l) ctx, cancel := context.WithCancel(context.Background()) @@ -382,7 +382,7 @@ func TestBlockByHash(t *testing.T) { eventSinkMock.On("Type").Return(indexer.EventSinkType("Mock")) rpcConfig := config.TestRPCConfig() - l := log.TestingLogger() + l := log.NewNopLogger() d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l) ctx, cancel := context.WithCancel(context.Background()) @@ -435,7 +435,7 @@ func TestBlockchain(t *testing.T) { eventSinkMock.On("Type").Return(indexer.EventSinkType("Mock")) rpcConfig := config.TestRPCConfig() - l := log.TestingLogger() + l := log.NewNopLogger() d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l) ctx, cancel := context.WithCancel(context.Background()) @@ -488,7 +488,7 @@ func TestValidators(t *testing.T) { eventSinkMock.On("Type").Return(indexer.EventSinkType("Mock")) rpcConfig := config.TestRPCConfig() - l := log.TestingLogger() + l := log.NewNopLogger() d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l) ctx, cancel := context.WithCancel(context.Background()) @@ -547,7 +547,7 @@ func TestBlockSearch(t *testing.T) { mock.MatchedBy(func(q *query.Query) bool { return testQuery == q.String() })). Return([]int64{testHeight}, nil) rpcConfig := config.TestRPCConfig() - l := log.TestingLogger() + l := log.NewNopLogger() d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l) ctx, cancel := context.WithCancel(context.Background()) diff --git a/internal/inspect/rpc/rpc.go b/internal/inspect/rpc/rpc.go index 40f0d5d26..00c3e52ef 100644 --- a/internal/inspect/rpc/rpc.go +++ b/internal/inspect/rpc/rpc.go @@ -38,16 +38,16 @@ func Routes(cfg config.RPCConfig, s state.Store, bs state.BlockStore, es []index Logger: logger, } return core.RoutesMap{ - "blockchain": server.NewRPCFunc(env.BlockchainInfo, "minHeight", "maxHeight"), - "consensus_params": server.NewRPCFunc(env.ConsensusParams, "height"), - "block": server.NewRPCFunc(env.Block, "height"), - "block_by_hash": server.NewRPCFunc(env.BlockByHash, "hash"), - "block_results": server.NewRPCFunc(env.BlockResults, "height"), - "commit": server.NewRPCFunc(env.Commit, "height"), - "validators": server.NewRPCFunc(env.Validators, "height", "page", "per_page"), - "tx": server.NewRPCFunc(env.Tx, "hash", "prove"), - "tx_search": server.NewRPCFunc(env.TxSearch, "query", "prove", "page", "per_page", "order_by"), - "block_search": server.NewRPCFunc(env.BlockSearch, "query", "page", "per_page", "order_by"), + "blockchain": server.NewRPCFunc(env.BlockchainInfo), + "consensus_params": server.NewRPCFunc(env.ConsensusParams), + "block": server.NewRPCFunc(env.Block), + "block_by_hash": server.NewRPCFunc(env.BlockByHash), + "block_results": server.NewRPCFunc(env.BlockResults), + "commit": server.NewRPCFunc(env.Commit), + "validators": server.NewRPCFunc(env.Validators), + "tx": server.NewRPCFunc(env.Tx), + "tx_search": server.NewRPCFunc(env.TxSearch), + "block_search": server.NewRPCFunc(env.BlockSearch), } } diff --git a/internal/libs/autofile/autofile.go b/internal/libs/autofile/autofile.go index 6f38fc43b..f554228ba 100644 --- a/internal/libs/autofile/autofile.go +++ b/internal/libs/autofile/autofile.go @@ -19,7 +19,7 @@ import ( // Create/Append to ./autofile_test af, err := OpenAutoFile("autofile_test") if err != nil { - panic(err) + log.Fatal(err) } // Stream of writes. @@ -32,7 +32,7 @@ for i := 0; i < 60; i++ { // Close the AutoFile err = af.Close() if err != nil { - panic(err) + log.Fatal(err) } */ @@ -41,9 +41,9 @@ const ( autoFilePerms = os.FileMode(0600) ) -// errAutoFileClosed is reported when operations attempt to use an autofile +// ErrAutoFileClosed is reported when operations attempt to use an autofile // after it has been closed. -var errAutoFileClosed = errors.New("autofile is closed") +var ErrAutoFileClosed = errors.New("autofile is closed") // AutoFile automatically closes and re-opens file for writing. The file is // automatically setup to close itself every 1s and upon receiving SIGHUP. @@ -155,7 +155,7 @@ func (af *AutoFile) Write(b []byte) (n int, err error) { af.mtx.Lock() defer af.mtx.Unlock() if af.closed { - return 0, fmt.Errorf("write: %w", errAutoFileClosed) + return 0, fmt.Errorf("write: %w", ErrAutoFileClosed) } if af.file == nil { @@ -174,7 +174,7 @@ func (af *AutoFile) Write(b []byte) (n int, err error) { func (af *AutoFile) Sync() error { return af.withLock(func() error { if af.closed { - return fmt.Errorf("sync: %w", errAutoFileClosed) + return fmt.Errorf("sync: %w", ErrAutoFileClosed) } else if af.file == nil { return nil // nothing to sync } @@ -189,13 +189,7 @@ func (af *AutoFile) openFile() error { if err != nil { return err } - // fileInfo, err := file.Stat() - // if err != nil { - // return err - // } - // if fileInfo.Mode() != autoFilePerms { - // return errors.NewErrPermissionsChanged(file.Name(), fileInfo.Mode(), autoFilePerms) - // } + af.file = file return nil } @@ -207,7 +201,7 @@ func (af *AutoFile) Size() (int64, error) { af.mtx.Lock() defer af.mtx.Unlock() if af.closed { - return 0, fmt.Errorf("size: %w", errAutoFileClosed) + return 0, fmt.Errorf("size: %w", ErrAutoFileClosed) } if af.file == nil { diff --git a/internal/libs/autofile/cmd/logjack.go b/internal/libs/autofile/cmd/logjack.go index a9f6cf766..c3c466503 100644 --- a/internal/libs/autofile/cmd/logjack.go +++ b/internal/libs/autofile/cmd/logjack.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "io" + stdlog "log" "os" "os/signal" "strconv" @@ -19,19 +20,26 @@ const Version = "0.0.1" const readBufferSize = 1024 // 1KB at a time // Parse command-line options -func parseFlags() (headPath string, chopSize int64, limitSize int64, version bool) { +func parseFlags() (headPath string, chopSize int64, limitSize int64, version bool, err error) { var flagSet = flag.NewFlagSet(os.Args[0], flag.ExitOnError) var chopSizeStr, limitSizeStr string flagSet.StringVar(&headPath, "head", "logjack.out", "Destination (head) file.") flagSet.StringVar(&chopSizeStr, "chop", "100M", "Move file if greater than this") flagSet.StringVar(&limitSizeStr, "limit", "10G", "Only keep this much (for each specified file). Remove old files.") flagSet.BoolVar(&version, "version", false, "Version") - if err := flagSet.Parse(os.Args[1:]); err != nil { - fmt.Printf("err parsing flag: %v\n", err) - os.Exit(1) + + if err = flagSet.Parse(os.Args[1:]); err != nil { + return + } + + chopSize, err = parseByteSize(chopSizeStr) + if err != nil { + return + } + limitSize, err = parseByteSize(limitSizeStr) + if err != nil { + return } - chopSize = parseBytesize(chopSizeStr) - limitSize = parseBytesize(limitSizeStr) return } @@ -41,22 +49,23 @@ func main() { defer func() { fmt.Println("logjack shutting down") }() // Read options - headPath, chopSize, limitSize, version := parseFlags() + headPath, chopSize, limitSize, version, err := parseFlags() + if err != nil { + stdlog.Fatalf("problem parsing arguments: %q", err.Error()) + } + if version { - fmt.Printf("logjack version %v\n", Version) - return + stdlog.Printf("logjack version %s", Version) } // Open Group group, err := auto.OpenGroup(ctx, log.NewNopLogger(), headPath, auto.GroupHeadSizeLimit(chopSize), auto.GroupTotalSizeLimit(limitSize)) if err != nil { - fmt.Printf("logjack couldn't create output file %v\n", headPath) - os.Exit(1) + stdlog.Fatalf("logjack couldn't create output file %q", headPath) } if err = group.Start(ctx); err != nil { - fmt.Printf("logjack couldn't start with file %v\n", headPath) - os.Exit(1) + stdlog.Fatalf("logjack couldn't start with file %q", headPath) } // Forever read from stdin and write to AutoFile. @@ -65,25 +74,21 @@ func main() { n, err := os.Stdin.Read(buf) if err != nil { if err == io.EOF { - os.Exit(0) - } else { - fmt.Println("logjack errored:", err.Error()) - os.Exit(1) + return } + stdlog.Fatalln("logjack errored:", err.Error()) } _, err = group.Write(buf[:n]) if err != nil { - fmt.Fprintf(os.Stderr, "logjack failed write with error %v\n", headPath) - os.Exit(1) + stdlog.Fatalf("logjack failed write %q with error: %q", headPath, err.Error()) } if err := group.FlushAndSync(); err != nil { - fmt.Fprintf(os.Stderr, "logjack flushsync fail with error %v\n", headPath) - os.Exit(1) + stdlog.Fatalf("logjack flushsync %q fail with error: %q", headPath, err.Error()) } } } -func parseBytesize(chopSize string) int64 { +func parseByteSize(chopSize string) (int64, error) { // Handle suffix multiplier var multiplier int64 = 1 if strings.HasSuffix(chopSize, "T") { @@ -106,8 +111,8 @@ func parseBytesize(chopSize string) int64 { // Parse the numeric part chopSizeInt, err := strconv.Atoi(chopSize) if err != nil { - panic(err) + return 0, err } - return int64(chopSizeInt) * multiplier + return int64(chopSizeInt) * multiplier, nil } diff --git a/internal/libs/autofile/group.go b/internal/libs/autofile/group.go index bb2c41808..81e16feea 100644 --- a/internal/libs/autofile/group.go +++ b/internal/libs/autofile/group.go @@ -69,11 +69,6 @@ type Group struct { minIndex int // Includes head maxIndex int // Includes head, where Head will move to - // close this when the processTicks routine is done. - // this ensures we can cleanup the dir after calling Stop - // and the routine won't be trying to access it anymore - doneProcessTicks chan struct{} - // TODO: When we start deleting files, we need to start tracking GroupReaders // and their dependencies. } @@ -101,7 +96,6 @@ func OpenGroup(ctx context.Context, logger log.Logger, headPath string, groupOpt groupCheckDuration: defaultGroupCheckDuration, minIndex: 0, maxIndex: 0, - doneProcessTicks: make(chan struct{}), } for _, option := range groupOptions { @@ -154,13 +148,6 @@ func (g *Group) OnStop() { } } -// Wait blocks until all internal goroutines are finished. Supposed to be -// called after Stop. -func (g *Group) Wait() { - // wait for processTicks routine to finish - <-g.doneProcessTicks -} - // Close closes the head file. The group must be stopped by this moment. func (g *Group) Close() { if err := g.FlushAndSync(); err != nil { @@ -241,8 +228,6 @@ func (g *Group) FlushAndSync() error { } func (g *Group) processTicks(ctx context.Context) { - defer close(g.doneProcessTicks) - for { select { case <-ctx.Done(): @@ -318,7 +303,8 @@ func (g *Group) checkTotalSizeLimit(ctx context.Context) { } } -// rotateFile causes group to close the current head and assign it some index. +// rotateFile causes group to close the current head and assign it +// some index. Panics if it encounters an error. func (g *Group) rotateFile(ctx context.Context) { g.mtx.Lock() defer g.mtx.Unlock() diff --git a/internal/libs/autofile/group_test.go b/internal/libs/autofile/group_test.go index e20604d82..4f5e346c2 100644 --- a/internal/libs/autofile/group_test.go +++ b/internal/libs/autofile/group_test.go @@ -47,7 +47,7 @@ func TestCheckHeadSizeLimit(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.TestingLogger() + logger := log.NewNopLogger() g := createTestGroupWithHeadSizeLimit(ctx, t, logger, 1000*1000) @@ -116,7 +116,7 @@ func TestCheckHeadSizeLimit(t *testing.T) { } func TestRotateFile(t *testing.T) { - logger := log.TestingLogger() + logger := log.NewNopLogger() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -181,7 +181,7 @@ func TestRotateFile(t *testing.T) { } func TestWrite(t *testing.T) { - logger := log.TestingLogger() + logger := log.NewNopLogger() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -209,7 +209,7 @@ func TestWrite(t *testing.T) { // test that Read reads the required amount of bytes from all the files in the // group and returns no error if n == size of the given slice. func TestGroupReaderRead(t *testing.T) { - logger := log.TestingLogger() + logger := log.NewNopLogger() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -247,7 +247,7 @@ func TestGroupReaderRead(t *testing.T) { // test that Read returns an error if number of bytes read < size of // the given slice. Subsequent call should return 0, io.EOF. func TestGroupReaderRead2(t *testing.T) { - logger := log.TestingLogger() + logger := log.NewNopLogger() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -287,7 +287,7 @@ func TestGroupReaderRead2(t *testing.T) { } func TestMinIndex(t *testing.T) { - logger := log.TestingLogger() + logger := log.NewNopLogger() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -300,7 +300,7 @@ func TestMinIndex(t *testing.T) { } func TestMaxIndex(t *testing.T) { - logger := log.TestingLogger() + logger := log.NewNopLogger() ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/internal/libs/clist/bench_test.go b/internal/libs/clist/bench_test.go index 95973cc76..ee5d836a7 100644 --- a/internal/libs/clist/bench_test.go +++ b/internal/libs/clist/bench_test.go @@ -12,7 +12,7 @@ func BenchmarkDetaching(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { start.removed = true - start.DetachNext() + start.detachNext() start.DetachPrev() tmp := nxt nxt = nxt.Next() diff --git a/internal/libs/clist/clist.go b/internal/libs/clist/clist.go index 99e5f05bf..3969c94cc 100644 --- a/internal/libs/clist/clist.go +++ b/internal/libs/clist/clist.go @@ -44,7 +44,6 @@ waiting on NextWait() (since it's just a read operation). type CElement struct { mtx sync.RWMutex prev *CElement - prevWaitCh chan struct{} next *CElement nextWaitCh chan struct{} removed bool @@ -72,33 +71,6 @@ func (e *CElement) NextWait() *CElement { } } -// Blocking implementation of Prev(). -// May return nil iff CElement was head and got removed. -func (e *CElement) PrevWait() *CElement { - for { - e.mtx.RLock() - prev := e.prev - removed := e.removed - signal := e.prevWaitCh - e.mtx.RUnlock() - - if prev != nil || removed { - return prev - } - - <-signal - } -} - -// PrevWaitChan can be used to wait until Prev becomes not nil. Once it does, -// channel will be closed. -func (e *CElement) PrevWaitChan() <-chan struct{} { - e.mtx.RLock() - defer e.mtx.RUnlock() - - return e.prevWaitCh -} - // NextWaitChan can be used to wait until Next becomes not nil. Once it does, // channel will be closed. func (e *CElement) NextWaitChan() <-chan struct{} { @@ -131,7 +103,7 @@ func (e *CElement) Removed() bool { return isRemoved } -func (e *CElement) DetachNext() { +func (e *CElement) detachNext() { e.mtx.Lock() if !e.removed { e.mtx.Unlock() @@ -153,7 +125,7 @@ func (e *CElement) DetachPrev() { // NOTE: This function needs to be safe for // concurrent goroutines waiting on nextWg. -func (e *CElement) SetNext(newNext *CElement) { +func (e *CElement) setNext(newNext *CElement) { e.mtx.Lock() oldNext := e.next @@ -174,30 +146,20 @@ func (e *CElement) SetNext(newNext *CElement) { // NOTE: This function needs to be safe for // concurrent goroutines waiting on prevWg -func (e *CElement) SetPrev(newPrev *CElement) { +func (e *CElement) setPrev(newPrev *CElement) { e.mtx.Lock() defer e.mtx.Unlock() - oldPrev := e.prev e.prev = newPrev - if oldPrev != nil && newPrev == nil { - e.prevWaitCh = make(chan struct{}) - } - if oldPrev == nil && newPrev != nil { - close(e.prevWaitCh) - } } -func (e *CElement) SetRemoved() { +func (e *CElement) setRemoved() { e.mtx.Lock() defer e.mtx.Unlock() e.removed = true - // This wakes up anyone waiting in either direction. - if e.prev == nil { - close(e.prevWaitCh) - } + // This wakes up anyone waiting. if e.next == nil { close(e.nextWaitCh) } @@ -211,7 +173,6 @@ func (e *CElement) SetRemoved() { // Panics if length grows beyond the max. type CList struct { mtx sync.RWMutex - wg *sync.WaitGroup waitCh chan struct{} head *CElement // first element tail *CElement // last element @@ -250,7 +211,7 @@ func (l *CList) Front() *CElement { return head } -func (l *CList) FrontWait() *CElement { +func (l *CList) frontWait() *CElement { // Loop until the head is non-nil else wait and try again for { l.mtx.RLock() @@ -273,22 +234,6 @@ func (l *CList) Back() *CElement { return back } -func (l *CList) BackWait() *CElement { - for { - l.mtx.RLock() - tail := l.tail - wg := l.wg - l.mtx.RUnlock() - - if tail != nil { - return tail - } - wg.Wait() - // l.tail doesn't necessarily exist here. - // That's why we need to continue a for-loop. - } -} - // WaitChan can be used to wait until Front or Back becomes not nil. Once it // does, channel will be closed. func (l *CList) WaitChan() <-chan struct{} { @@ -305,7 +250,6 @@ func (l *CList) PushBack(v interface{}) *CElement { // Construct a new element e := &CElement{ prev: nil, - prevWaitCh: make(chan struct{}), next: nil, nextWaitCh: make(chan struct{}), removed: false, @@ -326,8 +270,8 @@ func (l *CList) PushBack(v interface{}) *CElement { l.head = e l.tail = e } else { - e.SetPrev(l.tail) // We must init e first. - l.tail.SetNext(e) // This will make e accessible. + e.setPrev(l.tail) // We must init e first. + l.tail.setNext(e) // This will make e accessible. l.tail = e // Update the list. } l.mtx.Unlock() @@ -365,16 +309,16 @@ func (l *CList) Remove(e *CElement) interface{} { if prev == nil { l.head = next } else { - prev.SetNext(next) + prev.setNext(next) } if next == nil { l.tail = prev } else { - next.SetPrev(prev) + next.setPrev(prev) } // Set .Done() on e, otherwise waiters will wait forever. - e.SetRemoved() + e.setRemoved() return e.Value } diff --git a/internal/libs/clist/clist_test.go b/internal/libs/clist/clist_test.go index a0482fc40..a0449a985 100644 --- a/internal/libs/clist/clist_test.go +++ b/internal/libs/clist/clist_test.go @@ -218,7 +218,7 @@ func TestScanRightDeleteRandom(t *testing.T) { default: } if el == nil { - el = l.FrontWait() + el = l.frontWait() restartCounter++ } el = el.Next() @@ -314,30 +314,6 @@ FOR_LOOP: t.Fatalf("number of pushed items (%d) not equal to number of seen items (%d)", pushed, seen) } - // 4) test iterating backwards (PrevWaitChan and Prev) - prev := next - seen = 0 -FOR_LOOP2: - for { - select { - case <-prev.PrevWaitChan(): - prev = prev.Prev() - seen++ - if prev == nil { - t.Fatal("expected PrevWaitChan to block forever on nil when reached first elem") - } - if pushed == seen { - break FOR_LOOP2 - } - - case <-time.After(250 * time.Millisecond): - break FOR_LOOP2 - } - } - - if pushed != seen { - t.Fatalf("number of pushed items (%d) not equal to number of seen items (%d)", pushed, seen) - } } func TestRemoved(t *testing.T) { diff --git a/internal/libs/flowrate/flowrate.go b/internal/libs/flowrate/flowrate.go index c2234669b..aaa54a22c 100644 --- a/internal/libs/flowrate/flowrate.go +++ b/internal/libs/flowrate/flowrate.go @@ -275,3 +275,15 @@ func (m *Monitor) waitNextSample(now time.Duration) time.Duration { } return now } + +// CurrentTransferRate returns the current transfer rate +func (m *Monitor) CurrentTransferRate() int64 { + m.mu.Lock() + defer m.mu.Unlock() + + if m.sLast > m.start && m.active { + return round(m.rEMA) + } + + return 0 +} diff --git a/internal/libs/protoio/writer_test.go b/internal/libs/protoio/writer_test.go index 69867f733..cf1d0a2a4 100644 --- a/internal/libs/protoio/writer_test.go +++ b/internal/libs/protoio/writer_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/libs/protoio" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/types" @@ -25,10 +24,10 @@ func aVote(t testing.TB) *types.Vote { Round: 2, Timestamp: stamp, BlockID: types.BlockID{ - Hash: tmhash.Sum([]byte("blockID_hash")), + Hash: crypto.Checksum([]byte("blockID_hash")), PartSetHeader: types.PartSetHeader{ Total: 1000000, - Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), + Hash: crypto.Checksum([]byte("blockID_part_set_header_hash")), }, }, ValidatorAddress: crypto.AddressHash([]byte("validator_address")), diff --git a/internal/libs/queue/queue_test.go b/internal/libs/queue/queue_test.go index 204c18653..08ecc3955 100644 --- a/internal/libs/queue/queue_test.go +++ b/internal/libs/queue/queue_test.go @@ -167,7 +167,7 @@ func TestWait(t *testing.T) { defer close(done) got, err := q.Wait(ctx) if err != nil { - t.Errorf("Wait: unexpected error: %w", err) + t.Errorf("Wait: unexpected error: %v", err) } else if got != input { t.Errorf("Wait: got %q, want %q", got, input) } diff --git a/internal/mempool/mempool.go b/internal/mempool/mempool.go index 6fcfe86c1..629fa0bda 100644 --- a/internal/mempool/mempool.go +++ b/internal/mempool/mempool.go @@ -260,7 +260,7 @@ func (txmp *TxMempool) CheckTx( return types.ErrTxInCache } - res, err := txmp.proxyAppConn.CheckTx(ctx, abci.RequestCheckTx{Tx: tx}) + res, err := txmp.proxyAppConn.CheckTx(ctx, &abci.RequestCheckTx{Tx: tx}) if err != nil { txmp.cache.Remove(tx) return err @@ -700,7 +700,7 @@ func (txmp *TxMempool) updateReCheckTxs(ctx context.Context) { // Only execute CheckTx if the transaction is not marked as removed which // could happen if the transaction was evicted. if !txmp.txStore.IsTxRemoved(wtx.hash) { - res, err := txmp.proxyAppConn.CheckTx(ctx, abci.RequestCheckTx{ + res, err := txmp.proxyAppConn.CheckTx(ctx, &abci.RequestCheckTx{ Tx: wtx.tx, Type: abci.CheckTxType_Recheck, }) diff --git a/internal/mempool/mempool_bench_test.go b/internal/mempool/mempool_bench_test.go index 088af174a..14fb22197 100644 --- a/internal/mempool/mempool_bench_test.go +++ b/internal/mempool/mempool_bench_test.go @@ -8,15 +8,24 @@ import ( "time" "github.com/stretchr/testify/require" + + abciclient "github.com/tendermint/tendermint/abci/client" + "github.com/tendermint/tendermint/abci/example/kvstore" + "github.com/tendermint/tendermint/libs/log" ) func BenchmarkTxMempool_CheckTx(b *testing.B) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + client := abciclient.NewLocalClient(log.NewNopLogger(), kvstore.NewApplication()) + if err := client.Start(ctx); err != nil { + b.Fatal(err) + } + // setup the cache and the mempool number for hitting GetEvictableTxs during the // benchmark. 5000 is the current default mempool size in the TM config. - txmp := setup(ctx, b, 10000) + txmp := setup(b, client, 10000) txmp.config.Size = 5000 rng := rand.New(rand.NewSource(time.Now().UnixNano())) diff --git a/internal/mempool/mempool_test.go b/internal/mempool/mempool_test.go index e4f604cb1..946377b1c 100644 --- a/internal/mempool/mempool_test.go +++ b/internal/mempool/mempool_test.go @@ -36,7 +36,7 @@ type testTx struct { priority int64 } -func (app *application) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { +func (app *application) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) { var ( priority int64 sender string @@ -47,55 +47,43 @@ func (app *application) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { if len(parts) == 3 { v, err := strconv.ParseInt(string(parts[2]), 10, 64) if err != nil { - return abci.ResponseCheckTx{ + return &abci.ResponseCheckTx{ Priority: priority, Code: 100, GasWanted: 1, - } + }, nil } priority = v sender = string(parts[0]) } else { - return abci.ResponseCheckTx{ + return &abci.ResponseCheckTx{ Priority: priority, Code: 101, GasWanted: 1, - } + }, nil } - return abci.ResponseCheckTx{ + return &abci.ResponseCheckTx{ Priority: priority, Sender: sender, Code: code.CodeTypeOK, GasWanted: 1, - } + }, nil } -func setup(ctx context.Context, t testing.TB, cacheSize int, options ...TxMempoolOption) *TxMempool { +func setup(t testing.TB, app abciclient.Client, cacheSize int, options ...TxMempoolOption) *TxMempool { t.Helper() - var cancel context.CancelFunc - ctx, cancel = context.WithCancel(ctx) - - logger := log.TestingLogger() - - conn := abciclient.NewLocalClient(logger, &application{ - kvstore.NewApplication(), - }) + logger := log.NewNopLogger() cfg, err := config.ResetTestRoot(t.TempDir(), strings.ReplaceAll(t.Name(), "/", "|")) require.NoError(t, err) cfg.Mempool.CacheSize = cacheSize - require.NoError(t, conn.Start(ctx)) - t.Cleanup(func() { - os.RemoveAll(cfg.RootDir) - cancel() - conn.Wait() - }) + t.Cleanup(func() { os.RemoveAll(cfg.RootDir) }) - return NewTxMempool(logger.With("test", t.Name()), cfg.Mempool, conn, options...) + return NewTxMempool(logger.With("test", t.Name()), cfg.Mempool, app, options...) } func checkTxs(ctx context.Context, t *testing.T, txmp *TxMempool, numTxs int, peerID uint16) []testTx { @@ -137,7 +125,13 @@ func TestTxMempool_TxsAvailable(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - txmp := setup(ctx, t, 0) + client := abciclient.NewLocalClient(log.NewNopLogger(), &application{Application: kvstore.NewApplication()}) + if err := client.Start(ctx); err != nil { + t.Fatal(err) + } + t.Cleanup(client.Wait) + + txmp := setup(t, client, 0) txmp.EnableTxsAvailable() ensureNoTxFire := func() { @@ -194,7 +188,13 @@ func TestTxMempool_Size(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - txmp := setup(ctx, t, 0) + client := abciclient.NewLocalClient(log.NewNopLogger(), &application{Application: kvstore.NewApplication()}) + if err := client.Start(ctx); err != nil { + t.Fatal(err) + } + t.Cleanup(client.Wait) + + txmp := setup(t, client, 0) txs := checkTxs(ctx, t, txmp, 100, 0) require.Equal(t, len(txs), txmp.Size()) require.Equal(t, int64(5690), txmp.SizeBytes()) @@ -221,7 +221,13 @@ func TestTxMempool_Flush(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - txmp := setup(ctx, t, 0) + client := abciclient.NewLocalClient(log.NewNopLogger(), &application{Application: kvstore.NewApplication()}) + if err := client.Start(ctx); err != nil { + t.Fatal(err) + } + t.Cleanup(client.Wait) + + txmp := setup(t, client, 0) txs := checkTxs(ctx, t, txmp, 100, 0) require.Equal(t, len(txs), txmp.Size()) require.Equal(t, int64(5690), txmp.SizeBytes()) @@ -249,7 +255,13 @@ func TestTxMempool_ReapMaxBytesMaxGas(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - txmp := setup(ctx, t, 0) + client := abciclient.NewLocalClient(log.NewNopLogger(), &application{Application: kvstore.NewApplication()}) + if err := client.Start(ctx); err != nil { + t.Fatal(err) + } + t.Cleanup(client.Wait) + + txmp := setup(t, client, 0) tTxs := checkTxs(ctx, t, txmp, 100, 0) // all txs request 1 gas unit require.Equal(t, len(tTxs), txmp.Size()) require.Equal(t, int64(5690), txmp.SizeBytes()) @@ -302,7 +314,13 @@ func TestTxMempool_ReapMaxTxs(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - txmp := setup(ctx, t, 0) + client := abciclient.NewLocalClient(log.NewNopLogger(), &application{Application: kvstore.NewApplication()}) + if err := client.Start(ctx); err != nil { + t.Fatal(err) + } + t.Cleanup(client.Wait) + + txmp := setup(t, client, 0) tTxs := checkTxs(ctx, t, txmp, 100, 0) require.Equal(t, len(tTxs), txmp.Size()) require.Equal(t, int64(5690), txmp.SizeBytes()) @@ -354,7 +372,12 @@ func TestTxMempool_CheckTxExceedsMaxSize(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - txmp := setup(ctx, t, 0) + client := abciclient.NewLocalClient(log.NewNopLogger(), &application{Application: kvstore.NewApplication()}) + if err := client.Start(ctx); err != nil { + t.Fatal(err) + } + t.Cleanup(client.Wait) + txmp := setup(t, client, 0) rng := rand.New(rand.NewSource(time.Now().UnixNano())) tx := make([]byte, txmp.config.MaxTxBytes+1) @@ -374,7 +397,13 @@ func TestTxMempool_CheckTxSamePeer(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - txmp := setup(ctx, t, 100) + client := abciclient.NewLocalClient(log.NewNopLogger(), &application{Application: kvstore.NewApplication()}) + if err := client.Start(ctx); err != nil { + t.Fatal(err) + } + t.Cleanup(client.Wait) + + txmp := setup(t, client, 100) peerID := uint16(1) rng := rand.New(rand.NewSource(time.Now().UnixNano())) @@ -392,7 +421,13 @@ func TestTxMempool_CheckTxSameSender(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - txmp := setup(ctx, t, 100) + client := abciclient.NewLocalClient(log.NewNopLogger(), &application{Application: kvstore.NewApplication()}) + if err := client.Start(ctx); err != nil { + t.Fatal(err) + } + t.Cleanup(client.Wait) + + txmp := setup(t, client, 100) peerID := uint16(1) rng := rand.New(rand.NewSource(time.Now().UnixNano())) @@ -417,7 +452,13 @@ func TestTxMempool_ConcurrentTxs(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - txmp := setup(ctx, t, 100) + client := abciclient.NewLocalClient(log.NewNopLogger(), &application{Application: kvstore.NewApplication()}) + if err := client.Start(ctx); err != nil { + t.Fatal(err) + } + t.Cleanup(client.Wait) + + txmp := setup(t, client, 100) rng := rand.New(rand.NewSource(time.Now().UnixNano())) checkTxDone := make(chan struct{}) @@ -484,7 +525,13 @@ func TestTxMempool_ExpiredTxs_NumBlocks(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - txmp := setup(ctx, t, 500) + client := abciclient.NewLocalClient(log.NewNopLogger(), &application{Application: kvstore.NewApplication()}) + if err := client.Start(ctx); err != nil { + t.Fatal(err) + } + t.Cleanup(client.Wait) + + txmp := setup(t, client, 500) txmp.height = 100 txmp.config.TTLNumBlocks = 10 @@ -556,10 +603,16 @@ func TestTxMempool_CheckTxPostCheckError(t *testing.T) { ctx, cancel := context.WithCancel(ctx) defer cancel() + client := abciclient.NewLocalClient(log.NewNopLogger(), &application{Application: kvstore.NewApplication()}) + if err := client.Start(ctx); err != nil { + t.Fatal(err) + } + t.Cleanup(client.Wait) + postCheckFn := func(_ types.Tx, _ *abci.ResponseCheckTx) error { return testCase.err } - txmp := setup(ctx, t, 0, WithPostCheck(postCheckFn)) + txmp := setup(t, client, 0, WithPostCheck(postCheckFn)) rng := rand.New(rand.NewSource(time.Now().UnixNano())) tx := make([]byte, txmp.config.MaxTxBytes-1) _, err := rng.Read(tx) diff --git a/internal/mempool/mock/mempool.go b/internal/mempool/mock/mempool.go deleted file mode 100644 index 1a0cd75ae..000000000 --- a/internal/mempool/mock/mempool.go +++ /dev/null @@ -1,46 +0,0 @@ -package mock - -import ( - "context" - - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/internal/libs/clist" - "github.com/tendermint/tendermint/internal/mempool" - "github.com/tendermint/tendermint/types" -) - -// Mempool is an empty implementation of a Mempool, useful for testing. -type Mempool struct{} - -var _ Mempool = Mempool{} - -func (Mempool) Lock() {} -func (Mempool) Unlock() {} -func (Mempool) Size() int { return 0 } -func (Mempool) CheckTx(context.Context, types.Tx, func(*abci.ResponseCheckTx), mempool.TxInfo) error { - return nil -} -func (Mempool) RemoveTxByKey(txKey types.TxKey) error { return nil } -func (Mempool) ReapMaxBytesMaxGas(_, _ int64) types.Txs { return types.Txs{} } -func (Mempool) ReapMaxTxs(n int) types.Txs { return types.Txs{} } -func (Mempool) Update( - _ context.Context, - _ int64, - _ types.Txs, - _ []*abci.ExecTxResult, - _ mempool.PreCheckFunc, - _ mempool.PostCheckFunc, -) error { - return nil -} -func (Mempool) Flush() {} -func (Mempool) FlushAppConn(ctx context.Context) error { return nil } -func (Mempool) TxsAvailable() <-chan struct{} { return make(chan struct{}) } -func (Mempool) EnableTxsAvailable() {} -func (Mempool) SizeBytes() int64 { return 0 } - -func (Mempool) TxsFront() *clist.CElement { return nil } -func (Mempool) TxsWaitChan() <-chan struct{} { return nil } - -func (Mempool) InitWAL() error { return nil } -func (Mempool) CloseWAL() {} diff --git a/internal/mempool/mocks/mempool.go b/internal/mempool/mocks/mempool.go new file mode 100644 index 000000000..b82d7d63e --- /dev/null +++ b/internal/mempool/mocks/mempool.go @@ -0,0 +1,184 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + context "context" + + abcitypes "github.com/tendermint/tendermint/abci/types" + + mempool "github.com/tendermint/tendermint/internal/mempool" + + mock "github.com/stretchr/testify/mock" + + testing "testing" + + types "github.com/tendermint/tendermint/types" +) + +// Mempool is an autogenerated mock type for the Mempool type +type Mempool struct { + mock.Mock +} + +// CheckTx provides a mock function with given fields: ctx, tx, callback, txInfo +func (_m *Mempool) CheckTx(ctx context.Context, tx types.Tx, callback func(*abcitypes.ResponseCheckTx), txInfo mempool.TxInfo) error { + ret := _m.Called(ctx, tx, callback, txInfo) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, types.Tx, func(*abcitypes.ResponseCheckTx), mempool.TxInfo) error); ok { + r0 = rf(ctx, tx, callback, txInfo) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// EnableTxsAvailable provides a mock function with given fields: +func (_m *Mempool) EnableTxsAvailable() { + _m.Called() +} + +// Flush provides a mock function with given fields: +func (_m *Mempool) Flush() { + _m.Called() +} + +// FlushAppConn provides a mock function with given fields: _a0 +func (_m *Mempool) FlushAppConn(_a0 context.Context) error { + ret := _m.Called(_a0) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Lock provides a mock function with given fields: +func (_m *Mempool) Lock() { + _m.Called() +} + +// ReapMaxBytesMaxGas provides a mock function with given fields: maxBytes, maxGas +func (_m *Mempool) ReapMaxBytesMaxGas(maxBytes int64, maxGas int64) types.Txs { + ret := _m.Called(maxBytes, maxGas) + + var r0 types.Txs + if rf, ok := ret.Get(0).(func(int64, int64) types.Txs); ok { + r0 = rf(maxBytes, maxGas) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(types.Txs) + } + } + + return r0 +} + +// ReapMaxTxs provides a mock function with given fields: max +func (_m *Mempool) ReapMaxTxs(max int) types.Txs { + ret := _m.Called(max) + + var r0 types.Txs + if rf, ok := ret.Get(0).(func(int) types.Txs); ok { + r0 = rf(max) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(types.Txs) + } + } + + return r0 +} + +// RemoveTxByKey provides a mock function with given fields: txKey +func (_m *Mempool) RemoveTxByKey(txKey types.TxKey) error { + ret := _m.Called(txKey) + + var r0 error + if rf, ok := ret.Get(0).(func(types.TxKey) error); ok { + r0 = rf(txKey) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Size provides a mock function with given fields: +func (_m *Mempool) Size() int { + ret := _m.Called() + + var r0 int + if rf, ok := ret.Get(0).(func() int); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int) + } + + return r0 +} + +// SizeBytes provides a mock function with given fields: +func (_m *Mempool) SizeBytes() int64 { + ret := _m.Called() + + var r0 int64 + if rf, ok := ret.Get(0).(func() int64); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int64) + } + + return r0 +} + +// TxsAvailable provides a mock function with given fields: +func (_m *Mempool) TxsAvailable() <-chan struct{} { + ret := _m.Called() + + var r0 <-chan struct{} + if rf, ok := ret.Get(0).(func() <-chan struct{}); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(<-chan struct{}) + } + } + + return r0 +} + +// Unlock provides a mock function with given fields: +func (_m *Mempool) Unlock() { + _m.Called() +} + +// Update provides a mock function with given fields: ctx, blockHeight, blockTxs, txResults, newPreFn, newPostFn +func (_m *Mempool) Update(ctx context.Context, blockHeight int64, blockTxs types.Txs, txResults []*abcitypes.ExecTxResult, newPreFn mempool.PreCheckFunc, newPostFn mempool.PostCheckFunc) error { + ret := _m.Called(ctx, blockHeight, blockTxs, txResults, newPreFn, newPostFn) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int64, types.Txs, []*abcitypes.ExecTxResult, mempool.PreCheckFunc, mempool.PostCheckFunc) error); ok { + r0 = rf(ctx, blockHeight, blockTxs, txResults, newPreFn, newPostFn) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewMempool creates a new instance of Mempool. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewMempool(t testing.TB) *Mempool { + mock := &Mempool{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/mempool/reactor.go b/internal/mempool/reactor.go index 8f98d97f1..ae578e70a 100644 --- a/internal/mempool/reactor.go +++ b/internal/mempool/reactor.go @@ -40,13 +40,9 @@ type Reactor struct { mempool *TxMempool ids *IDs - // XXX: Currently, this is the only way to get information about a peer. Ideally, - // we rely on message-oriented communication to get necessary peer data. - // ref: https://github.com/tendermint/tendermint/issues/5670 - peerMgr PeerManager - - mempoolCh *p2p.Channel - peerUpdates *p2p.PeerUpdates + getPeerHeight func(types.NodeID) int64 + peerEvents p2p.PeerEventSubscriber + chCreator p2p.ChannelCreator // observePanic is a function for observing panics that were recovered in methods on // Reactor. observePanic is called with the recovered value. @@ -58,34 +54,27 @@ type Reactor struct { // NewReactor returns a reference to a new reactor. func NewReactor( - ctx context.Context, logger log.Logger, cfg *config.MempoolConfig, - peerMgr PeerManager, txmp *TxMempool, chCreator p2p.ChannelCreator, - peerUpdates *p2p.PeerUpdates, -) (*Reactor, error) { - - ch, err := chCreator(ctx, getChannelDescriptor(cfg)) - if err != nil { - return nil, err - } - + peerEvents p2p.PeerEventSubscriber, + getPeerHeight func(types.NodeID) int64, +) *Reactor { r := &Reactor{ - logger: logger, - cfg: cfg, - peerMgr: peerMgr, - mempool: txmp, - ids: NewMempoolIDs(), - mempoolCh: ch, - peerUpdates: peerUpdates, - peerRoutines: make(map[types.NodeID]context.CancelFunc), - observePanic: defaultObservePanic, + logger: logger, + cfg: cfg, + mempool: txmp, + ids: NewMempoolIDs(), + chCreator: chCreator, + peerEvents: peerEvents, + getPeerHeight: getPeerHeight, + peerRoutines: make(map[types.NodeID]context.CancelFunc), + observePanic: defaultObservePanic, } r.BaseService = *service.NewBaseService(logger, "Mempool", r) - return r, nil + return r } func defaultObservePanic(r interface{}) {} @@ -106,6 +95,7 @@ func getChannelDescriptor(cfg *config.MempoolConfig) *p2p.ChannelDescriptor { Priority: 5, RecvMessageCapacity: batchMsg.Size(), RecvBufferCapacity: 128, + Name: "mempool", } } @@ -118,8 +108,13 @@ func (r *Reactor) OnStart(ctx context.Context) error { r.logger.Info("tx broadcasting is disabled") } - go r.processMempoolCh(ctx) - go r.processPeerUpdates(ctx) + ch, err := r.chCreator(ctx, getChannelDescriptor(r.cfg)) + if err != nil { + return err + } + + go r.processMempoolCh(ctx, ch) + go r.processPeerUpdates(ctx, r.peerEvents(ctx), ch) return nil } @@ -174,7 +169,7 @@ func (r *Reactor) handleMempoolMessage(ctx context.Context, envelope *p2p.Envelo // handleMessage handles an Envelope sent from a peer on a specific p2p Channel. // It will handle errors and any possible panics gracefully. A caller can handle // any error returned by sending a PeerError on the respective channel. -func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelope *p2p.Envelope) (err error) { +func (r *Reactor) handleMessage(ctx context.Context, envelope *p2p.Envelope) (err error) { defer func() { if e := recover(); e != nil { r.observePanic(e) @@ -189,26 +184,25 @@ func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelop r.logger.Debug("received message", "peer", envelope.From) - switch chID { + switch envelope.ChannelID { case MempoolChannel: err = r.handleMempoolMessage(ctx, envelope) - default: - err = fmt.Errorf("unknown channel ID (%d) for envelope (%T)", chID, envelope.Message) + err = fmt.Errorf("unknown channel ID (%d) for envelope (%T)", envelope.ChannelID, envelope.Message) } - return err + return } // processMempoolCh implements a blocking event loop where we listen for p2p // Envelope messages from the mempoolCh. -func (r *Reactor) processMempoolCh(ctx context.Context) { - iter := r.mempoolCh.Receive(ctx) +func (r *Reactor) processMempoolCh(ctx context.Context, mempoolCh *p2p.Channel) { + iter := mempoolCh.Receive(ctx) for iter.Next(ctx) { envelope := iter.Envelope() - if err := r.handleMessage(ctx, r.mempoolCh.ID, envelope); err != nil { - r.logger.Error("failed to process message", "ch_id", r.mempoolCh.ID, "envelope", envelope, "err", err) - if serr := r.mempoolCh.SendError(ctx, p2p.PeerError{ + if err := r.handleMessage(ctx, envelope); err != nil { + r.logger.Error("failed to process message", "ch_id", envelope.ChannelID, "envelope", envelope, "err", err) + if serr := mempoolCh.SendError(ctx, p2p.PeerError{ NodeID: envelope.From, Err: err, }); serr != nil { @@ -223,7 +217,7 @@ func (r *Reactor) processMempoolCh(ctx context.Context) { // goroutine or not. If not, we start one for the newly added peer. For down or // removed peers, we remove the peer from the mempool peer ID set and signal to // stop the tx broadcasting goroutine. -func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpdate) { +func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpdate, mempoolCh *p2p.Channel) { r.logger.Debug("received peer update", "peer", peerUpdate.NodeID, "status", peerUpdate.Status) r.mtx.Lock() @@ -251,7 +245,7 @@ func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpda r.ids.ReserveForPeer(peerUpdate.NodeID) // start a broadcast routine ensuring all txs are forwarded to the peer - go r.broadcastTxRoutine(pctx, peerUpdate.NodeID) + go r.broadcastTxRoutine(pctx, peerUpdate.NodeID, mempoolCh) } } @@ -272,18 +266,18 @@ func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpda // processPeerUpdates initiates a blocking process where we listen for and handle // PeerUpdate messages. When the reactor is stopped, we will catch the signal and // close the p2p PeerUpdatesCh gracefully. -func (r *Reactor) processPeerUpdates(ctx context.Context) { +func (r *Reactor) processPeerUpdates(ctx context.Context, peerUpdates *p2p.PeerUpdates, mempoolCh *p2p.Channel) { for { select { case <-ctx.Done(): return - case peerUpdate := <-r.peerUpdates.Updates(): - r.processPeerUpdate(ctx, peerUpdate) + case peerUpdate := <-peerUpdates.Updates(): + r.processPeerUpdate(ctx, peerUpdate, mempoolCh) } } } -func (r *Reactor) broadcastTxRoutine(ctx context.Context, peerID types.NodeID) { +func (r *Reactor) broadcastTxRoutine(ctx context.Context, peerID types.NodeID, mempoolCh *p2p.Channel) { peerMempoolID := r.ids.GetForPeer(peerID) var nextGossipTx *clist.CElement @@ -324,8 +318,8 @@ func (r *Reactor) broadcastTxRoutine(ctx context.Context, peerID types.NodeID) { memTx := nextGossipTx.Value.(*WrappedTx) - if r.peerMgr != nil { - height := r.peerMgr.GetHeight(peerID) + if r.getPeerHeight != nil { + height := r.getPeerHeight(peerID) if height > 0 && height < memTx.height-1 { // allow for a lag of one block time.Sleep(PeerCatchupSleepIntervalMS * time.Millisecond) @@ -338,7 +332,7 @@ func (r *Reactor) broadcastTxRoutine(ctx context.Context, peerID types.NodeID) { if ok := r.mempool.txStore.TxHasPeer(memTx.hash, peerMempoolID); !ok { // Send the mempool tx to the corresponding peer. Note, the peer may be // behind and thus would not be able to process the mempool tx correctly. - if err := r.mempoolCh.Send(ctx, p2p.Envelope{ + if err := mempoolCh.Send(ctx, p2p.Envelope{ To: peerID, Message: &protomem.Txs{ Txs: [][]byte{memTx.tx}, diff --git a/internal/mempool/reactor_test.go b/internal/mempool/reactor_test.go index 04e51ca8d..8ceae2013 100644 --- a/internal/mempool/reactor_test.go +++ b/internal/mempool/reactor_test.go @@ -13,6 +13,7 @@ import ( "github.com/fortytw2/leaktest" "github.com/stretchr/testify/require" + abciclient "github.com/tendermint/tendermint/abci/client" "github.com/tendermint/tendermint/abci/example/kvstore" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/config" @@ -39,7 +40,7 @@ type reactorTestSuite struct { nodes []types.NodeID } -func setupReactors(ctx context.Context, t *testing.T, numNodes int, chBuf uint) *reactorTestSuite { +func setupReactors(ctx context.Context, t *testing.T, logger log.Logger, numNodes int, chBuf uint) *reactorTestSuite { t.Helper() cfg, err := config.ResetTestRoot(t.TempDir(), strings.ReplaceAll(t.Name(), "/", "|")) @@ -63,7 +64,11 @@ func setupReactors(ctx context.Context, t *testing.T, numNodes int, chBuf uint) for nodeID := range rts.network.Nodes { rts.kvstores[nodeID] = kvstore.NewApplication() - mempool := setup(ctx, t, 0) + client := abciclient.NewLocalClient(logger, rts.kvstores[nodeID]) + require.NoError(t, client.Start(ctx)) + t.Cleanup(client.Wait) + + mempool := setup(t, client, 0) rts.mempools[nodeID] = mempool rts.peerChans[nodeID] = make(chan p2p.PeerUpdate, chBuf) @@ -74,17 +79,14 @@ func setupReactors(ctx context.Context, t *testing.T, numNodes int, chBuf uint) return rts.mempoolChannels[nodeID], nil } - rts.reactors[nodeID], err = NewReactor( - ctx, + rts.reactors[nodeID] = NewReactor( rts.logger.With("nodeID", nodeID), cfg.Mempool, - rts.network.Nodes[nodeID].PeerManager, mempool, chCreator, - rts.peerUpdates[nodeID], + func(ctx context.Context) *p2p.PeerUpdates { return rts.peerUpdates[nodeID] }, + rts.network.Nodes[nodeID].PeerManager.GetHeight, ) - - require.NoError(t, err) rts.nodes = append(rts.nodes, nodeID) require.NoError(t, rts.reactors[nodeID].Start(ctx)) @@ -134,14 +136,14 @@ func (rts *reactorTestSuite) waitForTxns(t *testing.T, txs []types.Tx, ids ...ty } wg.Add(1) - go func(pool *TxMempool) { + go func(name types.NodeID, pool *TxMempool) { defer wg.Done() require.Eventually(t, func() bool { return len(txs) == pool.Size() }, time.Minute, 250*time.Millisecond, - "ntx=%d, size=%d", len(txs), pool.Size(), + "node=%q, ntx=%d, size=%d", name, len(txs), pool.Size(), ) - }(pool) + }(name, pool) } wg.Wait() } @@ -151,7 +153,9 @@ func TestReactorBroadcastDoesNotPanic(t *testing.T) { defer cancel() const numNodes = 2 - rts := setupReactors(ctx, t, numNodes, 0) + + logger := log.NewNopLogger() + rts := setupReactors(ctx, t, logger, numNodes, 0) observePanic := func(r interface{}) { t.Fatal("panic detected in reactor") @@ -172,7 +176,7 @@ func TestReactorBroadcastDoesNotPanic(t *testing.T) { // run the router rts.start(ctx, t) - go primaryReactor.broadcastTxRoutine(ctx, secondary) + go primaryReactor.broadcastTxRoutine(ctx, secondary, rts.mempoolChannels[primary]) wg := &sync.WaitGroup{} for i := 0; i < 50; i++ { @@ -189,12 +193,14 @@ func TestReactorBroadcastDoesNotPanic(t *testing.T) { } func TestReactorBroadcastTxs(t *testing.T) { - numTxs := 1000 - numNodes := 10 + numTxs := 512 + numNodes := 4 ctx, cancel := context.WithCancel(context.Background()) defer cancel() - rts := setupReactors(ctx, t, numNodes, uint(numTxs)) + logger := log.NewNopLogger() + + rts := setupReactors(ctx, t, logger, numNodes, uint(numTxs)) primary := rts.nodes[0] secondaries := rts.nodes[1:] @@ -218,7 +224,8 @@ func TestReactorConcurrency(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - rts := setupReactors(ctx, t, numNodes, 0) + logger := log.NewNopLogger() + rts := setupReactors(ctx, t, logger, numNodes, 0) primary := rts.nodes[0] secondary := rts.nodes[1] @@ -276,7 +283,8 @@ func TestReactorNoBroadcastToSender(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - rts := setupReactors(ctx, t, numNodes, uint(numTxs)) + logger := log.NewNopLogger() + rts := setupReactors(ctx, t, logger, numNodes, uint(numTxs)) primary := rts.nodes[0] secondary := rts.nodes[1] @@ -300,7 +308,9 @@ func TestReactor_MaxTxBytes(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - rts := setupReactors(ctx, t, numNodes, 0) + logger := log.NewNopLogger() + + rts := setupReactors(ctx, t, logger, numNodes, 0) primary := rts.nodes[0] secondary := rts.nodes[1] @@ -336,7 +346,8 @@ func TestDontExhaustMaxActiveIDs(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - rts := setupReactors(ctx, t, 1, MaxActiveIDs+1) + logger := log.NewNopLogger() + rts := setupReactors(ctx, t, logger, 1, MaxActiveIDs+1) nodeID := rts.nodes[0] @@ -388,7 +399,9 @@ func TestBroadcastTxForPeerStopsWhenPeerStops(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - rts := setupReactors(ctx, t, 2, 2) + logger := log.NewNopLogger() + + rts := setupReactors(ctx, t, logger, 2, 2) primary := rts.nodes[0] secondary := rts.nodes[1] diff --git a/internal/mempool/types.go b/internal/mempool/types.go index c2124d538..a51d286e2 100644 --- a/internal/mempool/types.go +++ b/internal/mempool/types.go @@ -23,6 +23,8 @@ const ( MaxActiveIDs = math.MaxUint16 ) +//go:generate ../../scripts/mockery_generate.sh Mempool + // Mempool defines the mempool interface. // // Updates to the mempool need to be synchronized with committing a block so diff --git a/internal/p2p/README.md b/internal/p2p/README.md index 9ba7303fa..16ad1d5f6 100644 --- a/internal/p2p/README.md +++ b/internal/p2p/README.md @@ -7,5 +7,5 @@ Docs: - [Connection](https://docs.tendermint.com/master/spec/p2p/connection.html) for details on how connections and multiplexing work - [Peer](https://docs.tendermint.com/master/spec/p2p/node.html) for details on peer ID, handshakes, and peer exchange - [Node](https://docs.tendermint.com/master/spec/p2p/node.html) for details about different types of nodes and how they should work -- [Pex](https://docs.tendermint.com/master/spec/reactors/pex/pex.html) for details on peer discovery and exchange +- [Pex](https://docs.tendermint.com/master/spec/p2p/messages/pex.html) for details on peer discovery and exchange - [Config](https://docs.tendermint.com/master/spec/p2p/config.html) for details on some config option diff --git a/internal/p2p/address.go b/internal/p2p/address.go index 7c084216e..0f4066faf 100644 --- a/internal/p2p/address.go +++ b/internal/p2p/address.go @@ -97,7 +97,7 @@ func ParseNodeAddress(urlString string) (NodeAddress, error) { // Resolve resolves a NodeAddress into a set of Endpoints, by expanding // out a DNS hostname to IP addresses. -func (a NodeAddress) Resolve(ctx context.Context) ([]Endpoint, error) { +func (a NodeAddress) Resolve(ctx context.Context) ([]*Endpoint, error) { if a.Protocol == "" { return nil, errors.New("address has no protocol") } @@ -109,7 +109,7 @@ func (a NodeAddress) Resolve(ctx context.Context) ([]Endpoint, error) { if a.NodeID == "" { return nil, errors.New("local address has no node ID") } - return []Endpoint{{ + return []*Endpoint{{ Protocol: a.Protocol, Path: string(a.NodeID), }}, nil @@ -119,9 +119,9 @@ func (a NodeAddress) Resolve(ctx context.Context) ([]Endpoint, error) { if err != nil { return nil, err } - endpoints := make([]Endpoint, len(ips)) + endpoints := make([]*Endpoint, len(ips)) for i, ip := range ips { - endpoints[i] = Endpoint{ + endpoints[i] = &Endpoint{ Protocol: a.Protocol, IP: ip, Port: a.Port, diff --git a/internal/p2p/address_test.go b/internal/p2p/address_test.go index d5f9e498e..d4c745d8d 100644 --- a/internal/p2p/address_test.go +++ b/internal/p2p/address_test.go @@ -210,71 +210,71 @@ func TestNodeAddress_Resolve(t *testing.T) { testcases := []struct { address p2p.NodeAddress - expect p2p.Endpoint + expect *p2p.Endpoint ok bool }{ // Valid networked addresses (with hostname). { p2p.NodeAddress{Protocol: "tcp", Hostname: "127.0.0.1", Port: 80, Path: "/path"}, - p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(127, 0, 0, 1), Port: 80, Path: "/path"}, + &p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(127, 0, 0, 1), Port: 80, Path: "/path"}, true, }, { p2p.NodeAddress{Protocol: "tcp", Hostname: "localhost", Port: 80, Path: "/path"}, - p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(127, 0, 0, 1), Port: 80, Path: "/path"}, + &p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(127, 0, 0, 1), Port: 80, Path: "/path"}, true, }, { p2p.NodeAddress{Protocol: "tcp", Hostname: "localhost", Port: 80, Path: "/path"}, - p2p.Endpoint{Protocol: "tcp", IP: net.IPv6loopback, Port: 80, Path: "/path"}, + &p2p.Endpoint{Protocol: "tcp", IP: net.IPv6loopback, Port: 80, Path: "/path"}, true, }, { p2p.NodeAddress{Protocol: "tcp", Hostname: "127.0.0.1"}, - p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(127, 0, 0, 1)}, + &p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(127, 0, 0, 1)}, true, }, { p2p.NodeAddress{Protocol: "tcp", Hostname: "::1"}, - p2p.Endpoint{Protocol: "tcp", IP: net.IPv6loopback}, + &p2p.Endpoint{Protocol: "tcp", IP: net.IPv6loopback}, true, }, { p2p.NodeAddress{Protocol: "tcp", Hostname: "8.8.8.8"}, - p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(8, 8, 8, 8)}, + &p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(8, 8, 8, 8)}, true, }, { p2p.NodeAddress{Protocol: "tcp", Hostname: "2001:0db8::ff00:0042:8329"}, - p2p.Endpoint{Protocol: "tcp", IP: []byte{ + &p2p.Endpoint{Protocol: "tcp", IP: []byte{ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x42, 0x83, 0x29}}, true, }, { p2p.NodeAddress{Protocol: "tcp", Hostname: "some.missing.host.tendermint.com"}, - p2p.Endpoint{}, + &p2p.Endpoint{}, false, }, // Valid non-networked addresses. { p2p.NodeAddress{Protocol: "memory", NodeID: id}, - p2p.Endpoint{Protocol: "memory", Path: string(id)}, + &p2p.Endpoint{Protocol: "memory", Path: string(id)}, true, }, { p2p.NodeAddress{Protocol: "memory", NodeID: id, Path: string(id)}, - p2p.Endpoint{Protocol: "memory", Path: string(id)}, + &p2p.Endpoint{Protocol: "memory", Path: string(id)}, true, }, // Invalid addresses. - {p2p.NodeAddress{}, p2p.Endpoint{}, false}, - {p2p.NodeAddress{Hostname: "127.0.0.1"}, p2p.Endpoint{}, false}, - {p2p.NodeAddress{Protocol: "tcp", Hostname: "127.0.0.1:80"}, p2p.Endpoint{}, false}, - {p2p.NodeAddress{Protocol: "memory"}, p2p.Endpoint{}, false}, - {p2p.NodeAddress{Protocol: "memory", Path: string(id)}, p2p.Endpoint{}, false}, - {p2p.NodeAddress{Protocol: "tcp", Hostname: "💥"}, p2p.Endpoint{}, false}, + {p2p.NodeAddress{}, &p2p.Endpoint{}, false}, + {p2p.NodeAddress{Hostname: "127.0.0.1"}, &p2p.Endpoint{}, false}, + {p2p.NodeAddress{Protocol: "tcp", Hostname: "127.0.0.1:80"}, &p2p.Endpoint{}, false}, + {p2p.NodeAddress{Protocol: "memory"}, &p2p.Endpoint{}, false}, + {p2p.NodeAddress{Protocol: "memory", Path: string(id)}, &p2p.Endpoint{}, false}, + {p2p.NodeAddress{Protocol: "tcp", Hostname: "💥"}, &p2p.Endpoint{}, false}, } for _, tc := range testcases { tc := tc diff --git a/internal/p2p/channel.go b/internal/p2p/channel.go index d7dad4d3b..d3d7d104f 100644 --- a/internal/p2p/channel.go +++ b/internal/p2p/channel.go @@ -59,24 +59,17 @@ type Channel struct { outCh chan<- Envelope // outbound messages (reactors to peers) errCh chan<- PeerError // peer error reporting - messageType proto.Message // the channel's message type, used for unmarshaling + name string } // NewChannel creates a new channel. It is primarily for internal and test // use, reactors should use Router.OpenChannel(). -func NewChannel( - id ChannelID, - messageType proto.Message, - inCh <-chan Envelope, - outCh chan<- Envelope, - errCh chan<- PeerError, -) *Channel { +func NewChannel(id ChannelID, inCh <-chan Envelope, outCh chan<- Envelope, errCh chan<- PeerError) *Channel { return &Channel{ - ID: id, - messageType: messageType, - inCh: inCh, - outCh: outCh, - errCh: errCh, + ID: id, + inCh: inCh, + outCh: outCh, + errCh: errCh, } } @@ -102,6 +95,8 @@ func (ch *Channel) SendError(ctx context.Context, pe PeerError) error { } } +func (ch *Channel) String() string { return fmt.Sprintf("p2p.Channel<%d:%s>", ch.ID, ch.name) } + // Receive returns a new unbuffered iterator to receive messages from ch. // The iterator runs until ctx ends. func (ch *Channel) Receive(ctx context.Context) *ChannelIterator { diff --git a/internal/p2p/conn/conn_go110.go b/internal/p2p/conn/conn_go110.go deleted file mode 100644 index 459c3169b..000000000 --- a/internal/p2p/conn/conn_go110.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build go1.10 -// +build go1.10 - -package conn - -// Go1.10 has a proper net.Conn implementation that -// has the SetDeadline method implemented as per -// https://github.com/golang/go/commit/e2dd8ca946be884bb877e074a21727f1a685a706 -// lest we run into problems like -// https://github.com/tendermint/tendermint/issues/851 - -import "net" - -func NetPipe() (net.Conn, net.Conn) { - return net.Pipe() -} diff --git a/internal/p2p/conn/conn_notgo110.go b/internal/p2p/conn/conn_notgo110.go deleted file mode 100644 index 21dffad2c..000000000 --- a/internal/p2p/conn/conn_notgo110.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build !go1.10 -// +build !go1.10 - -package conn - -import ( - "net" - "time" -) - -// Only Go1.10 has a proper net.Conn implementation that -// has the SetDeadline method implemented as per -// https://github.com/golang/go/commit/e2dd8ca946be884bb877e074a21727f1a685a706 -// lest we run into problems like -// https://github.com/tendermint/tendermint/issues/851 -// so for go versions < Go1.10 use our custom net.Conn creator -// that doesn't return an `Unimplemented error` for net.Conn. -// Before https://github.com/tendermint/tendermint/commit/49faa79bdce5663894b3febbf4955fb1d172df04 -// we hadn't cared about errors from SetDeadline so swallow them up anyways. -type pipe struct { - net.Conn -} - -func (p *pipe) SetDeadline(t time.Time) error { - return nil -} - -func NetPipe() (net.Conn, net.Conn) { - p1, p2 := net.Pipe() - return &pipe{p1}, &pipe{p2} -} - -var _ net.Conn = (*pipe)(nil) diff --git a/internal/p2p/conn/connection.go b/internal/p2p/conn/connection.go index 693a7ce58..c8fc21188 100644 --- a/internal/p2p/conn/connection.go +++ b/internal/p2p/conn/connection.go @@ -413,7 +413,7 @@ func (c *MConnection) sendSomePacketMsgs(ctx context.Context) bool { // Block until .sendMonitor says we can write. // Once we're ready we send more than we asked for, // but amortized it should even out. - c.sendMonitor.Limit(c._maxPacketMsgSize, atomic.LoadInt64(&c.config.SendRate), true) + c.sendMonitor.Limit(c._maxPacketMsgSize, c.config.SendRate, true) // Now send some PacketMsgs. for i := 0; i < numBatchPacketMsgs; i++ { @@ -481,7 +481,7 @@ FOR_LOOP: } // Block until .recvMonitor says we can read. - c.recvMonitor.Limit(c._maxPacketMsgSize, atomic.LoadInt64(&c.config.RecvRate), true) + c.recvMonitor.Limit(c._maxPacketMsgSize, c.config.RecvRate, true) // Peek into bufConnReader for debugging /* @@ -616,6 +616,10 @@ type ChannelDescriptor struct { // RecvBufferCapacity defines the max buffer size of inbound messages for a // given p2p Channel queue. RecvBufferCapacity int + + // Human readable name of the channel, used in logging and + // diagnostics. + Name string } func (chDesc ChannelDescriptor) FillDefaults() (filled ChannelDescriptor) { diff --git a/internal/p2p/conn/connection_test.go b/internal/p2p/conn/connection_test.go index e68b7584f..5a604cd23 100644 --- a/internal/p2p/conn/connection_test.go +++ b/internal/p2p/conn/connection_test.go @@ -48,13 +48,13 @@ func createMConnectionWithCallbacks( } func TestMConnectionSendFlushStop(t *testing.T) { - server, client := NetPipe() + server, client := net.Pipe() t.Cleanup(closeAll(t, client, server)) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - clientConn := createTestMConnection(log.TestingLogger(), client) + clientConn := createTestMConnection(log.NewNopLogger(), client) err := clientConn.Start(ctx) require.NoError(t, err) t.Cleanup(waitAll(clientConn)) @@ -85,13 +85,13 @@ func TestMConnectionSendFlushStop(t *testing.T) { } func TestMConnectionSend(t *testing.T) { - server, client := NetPipe() + server, client := net.Pipe() t.Cleanup(closeAll(t, client, server)) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - mconn := createTestMConnection(log.TestingLogger(), client) + mconn := createTestMConnection(log.NewNopLogger(), client) err := mconn.Start(ctx) require.NoError(t, err) t.Cleanup(waitAll(mconn)) @@ -116,7 +116,7 @@ func TestMConnectionSend(t *testing.T) { } func TestMConnectionReceive(t *testing.T) { - server, client := NetPipe() + server, client := net.Pipe() t.Cleanup(closeAll(t, client, server)) receivedCh := make(chan []byte) @@ -133,7 +133,7 @@ func TestMConnectionReceive(t *testing.T) { case <-ctx.Done(): } } - logger := log.TestingLogger() + logger := log.NewNopLogger() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -168,7 +168,7 @@ func TestMConnectionWillEventuallyTimeout(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - mconn := createMConnectionWithCallbacks(log.TestingLogger(), client, nil, nil) + mconn := createMConnectionWithCallbacks(log.NewNopLogger(), client, nil, nil) err := mconn.Start(ctx) require.NoError(t, err) t.Cleanup(waitAll(mconn)) @@ -224,7 +224,7 @@ func TestMConnectionMultiplePongsInTheBeginning(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - mconn := createMConnectionWithCallbacks(log.TestingLogger(), client, onReceive, onError) + mconn := createMConnectionWithCallbacks(log.NewNopLogger(), client, onReceive, onError) err := mconn.Start(ctx) require.NoError(t, err) t.Cleanup(waitAll(mconn)) @@ -282,7 +282,7 @@ func TestMConnectionMultiplePings(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - mconn := createMConnectionWithCallbacks(log.TestingLogger(), client, onReceive, onError) + mconn := createMConnectionWithCallbacks(log.NewNopLogger(), client, onReceive, onError) err := mconn.Start(ctx) require.NoError(t, err) t.Cleanup(waitAll(mconn)) @@ -339,7 +339,7 @@ func TestMConnectionPingPongs(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - mconn := createMConnectionWithCallbacks(log.TestingLogger(), client, onReceive, onError) + mconn := createMConnectionWithCallbacks(log.NewNopLogger(), client, onReceive, onError) err := mconn.Start(ctx) require.NoError(t, err) t.Cleanup(waitAll(mconn)) @@ -378,7 +378,7 @@ func TestMConnectionPingPongs(t *testing.T) { } func TestMConnectionStopsAndReturnsError(t *testing.T) { - server, client := NetPipe() + server, client := net.Pipe() t.Cleanup(closeAll(t, client, server)) receivedCh := make(chan []byte) @@ -398,7 +398,7 @@ func TestMConnectionStopsAndReturnsError(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - mconn := createMConnectionWithCallbacks(log.TestingLogger(), client, onReceive, onError) + mconn := createMConnectionWithCallbacks(log.NewNopLogger(), client, onReceive, onError) err := mconn.Start(ctx) require.NoError(t, err) t.Cleanup(waitAll(mconn)) @@ -423,7 +423,7 @@ func newClientAndServerConnsForReadErrors( t *testing.T, chOnErr chan struct{}, ) (*MConnection, *MConnection) { - server, client := NetPipe() + server, client := net.Pipe() onReceive := func(context.Context, ChannelID, []byte) {} onError := func(context.Context, interface{}) {} @@ -433,7 +433,7 @@ func newClientAndServerConnsForReadErrors( {ID: 0x01, Priority: 1, SendQueueCapacity: 1}, {ID: 0x02, Priority: 1, SendQueueCapacity: 1}, } - logger := log.TestingLogger() + logger := log.NewNopLogger() mconnClient := NewMConnection(logger.With("module", "client"), client, chDescs, onReceive, onError, DefaultMConnConfig()) err := mconnClient.Start(ctx) @@ -558,12 +558,12 @@ func TestMConnectionReadErrorUnknownMsgType(t *testing.T) { } func TestMConnectionTrySend(t *testing.T) { - server, client := NetPipe() + server, client := net.Pipe() t.Cleanup(closeAll(t, client, server)) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - mconn := createTestMConnection(log.TestingLogger(), client) + mconn := createTestMConnection(log.NewNopLogger(), client) err := mconn.Start(ctx) require.NoError(t, err) t.Cleanup(waitAll(mconn)) diff --git a/internal/p2p/conn/secret_connection_test.go b/internal/p2p/conn/secret_connection_test.go index 362c8102f..2e37f8388 100644 --- a/internal/p2p/conn/secret_connection_test.go +++ b/internal/p2p/conn/secret_connection_test.go @@ -126,7 +126,7 @@ func TestSecretConnectionReadWrite(t *testing.T) { nodePrvKey := ed25519.GenPrivKey() nodeSecretConn, err := MakeSecretConnection(nodeConn, nodePrvKey) if err != nil { - t.Errorf("failed to establish SecretConnection for node: %w", err) + t.Errorf("failed to establish SecretConnection for node: %v", err) return nil, true, err } // In parallel, handle some reads and writes. @@ -136,7 +136,7 @@ func TestSecretConnectionReadWrite(t *testing.T) { for _, nodeWrite := range nodeWrites { n, err := nodeSecretConn.Write([]byte(nodeWrite)) if err != nil { - t.Errorf("failed to write to nodeSecretConn: %w", err) + t.Errorf("failed to write to nodeSecretConn: %v", err) return nil, true, err } if n != len(nodeWrite) { @@ -163,7 +163,7 @@ func TestSecretConnectionReadWrite(t *testing.T) { } return nil, false, nil } else if err != nil { - t.Errorf("failed to read from nodeSecretConn: %w", err) + t.Errorf("failed to read from nodeSecretConn: %v", err) return nil, true, err } *nodeReads = append(*nodeReads, string(readBuffer[:n])) @@ -228,7 +228,7 @@ func TestDeriveSecretsAndChallengeGolden(t *testing.T) { goldenFilepath := filepath.Join("testdata", t.Name()+".golden") if *update { t.Logf("Updating golden test vector file %s", goldenFilepath) - data := createGoldenTestVectors(t) + data := createGoldenTestVectors() require.NoError(t, os.WriteFile(goldenFilepath, []byte(data), 0644)) } f, err := os.Open(goldenFilepath) @@ -288,7 +288,7 @@ func writeLots(t *testing.T, wg *sync.WaitGroup, conn io.Writer, txt string, n i for i := 0; i < n; i++ { _, err := conn.Write([]byte(txt)) if err != nil { - t.Errorf("failed to write to fooSecConn: %w", err) + t.Errorf("failed to write to fooSecConn: %v", err) return } } @@ -306,7 +306,7 @@ func readLots(t *testing.T, wg *sync.WaitGroup, conn io.Reader, n int) { // Creates the data for a test vector file. // The file format is: // Hex(diffie_hellman_secret), loc_is_least, Hex(recvSecret), Hex(sendSecret), Hex(challenge) -func createGoldenTestVectors(t *testing.T) string { +func createGoldenTestVectors() string { data := "" for i := 0; i < 32; i++ { randSecretVector := tmrand.Bytes(32) @@ -343,7 +343,7 @@ func makeSecretConnPair(tb testing.TB) (fooSecConn, barSecConn *SecretConnection func(_ int) (val interface{}, abort bool, err error) { fooSecConn, err = MakeSecretConnection(fooConn, fooPrvKey) if err != nil { - tb.Errorf("failed to establish SecretConnection for foo: %w", err) + tb.Errorf("failed to establish SecretConnection for foo: %v", err) return nil, true, err } remotePubBytes := fooSecConn.RemotePubKey() @@ -358,7 +358,7 @@ func makeSecretConnPair(tb testing.TB) (fooSecConn, barSecConn *SecretConnection func(_ int) (val interface{}, abort bool, err error) { barSecConn, err = MakeSecretConnection(barConn, barPrvKey) if barSecConn == nil { - tb.Errorf("failed to establish SecretConnection for bar: %w", err) + tb.Errorf("failed to establish SecretConnection for bar: %v", err) return nil, true, err } remotePubBytes := barSecConn.RemotePubKey() @@ -405,7 +405,7 @@ func BenchmarkWriteSecretConnection(b *testing.B) { if err == io.EOF { return } else if err != nil { - b.Errorf("failed to read from barSecConn: %w", err) + b.Errorf("failed to read from barSecConn: %v", err) return } } @@ -416,7 +416,7 @@ func BenchmarkWriteSecretConnection(b *testing.B) { idx := mrand.Intn(len(fooWriteBytes)) _, err := fooSecConn.Write(fooWriteBytes[idx]) if err != nil { - b.Errorf("failed to write to fooSecConn: %w", err) + b.Errorf("failed to write to fooSecConn: %v", err) return } } diff --git a/internal/p2p/conn_tracker.go b/internal/p2p/conn_tracker.go index 09673c093..54f9c8980 100644 --- a/internal/p2p/conn_tracker.go +++ b/internal/p2p/conn_tracker.go @@ -26,6 +26,7 @@ func newConnTracker(max uint, window time.Duration) connectionTracker { cache: make(map[string]uint), lastConnect: make(map[string]time.Time), max: max, + window: window, } } @@ -43,7 +44,7 @@ func (rat *connTrackerImpl) AddConn(addr net.IP) error { if num := rat.cache[address]; num >= rat.max { return fmt.Errorf("%q has %d connections [max=%d]", address, num, rat.max) } else if num == 0 { - // if there is already at least connection, check to + // if there is already at least one connection, check to // see if it was established before within the window, // and error if so. if last := rat.lastConnect[address]; time.Since(last) < rat.window { diff --git a/internal/p2p/conn_tracker_test.go b/internal/p2p/conn_tracker_test.go index 66656e114..daa3351f2 100644 --- a/internal/p2p/conn_tracker_test.go +++ b/internal/p2p/conn_tracker_test.go @@ -70,4 +70,15 @@ func TestConnTracker(t *testing.T) { } require.Equal(t, 10, ct.Len()) }) + t.Run("Window", func(t *testing.T) { + const window = 100 * time.Millisecond + ct := newConnTracker(10, window) + ip := randLocalIPv4() + require.NoError(t, ct.AddConn(ip)) + ct.RemoveConn(ip) + require.Error(t, ct.AddConn(ip)) + time.Sleep(window) + require.NoError(t, ct.AddConn(ip)) + }) + } diff --git a/internal/p2p/mocks/connection.go b/internal/p2p/mocks/connection.go index 576fb2386..73b6cfc3b 100644 --- a/internal/p2p/mocks/connection.go +++ b/internal/p2p/mocks/connection.go @@ -13,6 +13,8 @@ import ( p2p "github.com/tendermint/tendermint/internal/p2p" + testing "testing" + types "github.com/tendermint/tendermint/types" ) @@ -150,3 +152,13 @@ func (_m *Connection) String() string { return r0 } + +// NewConnection creates a new instance of Connection. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewConnection(t testing.TB) *Connection { + mock := &Connection{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/p2p/mocks/transport.go b/internal/p2p/mocks/transport.go index b17290118..34ebec20e 100644 --- a/internal/p2p/mocks/transport.go +++ b/internal/p2p/mocks/transport.go @@ -10,6 +10,8 @@ import ( mock "github.com/stretchr/testify/mock" p2p "github.com/tendermint/tendermint/internal/p2p" + + testing "testing" ) // Transport is an autogenerated mock type for the Transport type @@ -60,11 +62,11 @@ func (_m *Transport) Close() error { } // Dial provides a mock function with given fields: _a0, _a1 -func (_m *Transport) Dial(_a0 context.Context, _a1 p2p.Endpoint) (p2p.Connection, error) { +func (_m *Transport) Dial(_a0 context.Context, _a1 *p2p.Endpoint) (p2p.Connection, error) { ret := _m.Called(_a0, _a1) var r0 p2p.Connection - if rf, ok := ret.Get(0).(func(context.Context, p2p.Endpoint) p2p.Connection); ok { + if rf, ok := ret.Get(0).(func(context.Context, *p2p.Endpoint) p2p.Connection); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -73,7 +75,7 @@ func (_m *Transport) Dial(_a0 context.Context, _a1 p2p.Endpoint) (p2p.Connection } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, p2p.Endpoint) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *p2p.Endpoint) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -82,28 +84,35 @@ func (_m *Transport) Dial(_a0 context.Context, _a1 p2p.Endpoint) (p2p.Connection return r0, r1 } -// Endpoints provides a mock function with given fields: -func (_m *Transport) Endpoints() []p2p.Endpoint { +// Endpoint provides a mock function with given fields: +func (_m *Transport) Endpoint() (*p2p.Endpoint, error) { ret := _m.Called() - var r0 []p2p.Endpoint - if rf, ok := ret.Get(0).(func() []p2p.Endpoint); ok { + var r0 *p2p.Endpoint + if rf, ok := ret.Get(0).(func() *p2p.Endpoint); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]p2p.Endpoint) + r0 = ret.Get(0).(*p2p.Endpoint) } } - return r0 + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 } // Listen provides a mock function with given fields: _a0 -func (_m *Transport) Listen(_a0 p2p.Endpoint) error { +func (_m *Transport) Listen(_a0 *p2p.Endpoint) error { ret := _m.Called(_a0) var r0 error - if rf, ok := ret.Get(0).(func(p2p.Endpoint) error); ok { + if rf, ok := ret.Get(0).(func(*p2p.Endpoint) error); ok { r0 = rf(_a0) } else { r0 = ret.Error(0) @@ -141,3 +150,13 @@ func (_m *Transport) String() string { return r0 } + +// NewTransport creates a new instance of Transport. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewTransport(t testing.TB) *Transport { + mock := &Transport{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/p2p/p2ptest/network.go b/internal/p2p/p2ptest/network.go index 669d92962..85df029d8 100644 --- a/internal/p2p/p2ptest/network.go +++ b/internal/p2p/p2ptest/network.go @@ -50,7 +50,7 @@ func (opts *NetworkOptions) setDefaults() { // connects them to each other. func MakeNetwork(ctx context.Context, t *testing.T, opts NetworkOptions) *Network { opts.setDefaults() - logger := log.TestingLogger() + logger := log.NewNopLogger() network := &Network{ Nodes: map[types.NodeID]*Node{}, logger: logger, @@ -247,7 +247,9 @@ func (n *Network) MakeNode(ctx context.Context, t *testing.T, opts NodeOptions) } transport := n.memoryNetwork.CreateTransport(nodeID) - require.Len(t, transport.Endpoints(), 1, "transport not listening on 1 endpoint") + ep, err := transport.Endpoint() + require.NoError(t, err) + require.NotNil(t, ep, "transport not listening an endpoint") peerManager, err := p2p.NewPeerManager(nodeID, dbm.NewMemDB(), p2p.PeerManagerOptions{ MinRetryTime: 10 * time.Millisecond, @@ -259,14 +261,13 @@ func (n *Network) MakeNode(ctx context.Context, t *testing.T, opts NodeOptions) require.NoError(t, err) router, err := p2p.NewRouter( - ctx, n.logger, p2p.NopMetrics(), - nodeInfo, privKey, peerManager, - []p2p.Transport{transport}, - transport.Endpoints(), + func() *types.NodeInfo { return &nodeInfo }, + transport, + ep, p2p.RouterOptions{DialSleep: func(_ context.Context) {}}, ) @@ -285,7 +286,7 @@ func (n *Network) MakeNode(ctx context.Context, t *testing.T, opts NodeOptions) return &Node{ NodeID: nodeID, NodeInfo: nodeInfo, - NodeAddress: transport.Endpoints()[0].NodeAddress(nodeID), + NodeAddress: ep.NodeAddress(nodeID), PrivKey: privKey, Router: router, PeerManager: peerManager, @@ -305,7 +306,6 @@ func (n *Node) MakeChannel( ctx, cancel := context.WithCancel(ctx) channel, err := n.Router.OpenChannel(ctx, chDesc) require.NoError(t, err) - require.Contains(t, n.Router.NodeInfo().Channels, byte(chDesc.ID)) t.Cleanup(func() { RequireEmpty(ctx, t, channel) cancel() diff --git a/internal/p2p/p2ptest/require.go b/internal/p2p/p2ptest/require.go index f9f3ec40e..885e080d4 100644 --- a/internal/p2p/p2ptest/require.go +++ b/internal/p2p/p2ptest/require.go @@ -123,7 +123,7 @@ func RequireError(ctx context.Context, t *testing.T, channel *p2p.Channel, peerE err := channel.SendError(tctx, peerError) switch { case errors.Is(err, context.DeadlineExceeded): - require.Fail(t, "timed out reporting error", "%v on %v", peerError, channel.ID) + require.Fail(t, "timed out reporting error", "%v for %q", peerError, channel.String()) default: require.NoError(t, err, "unexpected error") } diff --git a/internal/p2p/peermanager.go b/internal/p2p/peermanager.go index 3293468ae..756551a49 100644 --- a/internal/p2p/peermanager.go +++ b/internal/p2p/peermanager.go @@ -42,7 +42,8 @@ const ( type PeerScore uint8 const ( - PeerScorePersistent PeerScore = math.MaxUint8 // persistent peers + PeerScorePersistent PeerScore = math.MaxUint8 // persistent peers + MaxPeerScoreNotPersistent PeerScore = PeerScorePersistent - 1 ) // PeerUpdate is a peer update event sent via PeerUpdates. @@ -827,6 +828,11 @@ func (m *PeerManager) Advertise(peerID types.NodeID, limit uint16) []NodeAddress return addresses } +// PeerEventSubscriber describes the type of the subscription method, to assist +// in isolating reactors specific construction and lifecycle from the +// peer manager. +type PeerEventSubscriber func(context.Context) *PeerUpdates + // Subscribe subscribes to peer updates. The caller must consume the peer // updates in a timely fashion and close the subscription when done, otherwise // the PeerManager will halt. @@ -1283,6 +1289,9 @@ func (p *peerInfo) Score() PeerScore { } score := p.MutableScore + if score > int64(MaxPeerScoreNotPersistent) { + score = int64(MaxPeerScoreNotPersistent) + } for _, addr := range p.AddressInfo { // DialFailures is reset when dials succeed, so this @@ -1294,10 +1303,6 @@ func (p *peerInfo) Score() PeerScore { return 0 } - if score >= math.MaxUint8 { - return PeerScore(math.MaxUint8) - } - return PeerScore(score) } diff --git a/internal/p2p/peermanager_scoring_test.go b/internal/p2p/peermanager_scoring_test.go index 4c7bef0cc..a45df0b72 100644 --- a/internal/p2p/peermanager_scoring_test.go +++ b/internal/p2p/peermanager_scoring_test.go @@ -80,4 +80,20 @@ func TestPeerScoring(t *testing.T) { time.Millisecond, "startAt=%d score=%d", start, peerManager.Scores()[id]) }) + t.Run("TestNonPersistantPeerUpperBound", func(t *testing.T) { + start := int64(peerManager.Scores()[id] + 1) + + for i := start; i <= int64(PeerScorePersistent); i++ { + peerManager.processPeerEvent(ctx, PeerUpdate{ + NodeID: id, + Status: PeerStatusGood, + }) + + if i == int64(PeerScorePersistent) { + require.EqualValues(t, MaxPeerScoreNotPersistent, peerManager.Scores()[id]) + } else { + require.EqualValues(t, i, peerManager.Scores()[id]) + } + } + }) } diff --git a/internal/p2p/pex/reactor.go b/internal/p2p/pex/reactor.go index 0c256a4f3..bd4737326 100644 --- a/internal/p2p/pex/reactor.go +++ b/internal/p2p/pex/reactor.go @@ -3,14 +3,12 @@ package pex import ( "context" "fmt" - "runtime/debug" "sync" "time" "github.com/tendermint/tendermint/internal/p2p" "github.com/tendermint/tendermint/internal/p2p/conn" "github.com/tendermint/tendermint/libs/log" - tmmath "github.com/tendermint/tendermint/libs/math" "github.com/tendermint/tendermint/libs/service" protop2p "github.com/tendermint/tendermint/proto/tendermint/p2p" "github.com/tendermint/tendermint/types" @@ -42,7 +40,7 @@ const ( minReceiveRequestInterval = 100 * time.Millisecond // the maximum amount of addresses that can be included in a response - maxAddresses uint16 = 100 + maxAddresses = 100 // How long to wait when there are no peers available before trying again noAvailablePeersWaitPeriod = 1 * time.Second @@ -65,6 +63,7 @@ func ChannelDescriptor() *conn.ChannelDescriptor { SendQueueCapacity: 10, RecvMessageCapacity: maxMsgSize, RecvBufferCapacity: 128, + Name: "pex", } } @@ -81,9 +80,8 @@ type Reactor struct { logger log.Logger peerManager *p2p.PeerManager - pexCh *p2p.Channel - peerUpdates *p2p.PeerUpdates - + chCreator p2p.ChannelCreator + peerEvents p2p.PeerEventSubscriber // list of available peers to loop through and send peer requests to availablePeers map[types.NodeID]struct{} @@ -100,43 +98,29 @@ type Reactor struct { // minReceiveRequestInterval). lastReceivedRequests map[types.NodeID]time.Time - // keep track of how many new peers to existing peers we have received to - // extrapolate the size of the network - newPeers uint32 - totalPeers uint32 - - // discoveryRatio is the inverse ratio of new peers to old peers squared. - // This is multiplied by the minimum duration to calculate how long to wait - // between each request. - discoveryRatio float32 + // the total number of unique peers added + totalPeers int } // NewReactor returns a reference to a new reactor. func NewReactor( - ctx context.Context, logger log.Logger, peerManager *p2p.PeerManager, channelCreator p2p.ChannelCreator, - peerUpdates *p2p.PeerUpdates, -) (*Reactor, error) { - - channel, err := channelCreator(ctx, ChannelDescriptor()) - if err != nil { - return nil, err - } - + peerEvents p2p.PeerEventSubscriber, +) *Reactor { r := &Reactor{ logger: logger, peerManager: peerManager, - pexCh: channel, - peerUpdates: peerUpdates, + chCreator: channelCreator, + peerEvents: peerEvents, availablePeers: make(map[types.NodeID]struct{}), requestsSent: make(map[types.NodeID]struct{}), lastReceivedRequests: make(map[types.NodeID]time.Time), } r.BaseService = *service.NewBaseService(logger, "PEX", r) - return r, nil + return r } // OnStart starts separate go routines for each p2p Channel and listens for @@ -144,8 +128,14 @@ func NewReactor( // messages on that p2p channel accordingly. The caller must be sure to execute // OnStop to ensure the outbound p2p Channels are closed. func (r *Reactor) OnStart(ctx context.Context) error { - go r.processPexCh(ctx) - go r.processPeerUpdates(ctx) + channel, err := r.chCreator(ctx, ChannelDescriptor()) + if err != nil { + return err + } + + peerUpdates := r.peerEvents(ctx) + go r.processPexCh(ctx, channel) + go r.processPeerUpdates(ctx, peerUpdates) return nil } @@ -155,21 +145,11 @@ func (r *Reactor) OnStop() {} // processPexCh implements a blocking event loop where we listen for p2p // Envelope messages from the pexCh. -func (r *Reactor) processPexCh(ctx context.Context) { - timer := time.NewTimer(0) - defer timer.Stop() - - r.mtx.Lock() - var ( - duration = r.calculateNextRequestTime() - err error - ) - r.mtx.Unlock() - +func (r *Reactor) processPexCh(ctx context.Context, pexCh *p2p.Channel) { incoming := make(chan *p2p.Envelope) go func() { defer close(incoming) - iter := r.pexCh.Receive(ctx) + iter := pexCh.Receive(ctx) for iter.Next(ctx) { select { case <-ctx.Done(): @@ -179,36 +159,51 @@ func (r *Reactor) processPexCh(ctx context.Context) { } }() + // Initially, we will request peers quickly to bootstrap. This duration + // will be adjusted upward as knowledge of the network grows. + var nextPeerRequest = minReceiveRequestInterval + + timer := time.NewTimer(0) + defer timer.Stop() + for { - timer.Reset(duration) + timer.Reset(nextPeerRequest) select { case <-ctx.Done(): return - // outbound requests for new peers case <-timer.C: - duration, err = r.sendRequestForPeers(ctx) - if err != nil { + // Send a request for more peer addresses. + if err := r.sendRequestForPeers(ctx, pexCh); err != nil { return + // TODO(creachadair): Do we really want to stop processing the PEX + // channel just because of an error here? } - // inbound requests for new peers or responses to requests sent by this - // reactor + + // Note we do not update the poll timer upon making a request, only + // when we receive an update that updates our priors. + case envelope, ok := <-incoming: if !ok { - return + return // channel closed } - duration, err = r.handleMessage(ctx, r.pexCh.ID, envelope) + + // A request from another peer, or a response to one of our requests. + dur, err := r.handlePexMessage(ctx, envelope, pexCh) if err != nil { - r.logger.Error("failed to process message", "ch_id", r.pexCh.ID, "envelope", envelope, "err", err) - if serr := r.pexCh.SendError(ctx, p2p.PeerError{ + r.logger.Error("failed to process message", + "ch_id", envelope.ChannelID, "envelope", envelope, "err", err) + if serr := pexCh.SendError(ctx, p2p.PeerError{ NodeID: envelope.From, Err: err, }); serr != nil { return } + } else if dur != 0 { + // We got a useful result; update the poll timer. + nextPeerRequest = dur } - } } } @@ -216,31 +211,32 @@ func (r *Reactor) processPexCh(ctx context.Context) { // processPeerUpdates initiates a blocking process where we listen for and handle // PeerUpdate messages. When the reactor is stopped, we will catch the signal and // close the p2p PeerUpdatesCh gracefully. -func (r *Reactor) processPeerUpdates(ctx context.Context) { +func (r *Reactor) processPeerUpdates(ctx context.Context, peerUpdates *p2p.PeerUpdates) { for { select { case <-ctx.Done(): return - case peerUpdate := <-r.peerUpdates.Updates(): + case peerUpdate := <-peerUpdates.Updates(): r.processPeerUpdate(peerUpdate) } } } // handlePexMessage handles envelopes sent from peers on the PexChannel. -func (r *Reactor) handlePexMessage(ctx context.Context, envelope *p2p.Envelope) (time.Duration, error) { +// If an update was received, a new polling interval is returned; otherwise the +// duration is 0. +func (r *Reactor) handlePexMessage(ctx context.Context, envelope *p2p.Envelope, pexCh *p2p.Channel) (time.Duration, error) { logger := r.logger.With("peer", envelope.From) switch msg := envelope.Message.(type) { case *protop2p.PexRequest: - // check if the peer hasn't sent a prior request too close to this one - // in time + // Verify that this peer hasn't sent us another request too recently. if err := r.markPeerRequest(envelope.From); err != nil { - return time.Minute, err + return 0, err } - // request peers from the peer manager and parse the NodeAddresses into - // URL strings + // Fetch peers from the peer manager, convert NodeAddresses into URL + // strings, and send them back to the caller. nodeAddresses := r.peerManager.Advertise(envelope.From, maxAddresses) pexAddresses := make([]protop2p.PexAddress, len(nodeAddresses)) for idx, addr := range nodeAddresses { @@ -248,28 +244,24 @@ func (r *Reactor) handlePexMessage(ctx context.Context, envelope *p2p.Envelope) URL: addr.String(), } } - if err := r.pexCh.Send(ctx, p2p.Envelope{ + return 0, pexCh.Send(ctx, p2p.Envelope{ To: envelope.From, Message: &protop2p.PexResponse{Addresses: pexAddresses}, - }); err != nil { + }) + + case *protop2p.PexResponse: + // Verify that this response corresponds to one of our pending requests. + if err := r.markPeerResponse(envelope.From); err != nil { return 0, err } - return time.Second, nil - case *protop2p.PexResponse: - // check if the response matches a request that was made to that peer - if err := r.markPeerResponse(envelope.From); err != nil { - return time.Minute, err - } - - // check the size of the response - if len(msg.Addresses) > int(maxAddresses) { - return 10 * time.Minute, fmt.Errorf("peer sent too many addresses (max: %d, got: %d)", - maxAddresses, - len(msg.Addresses), - ) + // Verify that the response does not exceed the safety limit. + if len(msg.Addresses) > maxAddresses { + return 0, fmt.Errorf("peer sent too many addresses (%d > maxiumum %d)", + len(msg.Addresses), maxAddresses) } + var numAdded int for _, pexAddress := range msg.Addresses { peerAddress, err := p2p.ParseNodeAddress(pexAddress.URL) if err != nil { @@ -278,47 +270,21 @@ func (r *Reactor) handlePexMessage(ctx context.Context, envelope *p2p.Envelope) added, err := r.peerManager.Add(peerAddress) if err != nil { logger.Error("failed to add PEX address", "address", peerAddress, "err", err) + continue } if added { - r.newPeers++ + numAdded++ logger.Debug("added PEX address", "address", peerAddress) } - r.totalPeers++ } - return 10 * time.Minute, nil + return r.calculateNextRequestTime(numAdded), nil + default: - return time.Second, fmt.Errorf("received unknown message: %T", msg) + return 0, fmt.Errorf("received unknown message: %T", msg) } } -// handleMessage handles an Envelope sent from a peer on a specific p2p Channel. -// It will handle errors and any possible panics gracefully. A caller can handle -// any error returned by sending a PeerError on the respective channel. -func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelope *p2p.Envelope) (duration time.Duration, err error) { - defer func() { - if e := recover(); e != nil { - err = fmt.Errorf("panic in processing message: %v", e) - r.logger.Error( - "recovering from processing message panic", - "err", err, - "stack", string(debug.Stack()), - ) - } - }() - - r.logger.Debug("received PEX message", "peer", envelope.From) - - switch chID { - case p2p.ChannelID(PexChannel): - duration, err = r.handlePexMessage(ctx, envelope) - default: - err = fmt.Errorf("unknown channel ID (%d) for envelope (%v)", chID, envelope) - } - - return -} - // processPeerUpdate processes a PeerUpdate. For added peers, PeerStatusUp, we // send a request for addresses. func (r *Reactor) processPeerUpdate(peerUpdate p2p.PeerUpdate) { @@ -338,95 +304,87 @@ func (r *Reactor) processPeerUpdate(peerUpdate p2p.PeerUpdate) { } } -// sendRequestForPeers pops the first peerID off the list and sends the -// peer a request for more peer addresses. The function then moves the -// peer into the requestsSent bucket and calculates when the next request -// time should be -func (r *Reactor) sendRequestForPeers(ctx context.Context) (time.Duration, error) { +// sendRequestForPeers chooses a peer from the set of available peers and sends +// that peer a request for more peer addresses. The chosen peer is moved into +// the requestsSent bucket so that we will not attempt to contact them again +// until they've replied or updated. +func (r *Reactor) sendRequestForPeers(ctx context.Context, pexCh *p2p.Channel) error { r.mtx.Lock() defer r.mtx.Unlock() if len(r.availablePeers) == 0 { // no peers are available - r.logger.Debug("no available peers to send request to, waiting...") - return noAvailablePeersWaitPeriod, nil + r.logger.Debug("no available peers to send a PEX request to (retrying)") + return nil } - var peerID types.NodeID - // use range to get a random peer. + // Select an arbitrary peer from the available set. + var peerID types.NodeID for peerID = range r.availablePeers { break } - // send out the pex request - if err := r.pexCh.Send(ctx, p2p.Envelope{ + if err := pexCh.Send(ctx, p2p.Envelope{ To: peerID, Message: &protop2p.PexRequest{}, }); err != nil { - return 0, err + return err } - // remove the peer from the abvailable peers list and mark it in the requestsSent map + // Move the peer from available to pending. delete(r.availablePeers, peerID) r.requestsSent[peerID] = struct{}{} - dur := r.calculateNextRequestTime() - r.logger.Debug("peer request sent", "next_request_time", dur) - return dur, nil + return nil } -// calculateNextRequestTime implements something of a proportional controller -// to estimate how often the reactor should be requesting new peer addresses. -// The dependent variable in this calculation is the ratio of new peers to -// all peers that the reactor receives. The interval is thus calculated as the -// inverse squared. In the beginning, all peers should be new peers. -// We expect this ratio to be near 1 and thus the interval to be as short -// as possible. As the node becomes more familiar with the network the ratio of -// new nodes will plummet to a very small number, meaning the interval expands -// to its upper bound. +// calculateNextRequestTime selects how long we should wait before attempting +// to send out another request for peer addresses. // -// CONTRACT: The caller must hold r.mtx exclusively when calling this method. -func (r *Reactor) calculateNextRequestTime() time.Duration { - // check if the peer store is full. If so then there is no need - // to send peer requests too often +// This implements a simplified proportional control mechanism to poll more +// often when our knowledge of the network is incomplete, and less often as our +// knowledge grows. To estimate our knowledge of the network, we use the +// fraction of "new" peers (addresses we have not previously seen) to the total +// so far observed. When we first join the network, this fraction will be close +// to 1, meaning most new peers are "new" to us, and as we discover more peers, +// the fraction will go toward zero. +// +// The minimum interval will be minReceiveRequestInterval to ensure we will not +// request from any peer more often than we would allow them to do from us. +func (r *Reactor) calculateNextRequestTime(added int) time.Duration { + r.mtx.Lock() + defer r.mtx.Unlock() + + r.totalPeers += added + + // If the peer store is nearly full, wait the maximum interval. if ratio := r.peerManager.PeerRatio(); ratio >= 0.95 { - r.logger.Debug("peer manager near full ratio, sleeping...", + r.logger.Debug("Peer manager is nearly full", "sleep_period", fullCapacityInterval, "ratio", ratio) return fullCapacityInterval } - // baseTime represents the shortest interval that we can send peer requests - // in. For example if we have 10 peers and we can't send a message to the - // same peer every 500ms, then we can send a request every 50ms. In practice - // we use a safety margin of 2, ergo 100ms - peers := tmmath.MinInt(len(r.availablePeers), 50) - baseTime := minReceiveRequestInterval - if peers > 0 { - baseTime = minReceiveRequestInterval * 2 / time.Duration(peers) + // If there are no available peers to query, poll less aggressively. + if len(r.availablePeers) == 0 { + r.logger.Debug("No available peers to send a PEX request", + "sleep_period", noAvailablePeersWaitPeriod) + return noAvailablePeersWaitPeriod } - if r.totalPeers > 0 || r.discoveryRatio == 0 { - // find the ratio of new peers. NOTE: We add 1 to both sides to avoid - // divide by zero problems - ratio := float32(r.totalPeers+1) / float32(r.newPeers+1) - // square the ratio in order to get non linear time intervals - // NOTE: The longest possible interval for a network with 100 or more peers - // where a node is connected to 50 of them is 2 minutes. - r.discoveryRatio = ratio * ratio - r.newPeers = 0 - r.totalPeers = 0 - } - // NOTE: As ratio is always >= 1, discovery ratio is >= 1. Therefore we don't need to worry - // about the next request time being less than the minimum time - return baseTime * time.Duration(r.discoveryRatio) + // Reaching here, there are available peers to query and the peer store + // still has space. Estimate our knowledge of the network from the latest + // update and choose a new interval. + base := float64(minReceiveRequestInterval) / float64(len(r.availablePeers)) + multiplier := float64(r.totalPeers+1) / float64(added+1) // +1 to avert zero division + return time.Duration(base*multiplier*multiplier) + minReceiveRequestInterval } func (r *Reactor) markPeerRequest(peer types.NodeID) error { r.mtx.Lock() defer r.mtx.Unlock() if lastRequestTime, ok := r.lastReceivedRequests[peer]; ok { - if time.Now().Before(lastRequestTime.Add(minReceiveRequestInterval)) { - return fmt.Errorf("peer sent a request too close after a prior one. Minimum interval: %v", - minReceiveRequestInterval) + if d := time.Since(lastRequestTime); d < minReceiveRequestInterval { + return fmt.Errorf("peer %v sent PEX request too soon (%v < minimum %v)", + peer, d, minReceiveRequestInterval) } } r.lastReceivedRequests[peer] = time.Now() diff --git a/internal/p2p/pex/reactor_test.go b/internal/p2p/pex/reactor_test.go index 4319cad20..ec2f03d83 100644 --- a/internal/p2p/pex/reactor_test.go +++ b/internal/p2p/pex/reactor_test.go @@ -23,8 +23,8 @@ import ( const ( checkFrequency = 500 * time.Millisecond defaultBufferSize = 2 - shortWait = 10 * time.Second - longWait = 60 * time.Second + shortWait = 5 * time.Second + longWait = 20 * time.Second firstNode = 0 secondNode = 1 @@ -96,7 +96,7 @@ func TestReactorSendsRequestsTooOften(t *testing.T) { peerErr := <-r.pexErrCh require.Error(t, peerErr.Err) require.Empty(t, r.pexOutCh) - require.Contains(t, peerErr.Err.Error(), "peer sent a request too close after a prior one") + require.Contains(t, peerErr.Err.Error(), "sent PEX request too soon") require.Equal(t, badNode, peerErr.NodeID) } @@ -151,14 +151,14 @@ func TestReactorErrorsOnReceivingTooManyPeers(t *testing.T) { defer cancel() r := setupSingle(ctx, t) - peer := p2p.NodeAddress{Protocol: p2p.MemoryProtocol, NodeID: randomNodeID(t)} + peer := p2p.NodeAddress{Protocol: p2p.MemoryProtocol, NodeID: randomNodeID()} added, err := r.manager.Add(peer) require.NoError(t, err) require.True(t, added) addresses := make([]p2pproto.PexAddress, 101) for i := 0; i < len(addresses); i++ { - nodeAddress := p2p.NodeAddress{Protocol: p2p.MemoryProtocol, NodeID: randomNodeID(t)} + nodeAddress := p2p.NodeAddress{Protocol: p2p.MemoryProtocol, NodeID: randomNodeID()} addresses[i] = p2pproto.PexAddress{ URL: nodeAddress.String(), } @@ -211,7 +211,8 @@ func TestReactorSmallPeerStoreInALargeNetwork(t *testing.T) { require.Eventually(t, func() bool { // nolint:scopelint return testNet.network.Nodes[nodeID].PeerManager.PeerRatio() >= 0.9 - }, longWait, checkFrequency) + }, longWait, checkFrequency, + "peer ratio is: %f", testNet.network.Nodes[nodeID].PeerManager.PeerRatio()) } } @@ -288,7 +289,6 @@ func setupSingle(ctx context.Context, t *testing.T) *singleTestReactor { pexErrCh := make(chan p2p.PeerError, chBuf) pexCh := p2p.NewChannel( p2p.ChannelID(pex.PexChannel), - new(p2pproto.PexMessage), pexInCh, pexOutCh, pexErrCh, @@ -303,8 +303,7 @@ func setupSingle(ctx context.Context, t *testing.T) *singleTestReactor { return pexCh, nil } - reactor, err := pex.NewReactor(ctx, log.TestingLogger(), peerManager, chCreator, peerUpdates) - require.NoError(t, err) + reactor := pex.NewReactor(log.NewNopLogger(), peerManager, chCreator, func(_ context.Context) *p2p.PeerUpdates { return peerUpdates }) require.NoError(t, reactor.Start(ctx)) t.Cleanup(reactor.Wait) @@ -365,7 +364,7 @@ func setupNetwork(ctx context.Context, t *testing.T, opts testOptions) *reactorT realNodes := opts.TotalNodes - opts.MockNodes rts := &reactorTestSuite{ - logger: log.TestingLogger().With("testCase", t.Name()), + logger: log.NewNopLogger().With("testCase", t.Name()), network: p2ptest.MakeNetwork(ctx, t, networkOpts), reactors: make(map[types.NodeID]*pex.Reactor, realNodes), pexChannels: make(map[types.NodeID]*p2p.Channel, opts.TotalNodes), @@ -381,6 +380,10 @@ func setupNetwork(ctx context.Context, t *testing.T, opts testOptions) *reactorT idx := 0 for nodeID := range rts.network.Nodes { + // make a copy to avoid getting hit by the range ref + // confusion: + nodeID := nodeID + rts.peerChans[nodeID] = make(chan p2p.PeerUpdate, chBuf) rts.peerUpdates[nodeID] = p2p.NewPeerUpdates(rts.peerChans[nodeID], chBuf) rts.network.Nodes[nodeID].PeerManager.Register(ctx, rts.peerUpdates[nodeID]) @@ -393,15 +396,12 @@ func setupNetwork(ctx context.Context, t *testing.T, opts testOptions) *reactorT if idx < opts.MockNodes { rts.mocks = append(rts.mocks, nodeID) } else { - var err error - rts.reactors[nodeID], err = pex.NewReactor( - ctx, + rts.reactors[nodeID] = pex.NewReactor( rts.logger.With("nodeID", nodeID), rts.network.Nodes[nodeID].PeerManager, chCreator, - rts.peerUpdates[nodeID], + func(_ context.Context) *p2p.PeerUpdates { return rts.peerUpdates[nodeID] }, ) - require.NoError(t, err) } rts.nodes = append(rts.nodes, nodeID) @@ -426,9 +426,10 @@ func setupNetwork(ctx context.Context, t *testing.T, opts testOptions) *reactorT func (r *reactorTestSuite) start(ctx context.Context, t *testing.T) { t.Helper() - for _, reactor := range r.reactors { + for name, reactor := range r.reactors { require.NoError(t, reactor.Start(ctx)) require.True(t, reactor.IsRunning()) + t.Log("started", name) } } @@ -451,15 +452,12 @@ func (r *reactorTestSuite) addNodes(ctx context.Context, t *testing.T, nodes int return r.pexChannels[nodeID], nil } - var err error - r.reactors[nodeID], err = pex.NewReactor( - ctx, + r.reactors[nodeID] = pex.NewReactor( r.logger.With("nodeID", nodeID), r.network.Nodes[nodeID].PeerManager, chCreator, - r.peerUpdates[nodeID], + func(_ context.Context) *p2p.PeerUpdates { return r.peerUpdates[nodeID] }, ) - require.NoError(t, err) r.nodes = append(r.nodes, nodeID) r.total++ } @@ -731,6 +729,6 @@ func newNodeID(t *testing.T, id string) types.NodeID { return nodeID } -func randomNodeID(t *testing.T) types.NodeID { +func randomNodeID() types.NodeID { return types.NodeIDFromPubKey(ed25519.GenPrivKey().PubKey()) } diff --git a/internal/p2p/router.go b/internal/p2p/router.go index f9b3d1ad8..459be7975 100644 --- a/internal/p2p/router.go +++ b/internal/p2p/router.go @@ -148,22 +148,21 @@ type Router struct { *service.BaseService logger log.Logger - metrics *Metrics - options RouterOptions - nodeInfo types.NodeInfo - privKey crypto.PrivKey - peerManager *PeerManager - chDescs []*ChannelDescriptor - transports []Transport - endpoints []Endpoint - connTracker connectionTracker - protocolTransports map[Protocol]Transport + metrics *Metrics + options RouterOptions + privKey crypto.PrivKey + peerManager *PeerManager + chDescs []*ChannelDescriptor + transport Transport + endpoint *Endpoint + connTracker connectionTracker peerMtx sync.RWMutex peerQueues map[types.NodeID]queue // outbound messages per peer for all channels // the channels that the peer queue has open - peerChannels map[types.NodeID]ChannelIDSet - queueFactory func(int) queue + peerChannels map[types.NodeID]ChannelIDSet + queueFactory func(int) queue + nodeInfoProducer func() *types.NodeInfo // FIXME: We don't strictly need to use a mutex for this if we seal the // channels on router start. This depends on whether we want to allow @@ -177,14 +176,13 @@ type Router struct { // listening on appropriate interfaces, and will be closed by the Router when it // stops. func NewRouter( - ctx context.Context, logger log.Logger, metrics *Metrics, - nodeInfo types.NodeInfo, privKey crypto.PrivKey, peerManager *PeerManager, - transports []Transport, - endpoints []Endpoint, + nodeInfoProducer func() *types.NodeInfo, + transport Transport, + endpoint *Endpoint, options RouterOptions, ) (*Router, error) { @@ -193,43 +191,27 @@ func NewRouter( } router := &Router{ - logger: logger, - metrics: metrics, - nodeInfo: nodeInfo, - privKey: privKey, + logger: logger, + metrics: metrics, + privKey: privKey, + nodeInfoProducer: nodeInfoProducer, connTracker: newConnTracker( options.MaxIncomingConnectionAttempts, options.IncomingConnectionWindow, ), - chDescs: make([]*ChannelDescriptor, 0), - transports: transports, - endpoints: endpoints, - protocolTransports: map[Protocol]Transport{}, - peerManager: peerManager, - options: options, - channelQueues: map[ChannelID]queue{}, - channelMessages: map[ChannelID]proto.Message{}, - peerQueues: map[types.NodeID]queue{}, - peerChannels: make(map[types.NodeID]ChannelIDSet), + chDescs: make([]*ChannelDescriptor, 0), + transport: transport, + endpoint: endpoint, + peerManager: peerManager, + options: options, + channelQueues: map[ChannelID]queue{}, + channelMessages: map[ChannelID]proto.Message{}, + peerQueues: map[types.NodeID]queue{}, + peerChannels: make(map[types.NodeID]ChannelIDSet), } router.BaseService = service.NewBaseService(logger, "router", router) - qf, err := router.createQueueFactory(ctx) - if err != nil { - return nil, err - } - - router.queueFactory = qf - - for _, transport := range transports { - for _, protocol := range transport.Protocols() { - if _, ok := router.protocolTransports[protocol]; !ok { - router.protocolTransports[protocol] = transport - } - } - } - return router, nil } @@ -280,7 +262,8 @@ func (r *Router) OpenChannel(ctx context.Context, chDesc *ChannelDescriptor) (*C queue := r.queueFactory(chDesc.RecvBufferCapacity) outCh := make(chan Envelope, chDesc.RecvBufferCapacity) errCh := make(chan PeerError, chDesc.RecvBufferCapacity) - channel := NewChannel(id, messageType, queue.dequeue(), outCh, errCh) + channel := NewChannel(id, queue.dequeue(), outCh, errCh) + channel.name = chDesc.Name var wrapper Wrapper if w, ok := messageType.(Wrapper); ok { @@ -291,11 +274,9 @@ func (r *Router) OpenChannel(ctx context.Context, chDesc *ChannelDescriptor) (*C r.channelMessages[id] = messageType // add the channel to the nodeInfo if it's not already there. - r.nodeInfo.AddChannel(uint16(chDesc.ID)) + r.nodeInfoProducer().AddChannel(uint16(chDesc.ID)) - for _, t := range r.transports { - t.AddChannelDescriptors([]*ChannelDescriptor{chDesc}) - } + r.transport.AddChannelDescriptors([]*ChannelDescriptor{chDesc}) go func() { defer func() { @@ -677,12 +658,6 @@ func (r *Router) dialPeer(ctx context.Context, address NodeAddress) (Connection, } for _, endpoint := range endpoints { - transport, ok := r.protocolTransports[endpoint.Protocol] - if !ok { - r.logger.Error("no transport found for protocol", "endpoint", endpoint) - continue - } - dialCtx := ctx if r.options.DialTimeout > 0 { var cancel context.CancelFunc @@ -697,7 +672,7 @@ func (r *Router) dialPeer(ctx context.Context, address NodeAddress) (Connection, // by the peer's endpoint, since e.g. a peer on 192.168.0.0 can reach us // on a private address on this endpoint, but a peer on the public // Internet can't and needs a different public address. - conn, err := transport.Dial(dialCtx, endpoint) + conn, err := r.transport.Dial(dialCtx, endpoint) if err != nil { r.logger.Error("failed to dial endpoint", "peer", address.NodeID, "endpoint", endpoint, "err", err) } else { @@ -722,7 +697,8 @@ func (r *Router) handshakePeer( defer cancel() } - peerInfo, peerKey, err := conn.Handshake(ctx, r.nodeInfo, r.privKey) + nodeInfo := r.nodeInfoProducer() + peerInfo, peerKey, err := conn.Handshake(ctx, *nodeInfo, r.privKey) if err != nil { return peerInfo, err } @@ -737,7 +713,7 @@ func (r *Router) handshakePeer( return peerInfo, fmt.Errorf("expected to connect with peer %q, got %q", expectID, peerInfo.NodeID) } - if err := r.nodeInfo.CompatibleWith(peerInfo); err != nil { + if err := nodeInfo.CompatibleWith(peerInfo); err != nil { return peerInfo, ErrRejected{ err: err, id: peerInfo.ID(), @@ -935,35 +911,29 @@ func (r *Router) evictPeers(ctx context.Context) { } } -// NodeInfo returns a copy of the current NodeInfo. Used for testing. -func (r *Router) NodeInfo() types.NodeInfo { - return r.nodeInfo.Copy() +func (r *Router) setupQueueFactory(ctx context.Context) error { + qf, err := r.createQueueFactory(ctx) + if err != nil { + return err + } + + r.queueFactory = qf + return nil } // OnStart implements service.Service. func (r *Router) OnStart(ctx context.Context) error { - for _, transport := range r.transports { - for _, endpoint := range r.endpoints { - if err := transport.Listen(endpoint); err != nil { - return err - } - } + if err := r.setupQueueFactory(ctx); err != nil { + return err } - r.logger.Info( - "starting router", - "node_id", r.nodeInfo.NodeID, - "channels", r.nodeInfo.Channels, - "listen_addr", r.nodeInfo.ListenAddr, - "transports", len(r.transports), - ) + if err := r.transport.Listen(r.endpoint); err != nil { + return err + } go r.dialPeers(ctx) go r.evictPeers(ctx) - - for _, transport := range r.transports { - go r.acceptPeers(ctx, transport) - } + go r.acceptPeers(ctx, r.transport) return nil } @@ -976,10 +946,8 @@ func (r *Router) OnStart(ctx context.Context) error { // sender's responsibility. func (r *Router) OnStop() { // Close transport listeners (unblocks Accept calls). - for _, transport := range r.transports { - if err := transport.Close(); err != nil { - r.logger.Error("failed to close transport", "transport", transport, "err", err) - } + if err := r.transport.Close(); err != nil { + r.logger.Error("failed to close transport", "err", err) } // Collect all remaining queues, and wait for them to close. diff --git a/internal/p2p/router_filter_test.go b/internal/p2p/router_filter_test.go index 8915dc888..217be8d32 100644 --- a/internal/p2p/router_filter_test.go +++ b/internal/p2p/router_filter_test.go @@ -15,7 +15,7 @@ import ( func TestConnectionFiltering(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.TestingLogger() + logger := log.NewNopLogger() filterByIPCount := 0 router := &Router{ diff --git a/internal/p2p/router_init_test.go b/internal/p2p/router_init_test.go index 19b4aa94c..20c3cb6dc 100644 --- a/internal/p2p/router_init_test.go +++ b/internal/p2p/router_init_test.go @@ -23,29 +23,35 @@ func TestRouter_ConstructQueueFactory(t *testing.T) { t.Run("Default", func(t *testing.T) { require.Zero(t, os.Getenv("TM_P2P_QUEUE")) opts := RouterOptions{} - r, err := NewRouter(ctx, log.NewNopLogger(), nil, types.NodeInfo{}, nil, nil, nil, nil, opts) + r, err := NewRouter(log.NewNopLogger(), nil, nil, nil, func() *types.NodeInfo { return &types.NodeInfo{} }, nil, nil, opts) require.NoError(t, err) + require.NoError(t, r.setupQueueFactory(ctx)) + _, ok := r.queueFactory(1).(*fifoQueue) require.True(t, ok) }) t.Run("Fifo", func(t *testing.T) { opts := RouterOptions{QueueType: queueTypeFifo} - r, err := NewRouter(ctx, log.NewNopLogger(), nil, types.NodeInfo{}, nil, nil, nil, nil, opts) + r, err := NewRouter(log.NewNopLogger(), nil, nil, nil, func() *types.NodeInfo { return &types.NodeInfo{} }, nil, nil, opts) require.NoError(t, err) + require.NoError(t, r.setupQueueFactory(ctx)) + _, ok := r.queueFactory(1).(*fifoQueue) require.True(t, ok) }) t.Run("Priority", func(t *testing.T) { opts := RouterOptions{QueueType: queueTypePriority} - r, err := NewRouter(ctx, log.NewNopLogger(), nil, types.NodeInfo{}, nil, nil, nil, nil, opts) + r, err := NewRouter(log.NewNopLogger(), nil, nil, nil, func() *types.NodeInfo { return &types.NodeInfo{} }, nil, nil, opts) require.NoError(t, err) + require.NoError(t, r.setupQueueFactory(ctx)) + q, ok := r.queueFactory(1).(*pqScheduler) require.True(t, ok) defer q.close() }) t.Run("NonExistant", func(t *testing.T) { opts := RouterOptions{QueueType: "fast"} - _, err := NewRouter(ctx, log.NewNopLogger(), nil, types.NodeInfo{}, nil, nil, nil, nil, opts) + _, err := NewRouter(log.NewNopLogger(), nil, nil, nil, func() *types.NodeInfo { return &types.NodeInfo{} }, nil, nil, opts) require.Error(t, err) require.Contains(t, err.Error(), "fast") }) diff --git a/internal/p2p/router_test.go b/internal/p2p/router_test.go index 61bdd2f89..663e6b81c 100644 --- a/internal/p2p/router_test.go +++ b/internal/p2p/router_test.go @@ -105,15 +105,16 @@ func TestRouter_Channel_Basic(t *testing.T) { peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{}) require.NoError(t, err) + testnet := p2ptest.MakeNetwork(ctx, t, p2ptest.NetworkOptions{NumNodes: 1}) + router, err := p2p.NewRouter( - ctx, - log.TestingLogger(), + log.NewNopLogger(), p2p.NopMetrics(), - selfInfo, selfKey, peerManager, - nil, - nil, + func() *types.NodeInfo { return &selfInfo }, + testnet.RandomNode().Transport, + &p2p.Endpoint{}, p2p.RouterOptions{}, ) require.NoError(t, err) @@ -127,7 +128,6 @@ func TestRouter_Channel_Basic(t *testing.T) { channel, err := router.OpenChannel(chctx, chDesc) require.NoError(t, err) - require.Contains(t, router.NodeInfo().Channels, byte(chDesc.ID)) require.NotNil(t, channel) // Opening the same channel again should fail. @@ -137,9 +137,7 @@ func TestRouter_Channel_Basic(t *testing.T) { // Opening a different channel should work. chDesc2 := &p2p.ChannelDescriptor{ID: 2, MessageType: &p2ptest.Message{}} _, err = router.OpenChannel(ctx, chDesc2) - require.NoError(t, err) - require.Contains(t, router.NodeInfo().Channels, byte(chDesc2.ID)) // Closing the channel, then opening it again should be fine. chcancel() @@ -397,10 +395,10 @@ func TestRouter_AcceptPeers(t *testing.T) { mockTransport := &mocks.Transport{} mockTransport.On("String").Maybe().Return("mock") - mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"}) mockTransport.On("Close").Return(nil).Maybe() mockTransport.On("Accept", mock.Anything).Once().Return(mockConnection, nil) mockTransport.On("Accept", mock.Anything).Maybe().Return(nil, io.EOF) + mockTransport.On("Listen", mock.Anything).Return(nil) // Set up and start the router. peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{}) @@ -409,13 +407,12 @@ func TestRouter_AcceptPeers(t *testing.T) { sub := peerManager.Subscribe(ctx) router, err := p2p.NewRouter( - ctx, - log.TestingLogger(), + log.NewNopLogger(), p2p.NopMetrics(), - selfInfo, selfKey, peerManager, - []p2p.Transport{mockTransport}, + func() *types.NodeInfo { return &selfInfo }, + mockTransport, nil, p2p.RouterOptions{}, ) @@ -455,22 +452,21 @@ func TestRouter_AcceptPeers_Error(t *testing.T) { // the router from calling Accept again. mockTransport := &mocks.Transport{} mockTransport.On("String").Maybe().Return("mock") - mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"}) mockTransport.On("Accept", mock.Anything).Once().Return(nil, errors.New("boom")) mockTransport.On("Close").Return(nil) + mockTransport.On("Listen", mock.Anything).Return(nil) // Set up and start the router. peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{}) require.NoError(t, err) router, err := p2p.NewRouter( - ctx, - log.TestingLogger(), + log.NewNopLogger(), p2p.NopMetrics(), - selfInfo, selfKey, peerManager, - []p2p.Transport{mockTransport}, + func() *types.NodeInfo { return &selfInfo }, + mockTransport, nil, p2p.RouterOptions{}, ) @@ -493,22 +489,21 @@ func TestRouter_AcceptPeers_ErrorEOF(t *testing.T) { // the router from calling Accept again. mockTransport := &mocks.Transport{} mockTransport.On("String").Maybe().Return("mock") - mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"}) mockTransport.On("Accept", mock.Anything).Once().Return(nil, io.EOF) mockTransport.On("Close").Return(nil) + mockTransport.On("Listen", mock.Anything).Return(nil) // Set up and start the router. peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{}) require.NoError(t, err) router, err := p2p.NewRouter( - ctx, - log.TestingLogger(), + log.NewNopLogger(), p2p.NopMetrics(), - selfInfo, selfKey, peerManager, - []p2p.Transport{mockTransport}, + func() *types.NodeInfo { return &selfInfo }, + mockTransport, nil, p2p.RouterOptions{}, ) @@ -542,25 +537,24 @@ func TestRouter_AcceptPeers_HeadOfLineBlocking(t *testing.T) { mockTransport := &mocks.Transport{} mockTransport.On("String").Maybe().Return("mock") - mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"}) mockTransport.On("Close").Return(nil) mockTransport.On("Accept", mock.Anything).Times(3).Run(func(_ mock.Arguments) { acceptCh <- true }).Return(mockConnection, nil) mockTransport.On("Accept", mock.Anything).Once().Return(nil, io.EOF) + mockTransport.On("Listen", mock.Anything).Return(nil) // Set up and start the router. peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{}) require.NoError(t, err) router, err := p2p.NewRouter( - ctx, - log.TestingLogger(), + log.NewNopLogger(), p2p.NopMetrics(), - selfInfo, selfKey, peerManager, - []p2p.Transport{mockTransport}, + func() *types.NodeInfo { return &selfInfo }, + mockTransport, nil, p2p.RouterOptions{}, ) @@ -616,7 +610,7 @@ func TestRouter_DialPeers(t *testing.T) { defer cancel() address := p2p.NodeAddress{Protocol: "mock", NodeID: tc.dialID} - endpoint := p2p.Endpoint{Protocol: "mock", Path: string(tc.dialID)} + endpoint := &p2p.Endpoint{Protocol: "mock", Path: string(tc.dialID)} // Set up a mock transport that handshakes. connCtx, connCancel := context.WithCancel(context.Background()) @@ -634,8 +628,8 @@ func TestRouter_DialPeers(t *testing.T) { mockTransport := &mocks.Transport{} mockTransport.On("String").Maybe().Return("mock") - mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"}) mockTransport.On("Close").Return(nil).Maybe() + mockTransport.On("Listen", mock.Anything).Return(nil) mockTransport.On("Accept", mock.Anything).Maybe().Return(nil, io.EOF) if tc.dialErr == nil { mockTransport.On("Dial", mock.Anything, endpoint).Once().Return(mockConnection, nil) @@ -658,13 +652,12 @@ func TestRouter_DialPeers(t *testing.T) { sub := peerManager.Subscribe(ctx) router, err := p2p.NewRouter( - ctx, - log.TestingLogger(), + log.NewNopLogger(), p2p.NopMetrics(), - selfInfo, selfKey, peerManager, - []p2p.Transport{mockTransport}, + func() *types.NodeInfo { return &selfInfo }, + mockTransport, nil, p2p.RouterOptions{}, ) @@ -717,11 +710,11 @@ func TestRouter_DialPeers_Parallel(t *testing.T) { mockTransport := &mocks.Transport{} mockTransport.On("String").Maybe().Return("mock") - mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"}) mockTransport.On("Close").Return(nil) + mockTransport.On("Listen", mock.Anything).Return(nil) mockTransport.On("Accept", mock.Anything).Once().Return(nil, io.EOF) for _, address := range []p2p.NodeAddress{a, b, c} { - endpoint := p2p.Endpoint{Protocol: address.Protocol, Path: string(address.NodeID)} + endpoint := &p2p.Endpoint{Protocol: address.Protocol, Path: string(address.NodeID)} mockTransport.On("Dial", mock.Anything, endpoint).Run(func(_ mock.Arguments) { dialCh <- true }).Return(mockConnection, nil) @@ -744,13 +737,12 @@ func TestRouter_DialPeers_Parallel(t *testing.T) { require.True(t, added) router, err := p2p.NewRouter( - ctx, - log.TestingLogger(), + log.NewNopLogger(), p2p.NopMetrics(), - selfInfo, selfKey, peerManager, - []p2p.Transport{mockTransport}, + func() *types.NodeInfo { return &selfInfo }, + mockTransport, nil, p2p.RouterOptions{ DialSleep: func(_ context.Context) {}, @@ -807,10 +799,10 @@ func TestRouter_EvictPeers(t *testing.T) { mockTransport := &mocks.Transport{} mockTransport.On("String").Maybe().Return("mock") - mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"}) mockTransport.On("Close").Return(nil) mockTransport.On("Accept", mock.Anything).Once().Return(mockConnection, nil) mockTransport.On("Accept", mock.Anything).Maybe().Return(nil, io.EOF) + mockTransport.On("Listen", mock.Anything).Return(nil) // Set up and start the router. peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{}) @@ -819,13 +811,12 @@ func TestRouter_EvictPeers(t *testing.T) { sub := peerManager.Subscribe(ctx) router, err := p2p.NewRouter( - ctx, - log.TestingLogger(), + log.NewNopLogger(), p2p.NopMetrics(), - selfInfo, selfKey, peerManager, - []p2p.Transport{mockTransport}, + func() *types.NodeInfo { return &selfInfo }, + mockTransport, nil, p2p.RouterOptions{}, ) @@ -872,23 +863,22 @@ func TestRouter_ChannelCompatability(t *testing.T) { mockTransport := &mocks.Transport{} mockTransport.On("String").Maybe().Return("mock") - mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"}) mockTransport.On("Close").Return(nil) mockTransport.On("Accept", mock.Anything).Once().Return(mockConnection, nil) mockTransport.On("Accept", mock.Anything).Once().Return(nil, io.EOF) + mockTransport.On("Listen", mock.Anything).Return(nil) // Set up and start the router. peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{}) require.NoError(t, err) router, err := p2p.NewRouter( - ctx, - log.TestingLogger(), + log.NewNopLogger(), p2p.NopMetrics(), - selfInfo, selfKey, peerManager, - []p2p.Transport{mockTransport}, + func() *types.NodeInfo { return &selfInfo }, + mockTransport, nil, p2p.RouterOptions{}, ) @@ -926,10 +916,10 @@ func TestRouter_DontSendOnInvalidChannel(t *testing.T) { mockTransport := &mocks.Transport{} mockTransport.On("AddChannelDescriptors", mock.Anything).Return() mockTransport.On("String").Maybe().Return("mock") - mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"}) mockTransport.On("Close").Return(nil) mockTransport.On("Accept", mock.Anything).Once().Return(mockConnection, nil) mockTransport.On("Accept", mock.Anything).Maybe().Return(nil, io.EOF) + mockTransport.On("Listen", mock.Anything).Return(nil) // Set up and start the router. peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{}) @@ -938,13 +928,12 @@ func TestRouter_DontSendOnInvalidChannel(t *testing.T) { sub := peerManager.Subscribe(ctx) router, err := p2p.NewRouter( - ctx, - log.TestingLogger(), + log.NewNopLogger(), p2p.NopMetrics(), - selfInfo, selfKey, peerManager, - []p2p.Transport{mockTransport}, + func() *types.NodeInfo { return &selfInfo }, + mockTransport, nil, p2p.RouterOptions{}, ) diff --git a/internal/p2p/transport.go b/internal/p2p/transport.go index 041bbda3a..7a965260a 100644 --- a/internal/p2p/transport.go +++ b/internal/p2p/transport.go @@ -24,7 +24,7 @@ type Protocol string // Transport is a connection-oriented mechanism for exchanging data with a peer. type Transport interface { // Listen starts the transport on the specified endpoint. - Listen(Endpoint) error + Listen(*Endpoint) error // Protocols returns the protocols supported by the transport. The Router // uses this to pick a transport for an Endpoint. @@ -34,7 +34,7 @@ type Transport interface { // // How to listen is transport-dependent, e.g. MConnTransport uses Listen() while // MemoryTransport starts listening via MemoryNetwork.CreateTransport(). - Endpoints() []Endpoint + Endpoint() (*Endpoint, error) // Accept waits for the next inbound connection on a listening endpoint, blocking // until either a connection is available or the transport is closed. On closure, @@ -42,7 +42,7 @@ type Transport interface { Accept(context.Context) (Connection, error) // Dial creates an outbound connection to an endpoint. - Dial(context.Context, Endpoint) (Connection, error) + Dial(context.Context, *Endpoint) (Connection, error) // Close stops accepting new connections, but does not close active connections. Close() error @@ -129,13 +129,13 @@ type Endpoint struct { } // NewEndpoint constructs an Endpoint from a types.NetAddress structure. -func NewEndpoint(addr string) (Endpoint, error) { +func NewEndpoint(addr string) (*Endpoint, error) { ip, port, err := types.ParseAddressString(addr) if err != nil { - return Endpoint{}, err + return nil, err } - return Endpoint{ + return &Endpoint{ Protocol: MConnProtocol, IP: ip, Port: port, diff --git a/internal/p2p/transport_mconn.go b/internal/p2p/transport_mconn.go index 6e6728f11..7bf17d1a0 100644 --- a/internal/p2p/transport_mconn.go +++ b/internal/p2p/transport_mconn.go @@ -78,25 +78,25 @@ func (m *MConnTransport) Protocols() []Protocol { return []Protocol{MConnProtocol, TCPProtocol} } -// Endpoints implements Transport. -func (m *MConnTransport) Endpoints() []Endpoint { +// Endpoint implements Transport. +func (m *MConnTransport) Endpoint() (*Endpoint, error) { if m.listener == nil { - return []Endpoint{} + return nil, errors.New("listenter not defined") } select { case <-m.doneCh: - return []Endpoint{} + return nil, errors.New("transport closed") default: } - endpoint := Endpoint{ + endpoint := &Endpoint{ Protocol: MConnProtocol, } if addr, ok := m.listener.Addr().(*net.TCPAddr); ok { endpoint.IP = addr.IP endpoint.Port = uint16(addr.Port) } - return []Endpoint{endpoint} + return endpoint, nil } // Listen asynchronously listens for inbound connections on the given endpoint. @@ -106,7 +106,7 @@ func (m *MConnTransport) Endpoints() []Endpoint { // FIXME: Listen currently only supports listening on a single endpoint, it // might be useful to support listening on multiple addresses (e.g. IPv4 and // IPv6, or a private and public address) via multiple Listen() calls. -func (m *MConnTransport) Listen(endpoint Endpoint) error { +func (m *MConnTransport) Listen(endpoint *Endpoint) error { if m.listener != nil { return errors.New("transport is already listening") } @@ -170,7 +170,7 @@ func (m *MConnTransport) Accept(ctx context.Context) (Connection, error) { } // Dial implements Transport. -func (m *MConnTransport) Dial(ctx context.Context, endpoint Endpoint) (Connection, error) { +func (m *MConnTransport) Dial(ctx context.Context, endpoint *Endpoint) (Connection, error) { if err := m.validateEndpoint(endpoint); err != nil { return nil, err } @@ -217,7 +217,7 @@ func (m *MConnTransport) AddChannelDescriptors(channelDesc []*ChannelDescriptor) } // validateEndpoint validates an endpoint. -func (m *MConnTransport) validateEndpoint(endpoint Endpoint) error { +func (m *MConnTransport) validateEndpoint(endpoint *Endpoint) error { if err := endpoint.Validate(); err != nil { return err } diff --git a/internal/p2p/transport_mconn_test.go b/internal/p2p/transport_mconn_test.go index a25278933..c478dbe1d 100644 --- a/internal/p2p/transport_mconn_test.go +++ b/internal/p2p/transport_mconn_test.go @@ -20,12 +20,12 @@ import ( func init() { testTransports["mconn"] = func(t *testing.T) p2p.Transport { transport := p2p.NewMConnTransport( - log.TestingLogger(), + log.NewNopLogger(), conn.DefaultMConnConfig(), []*p2p.ChannelDescriptor{{ID: chID, Priority: 1}}, p2p.MConnTransportOptions{}, ) - err := transport.Listen(p2p.Endpoint{ + err := transport.Listen(&p2p.Endpoint{ Protocol: p2p.MConnProtocol, IP: net.IPv4(127, 0, 0, 1), Port: 0, // assign a random port @@ -40,7 +40,7 @@ func init() { func TestMConnTransport_AcceptBeforeListen(t *testing.T) { transport := p2p.NewMConnTransport( - log.TestingLogger(), + log.NewNopLogger(), conn.DefaultMConnConfig(), []*p2p.ChannelDescriptor{{ID: chID, Priority: 1}}, p2p.MConnTransportOptions{ @@ -63,7 +63,7 @@ func TestMConnTransport_AcceptMaxAcceptedConnections(t *testing.T) { defer cancel() transport := p2p.NewMConnTransport( - log.TestingLogger(), + log.NewNopLogger(), conn.DefaultMConnConfig(), []*p2p.ChannelDescriptor{{ID: chID, Priority: 1}}, p2p.MConnTransportOptions{ @@ -73,13 +73,14 @@ func TestMConnTransport_AcceptMaxAcceptedConnections(t *testing.T) { t.Cleanup(func() { _ = transport.Close() }) - err := transport.Listen(p2p.Endpoint{ + err := transport.Listen(&p2p.Endpoint{ Protocol: p2p.MConnProtocol, IP: net.IPv4(127, 0, 0, 1), }) require.NoError(t, err) - require.NotEmpty(t, transport.Endpoints()) - endpoint := transport.Endpoints()[0] + endpoint, err := transport.Endpoint() + require.NoError(t, err) + require.NotNil(t, endpoint) // Start a goroutine to just accept any connections. acceptCh := make(chan p2p.Connection, 10) @@ -132,20 +133,20 @@ func TestMConnTransport_Listen(t *testing.T) { defer cancel() testcases := []struct { - endpoint p2p.Endpoint + endpoint *p2p.Endpoint ok bool }{ // Valid v4 and v6 addresses, with mconn and tcp protocols. - {p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv4zero}, true}, - {p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv4(127, 0, 0, 1)}, true}, - {p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv6zero}, true}, - {p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv6loopback}, true}, - {p2p.Endpoint{Protocol: p2p.TCPProtocol, IP: net.IPv4zero}, true}, + {&p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv4zero}, true}, + {&p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv4(127, 0, 0, 1)}, true}, + {&p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv6zero}, true}, + {&p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv6loopback}, true}, + {&p2p.Endpoint{Protocol: p2p.TCPProtocol, IP: net.IPv4zero}, true}, // Invalid endpoints. - {p2p.Endpoint{}, false}, - {p2p.Endpoint{Protocol: p2p.MConnProtocol, Path: "foo"}, false}, - {p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv4zero, Path: "foo"}, false}, + {&p2p.Endpoint{}, false}, + {&p2p.Endpoint{Protocol: p2p.MConnProtocol, Path: "foo"}, false}, + {&p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv4zero, Path: "foo"}, false}, } for _, tc := range testcases { tc := tc @@ -153,17 +154,19 @@ func TestMConnTransport_Listen(t *testing.T) { t.Cleanup(leaktest.Check(t)) transport := p2p.NewMConnTransport( - log.TestingLogger(), + log.NewNopLogger(), conn.DefaultMConnConfig(), []*p2p.ChannelDescriptor{{ID: chID, Priority: 1}}, p2p.MConnTransportOptions{}, ) // Transport should not listen on any endpoints yet. - require.Empty(t, transport.Endpoints()) + endpoint, err := transport.Endpoint() + require.Error(t, err) + require.Nil(t, endpoint) // Start listening, and check any expected errors. - err := transport.Listen(tc.endpoint) + err = transport.Listen(tc.endpoint) if !tc.ok { require.Error(t, err) return @@ -171,9 +174,9 @@ func TestMConnTransport_Listen(t *testing.T) { require.NoError(t, err) // Check the endpoint. - endpoints := transport.Endpoints() - require.Len(t, endpoints, 1) - endpoint := endpoints[0] + endpoint, err = transport.Endpoint() + require.NoError(t, err) + require.NotNil(t, endpoint) require.Equal(t, p2p.MConnProtocol, endpoint.Protocol) if tc.endpoint.IP.IsUnspecified() { diff --git a/internal/p2p/transport_memory.go b/internal/p2p/transport_memory.go index 528ce4bb6..3eb4c5b51 100644 --- a/internal/p2p/transport_memory.go +++ b/internal/p2p/transport_memory.go @@ -119,7 +119,7 @@ func (t *MemoryTransport) String() string { return string(MemoryProtocol) } -func (*MemoryTransport) Listen(Endpoint) error { return nil } +func (*MemoryTransport) Listen(*Endpoint) error { return nil } func (t *MemoryTransport) AddChannelDescriptors([]*ChannelDescriptor) {} @@ -129,19 +129,19 @@ func (t *MemoryTransport) Protocols() []Protocol { } // Endpoints implements Transport. -func (t *MemoryTransport) Endpoints() []Endpoint { +func (t *MemoryTransport) Endpoint() (*Endpoint, error) { if n := t.network.GetTransport(t.nodeID); n == nil { - return []Endpoint{} + return nil, errors.New("node not defined") } - return []Endpoint{{ + return &Endpoint{ Protocol: MemoryProtocol, Path: string(t.nodeID), // An arbitrary IP and port is used in order for the pex // reactor to be able to send addresses to one another. IP: net.IPv4zero, Port: 0, - }} + }, nil } // Accept implements Transport. @@ -158,7 +158,7 @@ func (t *MemoryTransport) Accept(ctx context.Context) (Connection, error) { } // Dial implements Transport. -func (t *MemoryTransport) Dial(ctx context.Context, endpoint Endpoint) (Connection, error) { +func (t *MemoryTransport) Dial(ctx context.Context, endpoint *Endpoint) (Connection, error) { if endpoint.Protocol != MemoryProtocol { return nil, fmt.Errorf("invalid protocol %q", endpoint.Protocol) } diff --git a/internal/p2p/transport_memory_test.go b/internal/p2p/transport_memory_test.go index 0569faa30..33d96cdb8 100644 --- a/internal/p2p/transport_memory_test.go +++ b/internal/p2p/transport_memory_test.go @@ -19,7 +19,7 @@ func init() { testTransports["memory"] = func(t *testing.T) p2p.Transport { if network == nil { - network = p2p.NewMemoryNetwork(log.TestingLogger(), 1) + network = p2p.NewMemoryNetwork(log.NewNopLogger(), 1) } i := byte(network.Size()) nodeID, err := types.NewNodeID(hex.EncodeToString(bytes.Repeat([]byte{i<<4 + i}, 20))) diff --git a/internal/p2p/transport_test.go b/internal/p2p/transport_test.go index 8f4f90483..b4edf9bc9 100644 --- a/internal/p2p/transport_test.go +++ b/internal/p2p/transport_test.go @@ -87,9 +87,9 @@ func TestTransport_DialEndpoints(t *testing.T) { withTransports(ctx, t, func(ctx context.Context, t *testing.T, makeTransport transportFactory) { a := makeTransport(t) - endpoints := a.Endpoints() - require.NotEmpty(t, endpoints) - endpoint := endpoints[0] + endpoint, err := a.Endpoint() + require.NoError(t, err) + require.NotNil(t, endpoint) // Spawn a goroutine to simply accept any connections until closed. go func() { @@ -108,19 +108,19 @@ func TestTransport_DialEndpoints(t *testing.T) { require.NoError(t, conn.Close()) // Dialing empty endpoint should error. - _, err = a.Dial(ctx, p2p.Endpoint{}) + _, err = a.Dial(ctx, &p2p.Endpoint{}) require.Error(t, err) // Dialing without protocol should error. - noProtocol := endpoint + noProtocol := *endpoint noProtocol.Protocol = "" - _, err = a.Dial(ctx, noProtocol) + _, err = a.Dial(ctx, &noProtocol) require.Error(t, err) // Dialing with invalid protocol should error. - fooProtocol := endpoint + fooProtocol := *endpoint fooProtocol.Protocol = "foo" - _, err = a.Dial(ctx, fooProtocol) + _, err = a.Dial(ctx, &fooProtocol) require.Error(t, err) // Tests for networked endpoints (with IP). @@ -129,11 +129,12 @@ func TestTransport_DialEndpoints(t *testing.T) { tc := tc t.Run(tc.ip.String(), func(t *testing.T) { e := endpoint + require.NotNil(t, e) e.IP = tc.ip conn, err := a.Dial(ctx, e) if tc.ok { - require.NoError(t, conn.Close()) require.NoError(t, err) + require.NoError(t, conn.Close()) } else { require.Error(t, err, "endpoint=%s", e) } @@ -167,16 +168,18 @@ func TestTransport_Dial(t *testing.T) { a := makeTransport(t) b := makeTransport(t) - require.NotEmpty(t, a.Endpoints()) - require.NotEmpty(t, b.Endpoints()) - aEndpoint := a.Endpoints()[0] - bEndpoint := b.Endpoints()[0] + aEndpoint, err := a.Endpoint() + require.NoError(t, err) + require.NotNil(t, aEndpoint) + bEndpoint, err := b.Endpoint() + require.NoError(t, err) + require.NotNil(t, bEndpoint) // Context cancellation should error. We can't test timeouts since we'd // need a non-responsive endpoint. cancelCtx, cancel := context.WithCancel(ctx) cancel() - _, err := a.Dial(cancelCtx, bEndpoint) + _, err = a.Dial(cancelCtx, bEndpoint) require.Error(t, err) // Unavailable endpoint should error. @@ -210,21 +213,26 @@ func TestTransport_Endpoints(t *testing.T) { b := makeTransport(t) // Both transports return valid and different endpoints. - aEndpoints := a.Endpoints() - bEndpoints := b.Endpoints() - require.NotEmpty(t, aEndpoints) - require.NotEmpty(t, bEndpoints) - require.NotEqual(t, aEndpoints, bEndpoints) - for _, endpoint := range append(aEndpoints, bEndpoints...) { + aEndpoint, err := a.Endpoint() + require.NoError(t, err) + require.NotNil(t, aEndpoint) + bEndpoint, err := b.Endpoint() + require.NoError(t, err) + require.NotNil(t, bEndpoint) + require.NotEqual(t, aEndpoint, bEndpoint) + for _, endpoint := range []*p2p.Endpoint{aEndpoint, bEndpoint} { err := endpoint.Validate() require.NoError(t, err, "invalid endpoint %q", endpoint) } // When closed, the transport should no longer return any endpoints. - err := a.Close() + require.NoError(t, a.Close()) + aEndpoint, err = a.Endpoint() + require.Error(t, err) + require.Nil(t, aEndpoint) + bEndpoint, err = b.Endpoint() require.NoError(t, err) - require.Empty(t, a.Endpoints()) - require.NotEmpty(t, b.Endpoints()) + require.NotNil(t, bEndpoint) }) } @@ -235,13 +243,12 @@ func TestTransport_Protocols(t *testing.T) { withTransports(ctx, t, func(ctx context.Context, t *testing.T, makeTransport transportFactory) { a := makeTransport(t) protocols := a.Protocols() - endpoints := a.Endpoints() + endpoint, err := a.Endpoint() + require.NoError(t, err) require.NotEmpty(t, protocols) - require.NotEmpty(t, endpoints) + require.NotNil(t, endpoint) - for _, endpoint := range endpoints { - require.Contains(t, protocols, endpoint.Protocol) - } + require.Contains(t, protocols, endpoint.Protocol) }) } @@ -595,8 +602,9 @@ func TestEndpoint_Validate(t *testing.T) { func dialAccept(ctx context.Context, t *testing.T, a, b p2p.Transport) (p2p.Connection, p2p.Connection) { t.Helper() - endpoints := b.Endpoints() - require.NotEmpty(t, endpoints, "peer not listening on any endpoints") + endpoint, err := b.Endpoint() + require.NoError(t, err) + require.NotNil(t, endpoint, "peer not listening on any endpoints") ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() @@ -609,7 +617,7 @@ func dialAccept(ctx context.Context, t *testing.T, a, b p2p.Transport) (p2p.Conn acceptCh <- conn }() - dialConn, err := a.Dial(ctx, endpoints[0]) + dialConn, err := a.Dial(ctx, endpoint) require.NoError(t, err) acceptConn := <-acceptCh diff --git a/internal/proxy/client.go b/internal/proxy/client.go index 7444c841e..735c99656 100644 --- a/internal/proxy/client.go +++ b/internal/proxy/client.go @@ -8,6 +8,7 @@ import ( "time" "github.com/go-kit/kit/metrics" + abciclient "github.com/tendermint/tendermint/abci/client" "github.com/tendermint/tendermint/abci/example/kvstore" "github.com/tendermint/tendermint/abci/types" @@ -123,32 +124,32 @@ func kill() error { return p.Signal(syscall.SIGABRT) } -func (app *proxyClient) InitChain(ctx context.Context, req types.RequestInitChain) (*types.ResponseInitChain, error) { +func (app *proxyClient) InitChain(ctx context.Context, req *types.RequestInitChain) (*types.ResponseInitChain, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "init_chain", "type", "sync"))() return app.client.InitChain(ctx, req) } -func (app *proxyClient) PrepareProposal(ctx context.Context, req types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { +func (app *proxyClient) PrepareProposal(ctx context.Context, req *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "prepare_proposal", "type", "sync"))() return app.client.PrepareProposal(ctx, req) } -func (app *proxyClient) ProcessProposal(ctx context.Context, req types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { +func (app *proxyClient) ProcessProposal(ctx context.Context, req *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "process_proposal", "type", "sync"))() return app.client.ProcessProposal(ctx, req) } -func (app *proxyClient) ExtendVote(ctx context.Context, req types.RequestExtendVote) (*types.ResponseExtendVote, error) { +func (app *proxyClient) ExtendVote(ctx context.Context, req *types.RequestExtendVote) (*types.ResponseExtendVote, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "extend_vote", "type", "sync"))() return app.client.ExtendVote(ctx, req) } -func (app *proxyClient) VerifyVoteExtension(ctx context.Context, req types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) { +func (app *proxyClient) VerifyVoteExtension(ctx context.Context, req *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "verify_vote_extension", "type", "sync"))() return app.client.VerifyVoteExtension(ctx, req) } -func (app *proxyClient) FinalizeBlock(ctx context.Context, req types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { +func (app *proxyClient) FinalizeBlock(ctx context.Context, req *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "finalize_block", "type", "sync"))() return app.client.FinalizeBlock(ctx, req) } @@ -163,7 +164,7 @@ func (app *proxyClient) Flush(ctx context.Context) error { return app.client.Flush(ctx) } -func (app *proxyClient) CheckTx(ctx context.Context, req types.RequestCheckTx) (*types.ResponseCheckTx, error) { +func (app *proxyClient) CheckTx(ctx context.Context, req *types.RequestCheckTx) (*types.ResponseCheckTx, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "check_tx", "type", "sync"))() return app.client.CheckTx(ctx, req) } @@ -173,32 +174,32 @@ func (app *proxyClient) Echo(ctx context.Context, msg string) (*types.ResponseEc return app.client.Echo(ctx, msg) } -func (app *proxyClient) Info(ctx context.Context, req types.RequestInfo) (*types.ResponseInfo, error) { +func (app *proxyClient) Info(ctx context.Context, req *types.RequestInfo) (*types.ResponseInfo, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "info", "type", "sync"))() return app.client.Info(ctx, req) } -func (app *proxyClient) Query(ctx context.Context, reqQuery types.RequestQuery) (*types.ResponseQuery, error) { +func (app *proxyClient) Query(ctx context.Context, req *types.RequestQuery) (*types.ResponseQuery, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "query", "type", "sync"))() - return app.client.Query(ctx, reqQuery) + return app.client.Query(ctx, req) } -func (app *proxyClient) ListSnapshots(ctx context.Context, req types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { +func (app *proxyClient) ListSnapshots(ctx context.Context, req *types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "list_snapshots", "type", "sync"))() return app.client.ListSnapshots(ctx, req) } -func (app *proxyClient) OfferSnapshot(ctx context.Context, req types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { +func (app *proxyClient) OfferSnapshot(ctx context.Context, req *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "offer_snapshot", "type", "sync"))() return app.client.OfferSnapshot(ctx, req) } -func (app *proxyClient) LoadSnapshotChunk(ctx context.Context, req types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { +func (app *proxyClient) LoadSnapshotChunk(ctx context.Context, req *types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "load_snapshot_chunk", "type", "sync"))() return app.client.LoadSnapshotChunk(ctx, req) } -func (app *proxyClient) ApplySnapshotChunk(ctx context.Context, req types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { +func (app *proxyClient) ApplySnapshotChunk(ctx context.Context, req *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "apply_snapshot_chunk", "type", "sync"))() return app.client.ApplySnapshotChunk(ctx, req) } diff --git a/internal/proxy/client_test.go b/internal/proxy/client_test.go index ca32b99e8..09ac3f2c8 100644 --- a/internal/proxy/client_test.go +++ b/internal/proxy/client_test.go @@ -14,6 +14,8 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "gotest.tools/assert" + abciclient "github.com/tendermint/tendermint/abci/client" abcimocks "github.com/tendermint/tendermint/abci/client/mocks" "github.com/tendermint/tendermint/abci/example/kvstore" @@ -21,7 +23,6 @@ import ( "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" tmrand "github.com/tendermint/tendermint/libs/rand" - "gotest.tools/assert" ) //---------------------------------------- @@ -29,7 +30,7 @@ import ( type appConnTestI interface { Echo(context.Context, string) (*types.ResponseEcho, error) Flush(context.Context) error - Info(context.Context, types.RequestInfo) (*types.ResponseInfo, error) + Info(context.Context, *types.RequestInfo) (*types.ResponseInfo, error) } type appConnTest struct { @@ -48,7 +49,7 @@ func (app *appConnTest) Flush(ctx context.Context) error { return app.appConn.Flush(ctx) } -func (app *appConnTest) Info(ctx context.Context, req types.RequestInfo) (*types.ResponseInfo, error) { +func (app *appConnTest) Info(ctx context.Context, req *types.RequestInfo) (*types.ResponseInfo, error) { return app.appConn.Info(ctx, req) } @@ -58,7 +59,7 @@ var SOCKET = "socket" func TestEcho(t *testing.T) { sockPath := fmt.Sprintf("unix:///tmp/echo_%v.sock", tmrand.Str(6)) - logger := log.TestingLogger() + logger := log.NewNopLogger() client, err := abciclient.NewClient(logger, sockPath, SOCKET, true) if err != nil { t.Fatal(err) @@ -98,7 +99,7 @@ func TestEcho(t *testing.T) { func BenchmarkEcho(b *testing.B) { b.StopTimer() // Initialize sockPath := fmt.Sprintf("unix:///tmp/echo_%v.sock", tmrand.Str(6)) - logger := log.TestingLogger() + logger := log.NewNopLogger() client, err := abciclient.NewClient(logger, sockPath, SOCKET, true) if err != nil { b.Fatal(err) @@ -146,7 +147,7 @@ func TestInfo(t *testing.T) { defer cancel() sockPath := fmt.Sprintf("unix:///tmp/echo_%v.sock", tmrand.Str(6)) - logger := log.TestingLogger() + logger := log.NewNopLogger() client, err := abciclient.NewClient(logger, sockPath, SOCKET, true) if err != nil { t.Fatal(err) @@ -163,7 +164,7 @@ func TestInfo(t *testing.T) { proxy := newAppConnTest(client) t.Log("Connected") - resInfo, err := proxy.Info(ctx, RequestInfo) + resInfo, err := proxy.Info(ctx, &RequestInfo) require.NoError(t, err) if resInfo.Data != "{\"size\":0}" { @@ -189,7 +190,7 @@ func TestAppConns_Start_Stop(t *testing.T) { clientMock.On("Wait").Return(nil).Times(1) cl := &noopStoppableClientImpl{Client: clientMock} - appConns := New(cl, log.TestingLogger(), NopMetrics()) + appConns := New(cl, log.NewNopLogger(), NopMetrics()) err := appConns.Start(ctx) require.NoError(t, err) @@ -219,7 +220,7 @@ func TestAppConns_Failure(t *testing.T) { clientMock.On("Error").Return(errors.New("EOF")) cl := &noopStoppableClientImpl{Client: clientMock} - appConns := New(cl, log.TestingLogger(), NopMetrics()) + appConns := New(cl, log.NewNopLogger(), NopMetrics()) err := appConns.Start(ctx) require.NoError(t, err) diff --git a/internal/pubsub/example_test.go b/internal/pubsub/example_test.go index 5eea61eb8..c4b5dc5c9 100644 --- a/internal/pubsub/example_test.go +++ b/internal/pubsub/example_test.go @@ -16,7 +16,7 @@ func TestExample(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - s := newTestServer(ctx, t, log.TestingLogger()) + s := newTestServer(ctx, t, log.NewNopLogger()) sub := newTestSub(t).must(s.SubscribeWithArgs(ctx, pubsub.SubscribeArgs{ ClientID: "example-client", @@ -29,6 +29,6 @@ func TestExample(t *testing.T) { Attributes: []abci.EventAttribute{{Key: "name", Value: "John"}}, }, } - require.NoError(t, s.PublishWithEvents(ctx, pubstring("Tombstone"), events)) + require.NoError(t, s.PublishWithEvents(pubstring("Tombstone"), events)) sub.mustReceive(ctx, pubstring("Tombstone")) } diff --git a/internal/pubsub/pubsub.go b/internal/pubsub/pubsub.go index df2dd90e3..c0ad4ae3c 100644 --- a/internal/pubsub/pubsub.go +++ b/internal/pubsub/pubsub.go @@ -287,16 +287,16 @@ func (s *Server) NumClientSubscriptions(clientID string) int { } // Publish publishes the given message. An error will be returned to the caller -// if the context is canceled. -func (s *Server) Publish(ctx context.Context, msg types.EventData) error { - return s.publish(ctx, msg, []abci.Event{}) +// if the pubsub server has shut down. +func (s *Server) Publish(msg types.EventData) error { + return s.publish(msg, []abci.Event{}) } // PublishWithEvents publishes the given message with the set of events. The set // is matched with clients queries. If there is a match, the message is sent to // the client. -func (s *Server) PublishWithEvents(ctx context.Context, msg types.EventData, events []abci.Event) error { - return s.publish(ctx, msg, events) +func (s *Server) PublishWithEvents(msg types.EventData, events []abci.Event) error { + return s.publish(msg, events) } // OnStop implements part of the Service interface. It is a no-op. @@ -309,18 +309,13 @@ func (s *Server) Wait() { <-s.exited; s.BaseService.Wait() } // OnStart implements Service.OnStart by starting the server. func (s *Server) OnStart(ctx context.Context) error { s.run(ctx); return nil } -// OnReset implements Service.OnReset. It has no effect for this service. -func (s *Server) OnReset() error { return nil } - -func (s *Server) publish(ctx context.Context, data types.EventData, events []abci.Event) error { +func (s *Server) publish(data types.EventData, events []abci.Event) error { s.pubs.RLock() defer s.pubs.RUnlock() select { case <-s.done: return ErrServerStopped - case <-ctx.Done(): - return ctx.Err() case s.queue <- item{ Data: data, Events: events, diff --git a/internal/pubsub/pubsub_test.go b/internal/pubsub/pubsub_test.go index eee065fbf..e366977b5 100644 --- a/internal/pubsub/pubsub_test.go +++ b/internal/pubsub/pubsub_test.go @@ -30,7 +30,7 @@ func TestSubscribeWithArgs(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.TestingLogger() + logger := log.NewNopLogger() s := newTestServer(ctx, t, logger) t.Run("DefaultLimit", func(t *testing.T) { @@ -42,7 +42,7 @@ func TestSubscribeWithArgs(t *testing.T) { require.Equal(t, 1, s.NumClients()) require.Equal(t, 1, s.NumClientSubscriptions(clientID)) - require.NoError(t, s.Publish(ctx, pubstring("Ka-Zar"))) + require.NoError(t, s.Publish(pubstring("Ka-Zar"))) sub.mustReceive(ctx, pubstring("Ka-Zar")) }) t.Run("PositiveLimit", func(t *testing.T) { @@ -51,7 +51,7 @@ func TestSubscribeWithArgs(t *testing.T) { Query: query.All, Limit: 10, })) - require.NoError(t, s.Publish(ctx, pubstring("Aggamon"))) + require.NoError(t, s.Publish(pubstring("Aggamon"))) sub.mustReceive(ctx, pubstring("Aggamon")) }) } @@ -59,7 +59,7 @@ func TestSubscribeWithArgs(t *testing.T) { func TestObserver(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.TestingLogger() + logger := log.NewNopLogger() s := newTestServer(ctx, t, logger) @@ -72,7 +72,7 @@ func TestObserver(t *testing.T) { })) const input = pubstring("Lions and tigers and bears, oh my!") - require.NoError(t, s.Publish(ctx, input)) + require.NoError(t, s.Publish(input)) <-done require.Equal(t, got, input) } @@ -81,7 +81,7 @@ func TestObserverErrors(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.TestingLogger() + logger := log.NewNopLogger() s := newTestServer(ctx, t, logger) @@ -94,7 +94,7 @@ func TestPublishDoesNotBlock(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.TestingLogger() + logger := log.NewNopLogger() s := newTestServer(ctx, t, logger) @@ -106,9 +106,9 @@ func TestPublishDoesNotBlock(t *testing.T) { go func() { defer close(published) - require.NoError(t, s.Publish(ctx, pubstring("Quicksilver"))) - require.NoError(t, s.Publish(ctx, pubstring("Asylum"))) - require.NoError(t, s.Publish(ctx, pubstring("Ivan"))) + require.NoError(t, s.Publish(pubstring("Quicksilver"))) + require.NoError(t, s.Publish(pubstring("Asylum"))) + require.NoError(t, s.Publish(pubstring("Ivan"))) }() select { @@ -124,7 +124,7 @@ func TestSubscribeErrors(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.TestingLogger() + logger := log.NewNopLogger() s := newTestServer(ctx, t, logger) t.Run("NegativeLimitErr", func(t *testing.T) { @@ -141,7 +141,7 @@ func TestSlowSubscriber(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.TestingLogger() + logger := log.NewNopLogger() s := newTestServer(ctx, t, logger) sub := newTestSub(t).must(s.SubscribeWithArgs(ctx, pubsub.SubscribeArgs{ @@ -149,9 +149,9 @@ func TestSlowSubscriber(t *testing.T) { Query: query.All, })) - require.NoError(t, s.Publish(ctx, pubstring("Fat Cobra"))) - require.NoError(t, s.Publish(ctx, pubstring("Viper"))) - require.NoError(t, s.Publish(ctx, pubstring("Black Panther"))) + require.NoError(t, s.Publish(pubstring("Fat Cobra"))) + require.NoError(t, s.Publish(pubstring("Viper"))) + require.NoError(t, s.Publish(pubstring("Black Panther"))) // We had capacity for one item, so we should get that item, but after that // the subscription should have been terminated by the publisher. @@ -163,7 +163,7 @@ func TestDifferentClients(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.TestingLogger() + logger := log.NewNopLogger() s := newTestServer(ctx, t, logger) sub1 := newTestSub(t).must(s.SubscribeWithArgs(ctx, pubsub.SubscribeArgs{ @@ -176,7 +176,7 @@ func TestDifferentClients(t *testing.T) { Attributes: []abci.EventAttribute{{Key: "type", Value: "NewBlock"}}, }} - require.NoError(t, s.PublishWithEvents(ctx, pubstring("Iceman"), events)) + require.NoError(t, s.PublishWithEvents(pubstring("Iceman"), events)) sub1.mustReceive(ctx, pubstring("Iceman")) sub2 := newTestSub(t).must(s.SubscribeWithArgs(ctx, pubsub.SubscribeArgs{ @@ -195,7 +195,7 @@ func TestDifferentClients(t *testing.T) { }, } - require.NoError(t, s.PublishWithEvents(ctx, pubstring("Ultimo"), events)) + require.NoError(t, s.PublishWithEvents(pubstring("Ultimo"), events)) sub1.mustReceive(ctx, pubstring("Ultimo")) sub2.mustReceive(ctx, pubstring("Ultimo")) @@ -210,7 +210,7 @@ func TestDifferentClients(t *testing.T) { Attributes: []abci.EventAttribute{{Key: "type", Value: "NewRoundStep"}}, }} - require.NoError(t, s.PublishWithEvents(ctx, pubstring("Valeria Richards"), events)) + require.NoError(t, s.PublishWithEvents(pubstring("Valeria Richards"), events)) sub3.mustTimeOut(ctx, 100*time.Millisecond) } @@ -218,7 +218,7 @@ func TestSubscribeDuplicateKeys(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.TestingLogger() + logger := log.NewNopLogger() s := newTestServer(ctx, t, logger) testCases := []struct { @@ -259,7 +259,7 @@ func TestSubscribeDuplicateKeys(t *testing.T) { }, } - require.NoError(t, s.PublishWithEvents(ctx, pubstring("Iceman"), events)) + require.NoError(t, s.PublishWithEvents(pubstring("Iceman"), events)) if tc.expected != nil { sub.mustReceive(ctx, tc.expected) @@ -274,7 +274,7 @@ func TestClientSubscribesTwice(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.TestingLogger() + logger := log.NewNopLogger() s := newTestServer(ctx, t, logger) q := query.MustCompile(`tm.events.type='NewBlock'`) @@ -288,7 +288,7 @@ func TestClientSubscribesTwice(t *testing.T) { Query: q, })) - require.NoError(t, s.PublishWithEvents(ctx, pubstring("Goblin Queen"), events)) + require.NoError(t, s.PublishWithEvents(pubstring("Goblin Queen"), events)) sub1.mustReceive(ctx, pubstring("Goblin Queen")) // Subscribing a second time with the same client ID and query fails. @@ -302,7 +302,7 @@ func TestClientSubscribesTwice(t *testing.T) { } // The attempt to re-subscribe does not disrupt the existing sub. - require.NoError(t, s.PublishWithEvents(ctx, pubstring("Spider-Man"), events)) + require.NoError(t, s.PublishWithEvents(pubstring("Spider-Man"), events)) sub1.mustReceive(ctx, pubstring("Spider-Man")) } @@ -310,7 +310,7 @@ func TestUnsubscribe(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.TestingLogger() + logger := log.NewNopLogger() s := newTestServer(ctx, t, logger) sub := newTestSub(t).must(s.SubscribeWithArgs(ctx, pubsub.SubscribeArgs{ @@ -325,7 +325,7 @@ func TestUnsubscribe(t *testing.T) { })) // Publishing should still work. - require.NoError(t, s.Publish(ctx, pubstring("Nick Fury"))) + require.NoError(t, s.Publish(pubstring("Nick Fury"))) // The unsubscribed subscriber should report as such. sub.mustFail(ctx, pubsub.ErrUnsubscribed) @@ -335,7 +335,7 @@ func TestClientUnsubscribesTwice(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.TestingLogger() + logger := log.NewNopLogger() s := newTestServer(ctx, t, logger) newTestSub(t).must(s.SubscribeWithArgs(ctx, pubsub.SubscribeArgs{ @@ -357,7 +357,7 @@ func TestResubscribe(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.TestingLogger() + logger := log.NewNopLogger() s := newTestServer(ctx, t, logger) args := pubsub.SubscribeArgs{ @@ -373,7 +373,7 @@ func TestResubscribe(t *testing.T) { sub := newTestSub(t).must(s.SubscribeWithArgs(ctx, args)) - require.NoError(t, s.Publish(ctx, pubstring("Cable"))) + require.NoError(t, s.Publish(pubstring("Cable"))) sub.mustReceive(ctx, pubstring("Cable")) } @@ -381,7 +381,7 @@ func TestUnsubscribeAll(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.TestingLogger() + logger := log.NewNopLogger() s := newTestServer(ctx, t, logger) sub1 := newTestSub(t).must(s.SubscribeWithArgs(ctx, pubsub.SubscribeArgs{ @@ -394,7 +394,7 @@ func TestUnsubscribeAll(t *testing.T) { })) require.NoError(t, s.UnsubscribeAll(ctx, clientID)) - require.NoError(t, s.Publish(ctx, pubstring("Nick Fury"))) + require.NoError(t, s.Publish(pubstring("Nick Fury"))) sub1.mustFail(ctx, pubsub.ErrUnsubscribed) sub2.mustFail(ctx, pubsub.ErrUnsubscribed) @@ -402,7 +402,7 @@ func TestUnsubscribeAll(t *testing.T) { } func TestBufferCapacity(t *testing.T) { - logger := log.TestingLogger() + logger := log.NewNopLogger() s := pubsub.NewServer(logger, pubsub.BufferCapacity(2)) require.Equal(t, 2, s.BufferCapacity()) @@ -410,13 +410,24 @@ func TestBufferCapacity(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - require.NoError(t, s.Publish(ctx, pubstring("Nighthawk"))) - require.NoError(t, s.Publish(ctx, pubstring("Sage"))) + require.NoError(t, s.Publish(pubstring("Nighthawk"))) + require.NoError(t, s.Publish(pubstring("Sage"))) ctx, cancel = context.WithTimeout(ctx, 100*time.Millisecond) defer cancel() - require.ErrorIs(t, s.Publish(ctx, pubstring("Ironclad")), context.DeadlineExceeded) + sig := make(chan struct{}) + + go func() { defer close(sig); _ = s.Publish(pubstring("Ironclad")) }() + + select { + case <-sig: + t.Fatal("should not fire") + + case <-ctx.Done(): + return + } + } func newTestServer(ctx context.Context, t testing.TB, logger log.Logger) *pubsub.Server { diff --git a/internal/pubsub/query/syntax/syntax_test.go b/internal/pubsub/query/syntax/syntax_test.go index 9ce5fa735..ac0473beb 100644 --- a/internal/pubsub/query/syntax/syntax_test.go +++ b/internal/pubsub/query/syntax/syntax_test.go @@ -55,7 +55,7 @@ func TestScanner(t *testing.T) { got = append(got, s.Token()) } if err := s.Err(); err != io.EOF { - t.Errorf("Next: unexpected error: %w", err) + t.Errorf("Next: unexpected error: %v", err) } if !reflect.DeepEqual(got, test.want) { diff --git a/internal/rpc/core/abci.go b/internal/rpc/core/abci.go index 8f5e61d55..fa45c6b45 100644 --- a/internal/rpc/core/abci.go +++ b/internal/rpc/core/abci.go @@ -5,24 +5,17 @@ import ( abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/internal/proxy" - "github.com/tendermint/tendermint/libs/bytes" "github.com/tendermint/tendermint/rpc/coretypes" ) // ABCIQuery queries the application for some information. // More: https://docs.tendermint.com/master/rpc/#/ABCI/abci_query -func (env *Environment) ABCIQuery( - ctx context.Context, - path string, - data bytes.HexBytes, - height int64, - prove bool, -) (*coretypes.ResultABCIQuery, error) { - resQuery, err := env.ProxyApp.Query(ctx, abci.RequestQuery{ - Path: path, - Data: data, - Height: height, - Prove: prove, +func (env *Environment) ABCIQuery(ctx context.Context, req *coretypes.RequestABCIQuery) (*coretypes.ResultABCIQuery, error) { + resQuery, err := env.ProxyApp.Query(ctx, &abci.RequestQuery{ + Path: req.Path, + Data: req.Data, + Height: int64(req.Height), + Prove: req.Prove, }) if err != nil { return nil, err @@ -34,7 +27,7 @@ func (env *Environment) ABCIQuery( // ABCIInfo gets some info about the application. // More: https://docs.tendermint.com/master/rpc/#/ABCI/abci_info func (env *Environment) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, error) { - resInfo, err := env.ProxyApp.Info(ctx, proxy.RequestInfo) + resInfo, err := env.ProxyApp.Info(ctx, &proxy.RequestInfo) if err != nil { return nil, err } diff --git a/internal/rpc/core/blocks.go b/internal/rpc/core/blocks.go index 427e691da..239344002 100644 --- a/internal/rpc/core/blocks.go +++ b/internal/rpc/core/blocks.go @@ -7,7 +7,6 @@ import ( tmquery "github.com/tendermint/tendermint/internal/pubsub/query" "github.com/tendermint/tendermint/internal/state/indexer" - "github.com/tendermint/tendermint/libs/bytes" tmmath "github.com/tendermint/tendermint/libs/math" "github.com/tendermint/tendermint/rpc/coretypes" "github.com/tendermint/tendermint/types" @@ -23,19 +22,15 @@ import ( // order (highest first). // // More: https://docs.tendermint.com/master/rpc/#/Info/blockchain -func (env *Environment) BlockchainInfo( - ctx context.Context, - minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) { - - const limit int64 = 20 - - var err error - minHeight, maxHeight, err = filterMinMax( +func (env *Environment) BlockchainInfo(ctx context.Context, req *coretypes.RequestBlockchainInfo) (*coretypes.ResultBlockchainInfo, error) { + const limit = 20 + minHeight, maxHeight, err := filterMinMax( env.BlockStore.Base(), env.BlockStore.Height(), - minHeight, - maxHeight, - limit) + int64(req.MinHeight), + int64(req.MaxHeight), + limit, + ) if err != nil { return nil, err } @@ -92,8 +87,8 @@ func filterMinMax(base, height, min, max, limit int64) (int64, int64, error) { // Block gets block at a given height. // If no height is provided, it will fetch the latest block. // More: https://docs.tendermint.com/master/rpc/#/Info/block -func (env *Environment) Block(ctx context.Context, heightPtr *int64) (*coretypes.ResultBlock, error) { - height, err := env.getHeight(env.BlockStore.Height(), heightPtr) +func (env *Environment) Block(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlock, error) { + height, err := env.getHeight(env.BlockStore.Height(), (*int64)(req.Height)) if err != nil { return nil, err } @@ -109,12 +104,8 @@ func (env *Environment) Block(ctx context.Context, heightPtr *int64) (*coretypes // BlockByHash gets block by hash. // More: https://docs.tendermint.com/master/rpc/#/Info/block_by_hash -func (env *Environment) BlockByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultBlock, error) { - // N.B. The hash parameter is HexBytes so that the reflective parameter - // decoding logic in the HTTP service will correctly translate from JSON. - // See https://github.com/tendermint/tendermint/issues/6802 for context. - - block := env.BlockStore.LoadBlockByHash(hash) +func (env *Environment) BlockByHash(ctx context.Context, req *coretypes.RequestBlockByHash) (*coretypes.ResultBlock, error) { + block := env.BlockStore.LoadBlockByHash(req.Hash) if block == nil { return &coretypes.ResultBlock{BlockID: types.BlockID{}, Block: nil}, nil } @@ -126,8 +117,8 @@ func (env *Environment) BlockByHash(ctx context.Context, hash bytes.HexBytes) (* // Header gets block header at a given height. // If no height is provided, it will fetch the latest header. // More: https://docs.tendermint.com/master/rpc/#/Info/header -func (env *Environment) Header(ctx context.Context, heightPtr *int64) (*coretypes.ResultHeader, error) { - height, err := env.getHeight(env.BlockStore.Height(), heightPtr) +func (env *Environment) Header(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultHeader, error) { + height, err := env.getHeight(env.BlockStore.Height(), (*int64)(req.Height)) if err != nil { return nil, err } @@ -142,12 +133,8 @@ func (env *Environment) Header(ctx context.Context, heightPtr *int64) (*coretype // HeaderByHash gets header by hash. // More: https://docs.tendermint.com/master/rpc/#/Info/header_by_hash -func (env *Environment) HeaderByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultHeader, error) { - // N.B. The hash parameter is HexBytes so that the reflective parameter - // decoding logic in the HTTP service will correctly translate from JSON. - // See https://github.com/tendermint/tendermint/issues/6802 for context. - - blockMeta := env.BlockStore.LoadBlockMetaByHash(hash) +func (env *Environment) HeaderByHash(ctx context.Context, req *coretypes.RequestBlockByHash) (*coretypes.ResultHeader, error) { + blockMeta := env.BlockStore.LoadBlockMetaByHash(req.Hash) if blockMeta == nil { return &coretypes.ResultHeader{}, nil } @@ -158,8 +145,8 @@ func (env *Environment) HeaderByHash(ctx context.Context, hash bytes.HexBytes) ( // Commit gets block commit at a given height. // If no height is provided, it will fetch the commit for the latest block. // More: https://docs.tendermint.com/master/rpc/#/Info/commit -func (env *Environment) Commit(ctx context.Context, heightPtr *int64) (*coretypes.ResultCommit, error) { - height, err := env.getHeight(env.BlockStore.Height(), heightPtr) +func (env *Environment) Commit(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultCommit, error) { + height, err := env.getHeight(env.BlockStore.Height(), (*int64)(req.Height)) if err != nil { return nil, err } @@ -194,8 +181,8 @@ func (env *Environment) Commit(ctx context.Context, heightPtr *int64) (*coretype // // Results are for the height of the block containing the txs. // More: https://docs.tendermint.com/master/rpc/#/Info/block_results -func (env *Environment) BlockResults(ctx context.Context, heightPtr *int64) (*coretypes.ResultBlockResults, error) { - height, err := env.getHeight(env.BlockStore.Height(), heightPtr) +func (env *Environment) BlockResults(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlockResults, error) { + height, err := env.getHeight(env.BlockStore.Height(), (*int64)(req.Height)) if err != nil { return nil, err } @@ -220,20 +207,13 @@ func (env *Environment) BlockResults(ctx context.Context, heightPtr *int64) (*co }, nil } -// BlockSearch searches for a paginated set of blocks matching the provided -// query. -func (env *Environment) BlockSearch( - ctx context.Context, - query string, - pagePtr, perPagePtr *int, - orderBy string, -) (*coretypes.ResultBlockSearch, error) { - +// BlockSearch searches for a paginated set of blocks matching the provided query. +func (env *Environment) BlockSearch(ctx context.Context, req *coretypes.RequestBlockSearch) (*coretypes.ResultBlockSearch, error) { if !indexer.KVSinkEnabled(env.EventSinks) { return nil, fmt.Errorf("block searching is disabled due to no kvEventSink") } - q, err := tmquery.New(query) + q, err := tmquery.New(req.Query) if err != nil { return nil, err } @@ -251,7 +231,7 @@ func (env *Environment) BlockSearch( } // sort results (must be done before pagination) - switch orderBy { + switch req.OrderBy { case "desc", "": sort.Slice(results, func(i, j int) bool { return results[i] > results[j] }) @@ -264,9 +244,9 @@ func (env *Environment) BlockSearch( // paginate results totalCount := len(results) - perPage := env.validatePerPage(perPagePtr) + perPage := env.validatePerPage(req.PerPage.IntPtr()) - page, err := validatePage(pagePtr, perPage, totalCount) + page, err := validatePage(req.Page.IntPtr(), perPage, totalCount) if err != nil { return nil, err } diff --git a/internal/rpc/core/blocks_test.go b/internal/rpc/core/blocks_test.go index c48ac4c48..d95338332 100644 --- a/internal/rpc/core/blocks_test.go +++ b/internal/rpc/core/blocks_test.go @@ -109,7 +109,9 @@ func TestBlockResults(t *testing.T) { ctx := context.Background() for _, tc := range testCases { - res, err := env.BlockResults(ctx, &tc.height) + res, err := env.BlockResults(ctx, &coretypes.RequestBlockInfo{ + Height: (*coretypes.Int64)(&tc.height), + }) if tc.wantErr { assert.Error(t, err) } else { diff --git a/internal/rpc/core/consensus.go b/internal/rpc/core/consensus.go index 6acdcc333..46e220f05 100644 --- a/internal/rpc/core/consensus.go +++ b/internal/rpc/core/consensus.go @@ -14,13 +14,9 @@ import ( // for the validators in the set as used in computing their Merkle root. // // More: https://docs.tendermint.com/master/rpc/#/Info/validators -func (env *Environment) Validators( - ctx context.Context, - heightPtr *int64, - pagePtr, perPagePtr *int) (*coretypes.ResultValidators, error) { - +func (env *Environment) Validators(ctx context.Context, req *coretypes.RequestValidators) (*coretypes.ResultValidators, error) { // The latest validator that we know is the NextValidator of the last block. - height, err := env.getHeight(env.latestUncommittedHeight(), heightPtr) + height, err := env.getHeight(env.latestUncommittedHeight(), (*int64)(req.Height)) if err != nil { return nil, err } @@ -31,8 +27,8 @@ func (env *Environment) Validators( } totalCount := len(validators.Validators) - perPage := env.validatePerPage(perPagePtr) - page, err := validatePage(pagePtr, perPage, totalCount) + perPage := env.validatePerPage(req.PerPage.IntPtr()) + page, err := validatePage(req.Page.IntPtr(), perPage, totalCount) if err != nil { return nil, err } @@ -45,7 +41,8 @@ func (env *Environment) Validators( BlockHeight: height, Validators: v, Count: len(v), - Total: totalCount}, nil + Total: totalCount, + }, nil } // DumpConsensusState dumps consensus state. @@ -86,7 +83,8 @@ func (env *Environment) DumpConsensusState(ctx context.Context) (*coretypes.Resu } return &coretypes.ResultDumpConsensusState{ RoundState: roundState, - Peers: peerStates}, nil + Peers: peerStates, + }, nil } // ConsensusState returns a concise summary of the consensus state. @@ -101,11 +99,10 @@ func (env *Environment) GetConsensusState(ctx context.Context) (*coretypes.Resul // ConsensusParams gets the consensus parameters at the given block height. // If no height is provided, it will fetch the latest consensus params. // More: https://docs.tendermint.com/master/rpc/#/Info/consensus_params -func (env *Environment) ConsensusParams(ctx context.Context, heightPtr *int64) (*coretypes.ResultConsensusParams, error) { - - // The latest consensus params that we know is the consensus params after the - // last block. - height, err := env.getHeight(env.latestUncommittedHeight(), heightPtr) +func (env *Environment) ConsensusParams(ctx context.Context, req *coretypes.RequestConsensusParams) (*coretypes.ResultConsensusParams, error) { + // The latest consensus params that we know is the consensus params after + // the last block. + height, err := env.getHeight(env.latestUncommittedHeight(), (*int64)(req.Height)) if err != nil { return nil, err } @@ -115,6 +112,9 @@ func (env *Environment) ConsensusParams(ctx context.Context, heightPtr *int64) ( return nil, err } + consensusParams.Synchrony = consensusParams.Synchrony.SynchronyParamsOrDefaults() + consensusParams.Timeout = consensusParams.Timeout.TimeoutParamsOrDefaults() + return &coretypes.ResultConsensusParams{ BlockHeight: height, ConsensusParams: consensusParams}, nil diff --git a/internal/rpc/core/events.go b/internal/rpc/core/events.go index 4e0d2ac8a..3f289bfa7 100644 --- a/internal/rpc/core/events.go +++ b/internal/rpc/core/events.go @@ -26,7 +26,7 @@ const ( // Subscribe for events via WebSocket. // More: https://docs.tendermint.com/master/rpc/#/Websocket/subscribe -func (env *Environment) Subscribe(ctx context.Context, query string) (*coretypes.ResultSubscribe, error) { +func (env *Environment) Subscribe(ctx context.Context, req *coretypes.RequestSubscribe) (*coretypes.ResultSubscribe, error) { callInfo := rpctypes.GetCallInfo(ctx) addr := callInfo.RemoteAddr() @@ -34,15 +34,15 @@ func (env *Environment) Subscribe(ctx context.Context, query string) (*coretypes return nil, fmt.Errorf("max_subscription_clients %d reached", env.Config.MaxSubscriptionClients) } else if env.EventBus.NumClientSubscriptions(addr) >= env.Config.MaxSubscriptionsPerClient { return nil, fmt.Errorf("max_subscriptions_per_client %d reached", env.Config.MaxSubscriptionsPerClient) - } else if len(query) > maxQueryLength { + } else if len(req.Query) > maxQueryLength { return nil, errors.New("maximum query length exceeded") } env.Logger.Info("WARNING: Websocket subscriptions are deprecated and will be removed " + "in Tendermint v0.37. See https://tinyurl.com/adr075 for more information.") - env.Logger.Info("Subscribe to query", "remote", addr, "query", query) + env.Logger.Info("Subscribe to query", "remote", addr, "query", req.Query) - q, err := tmquery.New(query) + q, err := tmquery.New(req.Query) if err != nil { return nil, fmt.Errorf("failed to parse query: %w", err) } @@ -83,7 +83,7 @@ func (env *Environment) Subscribe(ctx context.Context, query string) (*coretypes // We have a message to deliver to the client. resp := callInfo.RPCRequest.MakeResponse(&coretypes.ResultEvent{ - Query: query, + Query: req.Query, Data: msg.Data(), Events: msg.Events(), }) @@ -102,15 +102,15 @@ func (env *Environment) Subscribe(ctx context.Context, query string) (*coretypes // Unsubscribe from events via WebSocket. // More: https://docs.tendermint.com/master/rpc/#/Websocket/unsubscribe -func (env *Environment) Unsubscribe(ctx context.Context, query string) (*coretypes.ResultUnsubscribe, error) { +func (env *Environment) Unsubscribe(ctx context.Context, req *coretypes.RequestUnsubscribe) (*coretypes.ResultUnsubscribe, error) { args := tmpubsub.UnsubscribeArgs{Subscriber: rpctypes.GetCallInfo(ctx).RemoteAddr()} - env.Logger.Info("Unsubscribe from query", "remote", args.Subscriber, "subscription", query) + env.Logger.Info("Unsubscribe from query", "remote", args.Subscriber, "subscription", req.Query) var err error - args.Query, err = tmquery.New(query) + args.Query, err = tmquery.New(req.Query) if err != nil { - args.ID = query + args.ID = req.Query } err = env.EventBus.Unsubscribe(ctx, args) @@ -148,17 +148,13 @@ func (env *Environment) UnsubscribeAll(ctx context.Context) (*coretypes.ResultUn // If maxItems ≤ 0, a default positive number of events is chosen. The values // of maxItems and waitTime may be capped to sensible internal maxima without // reporting an error to the caller. -func (env *Environment) Events(ctx context.Context, - filter *coretypes.EventFilter, - maxItems int, - before, after cursor.Cursor, - waitTime time.Duration, -) (*coretypes.ResultEvents, error) { +func (env *Environment) Events(ctx context.Context, req *coretypes.RequestEvents) (*coretypes.ResultEvents, error) { if env.EventLog == nil { return nil, errors.New("the event log is not enabled") } // Parse and validate parameters. + maxItems := req.MaxItems if maxItems <= 0 { maxItems = 10 } else if maxItems > 100 { @@ -167,6 +163,8 @@ func (env *Environment) Events(ctx context.Context, const minWaitTime = 1 * time.Second const maxWaitTime = 30 * time.Second + + waitTime := req.WaitTime if waitTime < minWaitTime { waitTime = minWaitTime } else if waitTime > maxWaitTime { @@ -174,14 +172,22 @@ func (env *Environment) Events(ctx context.Context, } query := tmquery.All - if filter != nil && filter.Query != "" { - q, err := tmquery.New(filter.Query) + if req.Filter != nil && req.Filter.Query != "" { + q, err := tmquery.New(req.Filter.Query) if err != nil { return nil, fmt.Errorf("invalid filter query: %w", err) } query = q } + var before, after cursor.Cursor + if err := before.UnmarshalText([]byte(req.Before)); err != nil { + return nil, fmt.Errorf("invalid cursor %q: %w", req.Before, err) + } + if err := after.UnmarshalText([]byte(req.After)); err != nil { + return nil, fmt.Errorf("invalid cursor %q: %w", req.After, err) + } + var info eventlog.Info var items []*eventlog.Item var err error diff --git a/internal/rpc/core/evidence.go b/internal/rpc/core/evidence.go index c7e2bea8a..5de93d2c2 100644 --- a/internal/rpc/core/evidence.go +++ b/internal/rpc/core/evidence.go @@ -9,18 +9,15 @@ import ( // BroadcastEvidence broadcasts evidence of the misbehavior. // More: https://docs.tendermint.com/master/rpc/#/Evidence/broadcast_evidence -func (env *Environment) BroadcastEvidence( - ctx context.Context, - ev coretypes.Evidence, -) (*coretypes.ResultBroadcastEvidence, error) { - if ev.Value == nil { +func (env *Environment) BroadcastEvidence(ctx context.Context, req *coretypes.RequestBroadcastEvidence) (*coretypes.ResultBroadcastEvidence, error) { + if req.Evidence == nil { return nil, fmt.Errorf("%w: no evidence was provided", coretypes.ErrInvalidRequest) } - if err := ev.Value.ValidateBasic(); err != nil { + if err := req.Evidence.ValidateBasic(); err != nil { return nil, fmt.Errorf("evidence.ValidateBasic failed: %w", err) } - if err := env.EvidencePool.AddEvidence(ctx, ev.Value); err != nil { + if err := env.EvidencePool.AddEvidence(ctx, req.Evidence); err != nil { return nil, fmt.Errorf("failed to add evidence: %w", err) } - return &coretypes.ResultBroadcastEvidence{Hash: ev.Value.Hash()}, nil + return &coretypes.ResultBroadcastEvidence{Hash: req.Evidence.Hash()}, nil } diff --git a/internal/rpc/core/mempool.go b/internal/rpc/core/mempool.go index c2a9084db..5fc2b9fcf 100644 --- a/internal/rpc/core/mempool.go +++ b/internal/rpc/core/mempool.go @@ -12,7 +12,6 @@ import ( "github.com/tendermint/tendermint/internal/state/indexer" tmmath "github.com/tendermint/tendermint/libs/math" "github.com/tendermint/tendermint/rpc/coretypes" - "github.com/tendermint/tendermint/types" ) //----------------------------------------------------------------------------- @@ -21,114 +20,134 @@ import ( // BroadcastTxAsync returns right away, with no response. Does not wait for // CheckTx nor DeliverTx results. // More: https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_async -func (env *Environment) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { - err := env.Mempool.CheckTx(ctx, tx, nil, mempool.TxInfo{}) +func (env *Environment) BroadcastTxAsync(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error) { + err := env.Mempool.CheckTx(ctx, req.Tx, nil, mempool.TxInfo{}) if err != nil { return nil, err } - return &coretypes.ResultBroadcastTx{Hash: tx.Hash()}, nil + return &coretypes.ResultBroadcastTx{Hash: req.Tx.Hash()}, nil } // BroadcastTxSync returns with the response from CheckTx. Does not wait for // DeliverTx result. // More: https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_sync -func (env *Environment) BroadcastTxSync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { +func (env *Environment) BroadcastTxSync(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error) { resCh := make(chan *abci.ResponseCheckTx, 1) err := env.Mempool.CheckTx( ctx, - tx, - func(res *abci.ResponseCheckTx) { resCh <- res }, + req.Tx, + func(res *abci.ResponseCheckTx) { + select { + case <-ctx.Done(): + case resCh <- res: + } + }, mempool.TxInfo{}, ) if err != nil { return nil, err } - r := <-resCh - - return &coretypes.ResultBroadcastTx{ - Code: r.Code, - Data: r.Data, - Log: r.Log, - Codespace: r.Codespace, - MempoolError: r.MempoolError, - Hash: tx.Hash(), - }, nil + select { + case <-ctx.Done(): + return nil, fmt.Errorf("broadcast confirmation not received: %w", ctx.Err()) + case r := <-resCh: + return &coretypes.ResultBroadcastTx{ + Code: r.Code, + Data: r.Data, + Log: r.Log, + Codespace: r.Codespace, + MempoolError: r.MempoolError, + Hash: req.Tx.Hash(), + }, nil + } } // BroadcastTxCommit returns with the responses from CheckTx and DeliverTx. // More: https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_commit -func (env *Environment) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) { +func (env *Environment) BroadcastTxCommit(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTxCommit, error) { resCh := make(chan *abci.ResponseCheckTx, 1) err := env.Mempool.CheckTx( ctx, - tx, - func(res *abci.ResponseCheckTx) { resCh <- res }, + req.Tx, + func(res *abci.ResponseCheckTx) { + select { + case <-ctx.Done(): + case resCh <- res: + } + }, mempool.TxInfo{}, ) if err != nil { return nil, err } - r := <-resCh - if r.Code != abci.CodeTypeOK { - return &coretypes.ResultBroadcastTxCommit{ - CheckTx: *r, - Hash: tx.Hash(), - }, fmt.Errorf("transaction encountered error (%s)", r.MempoolError) - } - - if !indexer.KVSinkEnabled(env.EventSinks) { - return &coretypes.ResultBroadcastTxCommit{ + select { + case <-ctx.Done(): + return nil, fmt.Errorf("broadcast confirmation not received: %w", ctx.Err()) + case r := <-resCh: + if r.Code != abci.CodeTypeOK { + return &coretypes.ResultBroadcastTxCommit{ CheckTx: *r, - Hash: tx.Hash(), - }, - errors.New("cannot confirm transaction because kvEventSink is not enabled") - } + Hash: req.Tx.Hash(), + }, fmt.Errorf("transaction encountered error (%s)", r.MempoolError) + } - startAt := time.Now() - timer := time.NewTimer(0) - defer timer.Stop() - - count := 0 - for { - count++ - select { - case <-ctx.Done(): - env.Logger.Error("error on broadcastTxCommit", - "duration", time.Since(startAt), - "err", err) + if !indexer.KVSinkEnabled(env.EventSinks) { return &coretypes.ResultBroadcastTxCommit{ CheckTx: *r, - Hash: tx.Hash(), - }, fmt.Errorf("timeout waiting for commit of tx %s (%s)", - tx.Hash(), time.Since(startAt)) - case <-timer.C: - txres, err := env.Tx(ctx, tx.Hash(), false) - if err != nil { - jitter := 100*time.Millisecond + time.Duration(rand.Int63n(int64(time.Second))) // nolint: gosec - backoff := 100 * time.Duration(count) * time.Millisecond - timer.Reset(jitter + backoff) - continue - } + Hash: req.Tx.Hash(), + }, + errors.New("cannot confirm transaction because kvEventSink is not enabled") + } - return &coretypes.ResultBroadcastTxCommit{ - CheckTx: *r, - TxResult: txres.TxResult, - Hash: tx.Hash(), - Height: txres.Height, - }, nil + startAt := time.Now() + timer := time.NewTimer(0) + defer timer.Stop() + + count := 0 + for { + count++ + select { + case <-ctx.Done(): + env.Logger.Error("error on broadcastTxCommit", + "duration", time.Since(startAt), + "err", err) + return &coretypes.ResultBroadcastTxCommit{ + CheckTx: *r, + Hash: req.Tx.Hash(), + }, fmt.Errorf("timeout waiting for commit of tx %s (%s)", + req.Tx.Hash(), time.Since(startAt)) + case <-timer.C: + txres, err := env.Tx(ctx, &coretypes.RequestTx{ + Hash: req.Tx.Hash(), + Prove: false, + }) + if err != nil { + jitter := 100*time.Millisecond + time.Duration(rand.Int63n(int64(time.Second))) // nolint: gosec + backoff := 100 * time.Duration(count) * time.Millisecond + timer.Reset(jitter + backoff) + continue + } + + return &coretypes.ResultBroadcastTxCommit{ + CheckTx: *r, + TxResult: txres.TxResult, + Hash: req.Tx.Hash(), + Height: txres.Height, + }, nil + } } } } // UnconfirmedTxs gets unconfirmed transactions from the mempool in order of priority // More: https://docs.tendermint.com/master/rpc/#/Info/unconfirmed_txs -func (env *Environment) UnconfirmedTxs(ctx context.Context, pagePtr, perPagePtr *int) (*coretypes.ResultUnconfirmedTxs, error) { +func (env *Environment) UnconfirmedTxs(ctx context.Context, req *coretypes.RequestUnconfirmedTxs) (*coretypes.ResultUnconfirmedTxs, error) { totalCount := env.Mempool.Size() - perPage := env.validatePerPage(perPagePtr) - page, err := validatePage(pagePtr, perPage, totalCount) + perPage := env.validatePerPage(req.PerPage.IntPtr()) + page, err := validatePage(req.Page.IntPtr(), perPage, totalCount) if err != nil { return nil, err } @@ -142,7 +161,8 @@ func (env *Environment) UnconfirmedTxs(ctx context.Context, pagePtr, perPagePtr Count: len(result), Total: totalCount, TotalBytes: env.Mempool.SizeBytes(), - Txs: result}, nil + Txs: result, + }, nil } // NumUnconfirmedTxs gets number of unconfirmed transactions. @@ -157,14 +177,14 @@ func (env *Environment) NumUnconfirmedTxs(ctx context.Context) (*coretypes.Resul // CheckTx checks the transaction without executing it. The transaction won't // be added to the mempool either. // More: https://docs.tendermint.com/master/rpc/#/Tx/check_tx -func (env *Environment) CheckTx(ctx context.Context, tx types.Tx) (*coretypes.ResultCheckTx, error) { - res, err := env.ProxyApp.CheckTx(ctx, abci.RequestCheckTx{Tx: tx}) +func (env *Environment) CheckTx(ctx context.Context, req *coretypes.RequestCheckTx) (*coretypes.ResultCheckTx, error) { + res, err := env.ProxyApp.CheckTx(ctx, &abci.RequestCheckTx{Tx: req.Tx}) if err != nil { return nil, err } return &coretypes.ResultCheckTx{ResponseCheckTx: *res}, nil } -func (env *Environment) RemoveTx(ctx context.Context, txkey types.TxKey) error { - return env.Mempool.RemoveTxByKey(txkey) +func (env *Environment) RemoveTx(ctx context.Context, req *coretypes.RequestRemoveTx) error { + return env.Mempool.RemoveTxByKey(req.TxKey) } diff --git a/internal/rpc/core/net.go b/internal/rpc/core/net.go index 5444b77b7..b18f1e2fc 100644 --- a/internal/rpc/core/net.go +++ b/internal/rpc/core/net.go @@ -44,7 +44,7 @@ func (env *Environment) Genesis(ctx context.Context) (*coretypes.ResultGenesis, return &coretypes.ResultGenesis{Genesis: env.GenDoc}, nil } -func (env *Environment) GenesisChunked(ctx context.Context, chunk uint) (*coretypes.ResultGenesisChunk, error) { +func (env *Environment) GenesisChunked(ctx context.Context, req *coretypes.RequestGenesisChunked) (*coretypes.ResultGenesisChunk, error) { if env.genChunks == nil { return nil, fmt.Errorf("service configuration error, genesis chunks are not initialized") } @@ -53,7 +53,7 @@ func (env *Environment) GenesisChunked(ctx context.Context, chunk uint) (*corety return nil, fmt.Errorf("service configuration error, there are no chunks") } - id := int(chunk) + id := int(req.Chunk) if id > len(env.genChunks)-1 { return nil, fmt.Errorf("there are %d chunks, %d is invalid", len(env.genChunks)-1, id) diff --git a/internal/rpc/core/routes.go b/internal/rpc/core/routes.go index 945798ed7..4bc1ca414 100644 --- a/internal/rpc/core/routes.go +++ b/internal/rpc/core/routes.go @@ -2,13 +2,9 @@ package core import ( "context" - "time" - "github.com/tendermint/tendermint/internal/eventlog/cursor" - "github.com/tendermint/tendermint/libs/bytes" "github.com/tendermint/tendermint/rpc/coretypes" rpc "github.com/tendermint/tendermint/rpc/jsonrpc/server" - "github.com/tendermint/tendermint/types" ) // TODO: better system than "unsafe" prefix @@ -32,47 +28,47 @@ func NewRoutesMap(svc RPCService, opts *RouteOptions) RoutesMap { out := RoutesMap{ // Event subscription. Note that subscribe, unsubscribe, and // unsubscribe_all are only available via the websocket endpoint. - "events": rpc.NewRPCFunc(svc.Events, "filter", "maxItems", "before", "after", "waitTime"), - "subscribe": rpc.NewWSRPCFunc(svc.Subscribe, "query"), - "unsubscribe": rpc.NewWSRPCFunc(svc.Unsubscribe, "query"), + "events": rpc.NewRPCFunc(svc.Events), + "subscribe": rpc.NewWSRPCFunc(svc.Subscribe), + "unsubscribe": rpc.NewWSRPCFunc(svc.Unsubscribe), "unsubscribe_all": rpc.NewWSRPCFunc(svc.UnsubscribeAll), // info API "health": rpc.NewRPCFunc(svc.Health), "status": rpc.NewRPCFunc(svc.Status), "net_info": rpc.NewRPCFunc(svc.NetInfo), - "blockchain": rpc.NewRPCFunc(svc.BlockchainInfo, "minHeight", "maxHeight"), + "blockchain": rpc.NewRPCFunc(svc.BlockchainInfo), "genesis": rpc.NewRPCFunc(svc.Genesis), - "genesis_chunked": rpc.NewRPCFunc(svc.GenesisChunked, "chunk"), - "header": rpc.NewRPCFunc(svc.Header, "height"), - "header_by_hash": rpc.NewRPCFunc(svc.HeaderByHash, "hash"), - "block": rpc.NewRPCFunc(svc.Block, "height"), - "block_by_hash": rpc.NewRPCFunc(svc.BlockByHash, "hash"), - "block_results": rpc.NewRPCFunc(svc.BlockResults, "height"), - "commit": rpc.NewRPCFunc(svc.Commit, "height"), - "check_tx": rpc.NewRPCFunc(svc.CheckTx, "tx"), - "remove_tx": rpc.NewRPCFunc(svc.RemoveTx, "txkey"), - "tx": rpc.NewRPCFunc(svc.Tx, "hash", "prove"), - "tx_search": rpc.NewRPCFunc(svc.TxSearch, "query", "prove", "page", "per_page", "order_by"), - "block_search": rpc.NewRPCFunc(svc.BlockSearch, "query", "page", "per_page", "order_by"), - "validators": rpc.NewRPCFunc(svc.Validators, "height", "page", "per_page"), + "genesis_chunked": rpc.NewRPCFunc(svc.GenesisChunked), + "header": rpc.NewRPCFunc(svc.Header), + "header_by_hash": rpc.NewRPCFunc(svc.HeaderByHash), + "block": rpc.NewRPCFunc(svc.Block), + "block_by_hash": rpc.NewRPCFunc(svc.BlockByHash), + "block_results": rpc.NewRPCFunc(svc.BlockResults), + "commit": rpc.NewRPCFunc(svc.Commit), + "check_tx": rpc.NewRPCFunc(svc.CheckTx), + "remove_tx": rpc.NewRPCFunc(svc.RemoveTx), + "tx": rpc.NewRPCFunc(svc.Tx), + "tx_search": rpc.NewRPCFunc(svc.TxSearch), + "block_search": rpc.NewRPCFunc(svc.BlockSearch), + "validators": rpc.NewRPCFunc(svc.Validators), "dump_consensus_state": rpc.NewRPCFunc(svc.DumpConsensusState), "consensus_state": rpc.NewRPCFunc(svc.GetConsensusState), - "consensus_params": rpc.NewRPCFunc(svc.ConsensusParams, "height"), - "unconfirmed_txs": rpc.NewRPCFunc(svc.UnconfirmedTxs, "page", "per_page"), + "consensus_params": rpc.NewRPCFunc(svc.ConsensusParams), + "unconfirmed_txs": rpc.NewRPCFunc(svc.UnconfirmedTxs), "num_unconfirmed_txs": rpc.NewRPCFunc(svc.NumUnconfirmedTxs), // tx broadcast API - "broadcast_tx_commit": rpc.NewRPCFunc(svc.BroadcastTxCommit, "tx"), - "broadcast_tx_sync": rpc.NewRPCFunc(svc.BroadcastTxSync, "tx"), - "broadcast_tx_async": rpc.NewRPCFunc(svc.BroadcastTxAsync, "tx"), + "broadcast_tx_commit": rpc.NewRPCFunc(svc.BroadcastTxCommit), + "broadcast_tx_sync": rpc.NewRPCFunc(svc.BroadcastTxSync), + "broadcast_tx_async": rpc.NewRPCFunc(svc.BroadcastTxAsync), // abci API - "abci_query": rpc.NewRPCFunc(svc.ABCIQuery, "path", "data", "height", "prove"), + "abci_query": rpc.NewRPCFunc(svc.ABCIQuery), "abci_info": rpc.NewRPCFunc(svc.ABCIInfo), // evidence API - "broadcast_evidence": rpc.NewRPCFunc(svc.BroadcastEvidence, "evidence"), + "broadcast_evidence": rpc.NewRPCFunc(svc.BroadcastEvidence), } if u, ok := svc.(RPCUnsafe); ok && opts.Unsafe { out["unsafe_flush_mempool"] = rpc.NewRPCFunc(u.UnsafeFlushMempool) @@ -84,38 +80,38 @@ func NewRoutesMap(svc RPCService, opts *RouteOptions) RoutesMap { // implementation, for use in constructing a routing table. type RPCService interface { ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, error) - ABCIQuery(ctx context.Context, path string, data bytes.HexBytes, height int64, prove bool) (*coretypes.ResultABCIQuery, error) - Block(ctx context.Context, heightPtr *int64) (*coretypes.ResultBlock, error) - BlockByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultBlock, error) - BlockResults(ctx context.Context, heightPtr *int64) (*coretypes.ResultBlockResults, error) - BlockSearch(ctx context.Context, query string, pagePtr, perPagePtr *int, orderBy string) (*coretypes.ResultBlockSearch, error) - BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) - BroadcastEvidence(ctx context.Context, ev coretypes.Evidence) (*coretypes.ResultBroadcastEvidence, error) - BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) - BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) - BroadcastTxSync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) - CheckTx(ctx context.Context, tx types.Tx) (*coretypes.ResultCheckTx, error) - Commit(ctx context.Context, heightPtr *int64) (*coretypes.ResultCommit, error) - ConsensusParams(ctx context.Context, heightPtr *int64) (*coretypes.ResultConsensusParams, error) + ABCIQuery(ctx context.Context, req *coretypes.RequestABCIQuery) (*coretypes.ResultABCIQuery, error) + Block(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlock, error) + BlockByHash(ctx context.Context, req *coretypes.RequestBlockByHash) (*coretypes.ResultBlock, error) + BlockResults(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlockResults, error) + BlockSearch(ctx context.Context, req *coretypes.RequestBlockSearch) (*coretypes.ResultBlockSearch, error) + BlockchainInfo(ctx context.Context, req *coretypes.RequestBlockchainInfo) (*coretypes.ResultBlockchainInfo, error) + BroadcastEvidence(ctx context.Context, req *coretypes.RequestBroadcastEvidence) (*coretypes.ResultBroadcastEvidence, error) + BroadcastTxAsync(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error) + BroadcastTxCommit(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTxCommit, error) + BroadcastTxSync(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error) + CheckTx(ctx context.Context, req *coretypes.RequestCheckTx) (*coretypes.ResultCheckTx, error) + Commit(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultCommit, error) + ConsensusParams(ctx context.Context, req *coretypes.RequestConsensusParams) (*coretypes.ResultConsensusParams, error) DumpConsensusState(ctx context.Context) (*coretypes.ResultDumpConsensusState, error) - Events(ctx context.Context, filter *coretypes.EventFilter, maxItems int, before, after cursor.Cursor, waitTime time.Duration) (*coretypes.ResultEvents, error) + Events(ctx context.Context, req *coretypes.RequestEvents) (*coretypes.ResultEvents, error) Genesis(ctx context.Context) (*coretypes.ResultGenesis, error) - GenesisChunked(ctx context.Context, chunk uint) (*coretypes.ResultGenesisChunk, error) + GenesisChunked(ctx context.Context, req *coretypes.RequestGenesisChunked) (*coretypes.ResultGenesisChunk, error) GetConsensusState(ctx context.Context) (*coretypes.ResultConsensusState, error) - Header(ctx context.Context, heightPtr *int64) (*coretypes.ResultHeader, error) - HeaderByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultHeader, error) + Header(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultHeader, error) + HeaderByHash(ctx context.Context, req *coretypes.RequestBlockByHash) (*coretypes.ResultHeader, error) Health(ctx context.Context) (*coretypes.ResultHealth, error) NetInfo(ctx context.Context) (*coretypes.ResultNetInfo, error) NumUnconfirmedTxs(ctx context.Context) (*coretypes.ResultUnconfirmedTxs, error) - RemoveTx(ctx context.Context, txkey types.TxKey) error + RemoveTx(ctx context.Context, req *coretypes.RequestRemoveTx) error Status(ctx context.Context) (*coretypes.ResultStatus, error) - Subscribe(ctx context.Context, query string) (*coretypes.ResultSubscribe, error) - Tx(ctx context.Context, hash bytes.HexBytes, prove bool) (*coretypes.ResultTx, error) - TxSearch(ctx context.Context, query string, prove bool, pagePtr, perPagePtr *int, orderBy string) (*coretypes.ResultTxSearch, error) - UnconfirmedTxs(ctx context.Context, page, perPage *int) (*coretypes.ResultUnconfirmedTxs, error) - Unsubscribe(ctx context.Context, query string) (*coretypes.ResultUnsubscribe, error) + Subscribe(ctx context.Context, req *coretypes.RequestSubscribe) (*coretypes.ResultSubscribe, error) + Tx(ctx context.Context, req *coretypes.RequestTx) (*coretypes.ResultTx, error) + TxSearch(ctx context.Context, req *coretypes.RequestTxSearch) (*coretypes.ResultTxSearch, error) + UnconfirmedTxs(ctx context.Context, req *coretypes.RequestUnconfirmedTxs) (*coretypes.ResultUnconfirmedTxs, error) + Unsubscribe(ctx context.Context, req *coretypes.RequestUnsubscribe) (*coretypes.ResultUnsubscribe, error) UnsubscribeAll(ctx context.Context) (*coretypes.ResultUnsubscribe, error) - Validators(ctx context.Context, heightPtr *int64, pagePtr, perPagePtr *int) (*coretypes.ResultValidators, error) + Validators(ctx context.Context, req *coretypes.RequestValidators) (*coretypes.ResultValidators, error) } // RPCUnsafe defines the set of "unsafe" methods that may optionally be diff --git a/internal/rpc/core/status.go b/internal/rpc/core/status.go index 46b8a6fcd..870c13424 100644 --- a/internal/rpc/core/status.go +++ b/internal/rpc/core/status.go @@ -77,14 +77,25 @@ func (env *Environment) Status(ctx context.Context) (*coretypes.ResultStatus, er EarliestAppHash: earliestAppHash, EarliestBlockHeight: earliestBlockHeight, EarliestBlockTime: time.Unix(0, earliestBlockTimeNano), - MaxPeerBlockHeight: env.BlockSyncReactor.GetMaxPeerBlockHeight(), - CatchingUp: env.ConsensusReactor.WaitSync(), - TotalSyncedTime: env.BlockSyncReactor.GetTotalSyncedTime(), - RemainingTime: env.BlockSyncReactor.GetRemainingSyncTime(), + // this should start as true, if consensus + // hasn't started yet, and then flip to false + // (or true,) depending on what's actually + // happening. + CatchingUp: true, }, ValidatorInfo: validatorInfo, } + if env.ConsensusReactor != nil { + result.SyncInfo.CatchingUp = env.ConsensusReactor.WaitSync() + } + + if env.BlockSyncReactor != nil { + result.SyncInfo.MaxPeerBlockHeight = env.BlockSyncReactor.GetMaxPeerBlockHeight() + result.SyncInfo.TotalSyncedTime = env.BlockSyncReactor.GetTotalSyncedTime() + result.SyncInfo.RemainingTime = env.BlockSyncReactor.GetRemainingSyncTime() + } + if env.StateSyncMetricer != nil { result.SyncInfo.TotalSnapshots = env.StateSyncMetricer.TotalSnapshots() result.SyncInfo.ChunkProcessAvgTime = env.StateSyncMetricer.ChunkProcessAvgTime() @@ -103,6 +114,9 @@ func (env *Environment) validatorAtHeight(h int64) *types.Validator { if err != nil { return nil } + if env.ConsensusState == nil { + return nil + } if env.PubKey == nil { return nil } diff --git a/internal/rpc/core/tx.go b/internal/rpc/core/tx.go index 126875d0d..cd643b844 100644 --- a/internal/rpc/core/tx.go +++ b/internal/rpc/core/tx.go @@ -8,7 +8,6 @@ import ( tmquery "github.com/tendermint/tendermint/internal/pubsub/query" "github.com/tendermint/tendermint/internal/state/indexer" - "github.com/tendermint/tendermint/libs/bytes" tmmath "github.com/tendermint/tendermint/libs/math" "github.com/tendermint/tendermint/rpc/coretypes" "github.com/tendermint/tendermint/types" @@ -18,37 +17,29 @@ import ( // transaction is in the mempool, invalidated, or was not sent in the first // place. // More: https://docs.tendermint.com/master/rpc/#/Info/tx -func (env *Environment) Tx(ctx context.Context, hash bytes.HexBytes, prove bool) (*coretypes.ResultTx, error) { +func (env *Environment) Tx(ctx context.Context, req *coretypes.RequestTx) (*coretypes.ResultTx, error) { // if index is disabled, return error - - // N.B. The hash parameter is HexBytes so that the reflective parameter - // decoding logic in the HTTP service will correctly translate from JSON. - // See https://github.com/tendermint/tendermint/issues/6802 for context. - if !indexer.KVSinkEnabled(env.EventSinks) { return nil, errors.New("transaction querying is disabled due to no kvEventSink") } for _, sink := range env.EventSinks { if sink.Type() == indexer.KV { - r, err := sink.GetTxByHash(hash) + r, err := sink.GetTxByHash(req.Hash) if r == nil { - return nil, fmt.Errorf("tx (%X) not found, err: %w", hash, err) + return nil, fmt.Errorf("tx (%X) not found, err: %w", req.Hash, err) } - height := r.Height - index := r.Index - var proof types.TxProof - if prove { - block := env.BlockStore.LoadBlock(height) - proof = block.Data.Txs.Proof(int(index)) // XXX: overflow on 32-bit machines + if req.Prove { + block := env.BlockStore.LoadBlock(r.Height) + proof = block.Data.Txs.Proof(int(r.Index)) } return &coretypes.ResultTx{ - Hash: hash, - Height: height, - Index: index, + Hash: req.Hash, + Height: r.Height, + Index: r.Index, TxResult: r.Result, Tx: r.Tx, Proof: proof, @@ -62,21 +53,14 @@ func (env *Environment) Tx(ctx context.Context, hash bytes.HexBytes, prove bool) // TxSearch allows you to query for multiple transactions results. It returns a // list of transactions (maximum ?per_page entries) and the total count. // More: https://docs.tendermint.com/master/rpc/#/Info/tx_search -func (env *Environment) TxSearch( - ctx context.Context, - query string, - prove bool, - pagePtr, perPagePtr *int, - orderBy string, -) (*coretypes.ResultTxSearch, error) { - +func (env *Environment) TxSearch(ctx context.Context, req *coretypes.RequestTxSearch) (*coretypes.ResultTxSearch, error) { if !indexer.KVSinkEnabled(env.EventSinks) { return nil, fmt.Errorf("transaction searching is disabled due to no kvEventSink") - } else if len(query) > maxQueryLength { + } else if len(req.Query) > maxQueryLength { return nil, errors.New("maximum query length exceeded") } - q, err := tmquery.New(query) + q, err := tmquery.New(req.Query) if err != nil { return nil, err } @@ -89,7 +73,7 @@ func (env *Environment) TxSearch( } // sort results (must be done before pagination) - switch orderBy { + switch req.OrderBy { case "desc", "": sort.Slice(results, func(i, j int) bool { if results[i].Height == results[j].Height { @@ -110,9 +94,9 @@ func (env *Environment) TxSearch( // paginate results totalCount := len(results) - perPage := env.validatePerPage(perPagePtr) + perPage := env.validatePerPage(req.PerPage.IntPtr()) - page, err := validatePage(pagePtr, perPage, totalCount) + page, err := validatePage(req.Page.IntPtr(), perPage, totalCount) if err != nil { return nil, err } @@ -125,9 +109,9 @@ func (env *Environment) TxSearch( r := results[i] var proof types.TxProof - if prove { + if req.Prove { block := env.BlockStore.LoadBlock(r.Height) - proof = block.Data.Txs.Proof(int(r.Index)) // XXX: overflow on 32-bit machines + proof = block.Data.Txs.Proof(int(r.Index)) } apiResults = append(apiResults, &coretypes.ResultTx{ diff --git a/internal/state/errors.go b/internal/state/errors.go index 6e0cdfa47..e8ad776f4 100644 --- a/internal/state/errors.go +++ b/internal/state/errors.go @@ -39,6 +39,7 @@ type ( ErrNoValSetForHeight struct { Height int64 + Err error } ErrNoConsensusParamsForHeight struct { @@ -89,9 +90,14 @@ func (e ErrStateMismatch) Error() string { } func (e ErrNoValSetForHeight) Error() string { - return fmt.Sprintf("could not find validator set for height #%d", e.Height) + if e.Err == nil { + return fmt.Sprintf("could not find validator set for height #%d", e.Height) + } + return fmt.Sprintf("could not find validator set for height #%d: %s", e.Height, e.Err.Error()) } +func (e ErrNoValSetForHeight) Unwrap() error { return e.Err } + func (e ErrNoConsensusParamsForHeight) Error() string { return fmt.Sprintf("could not find consensus params for height #%d", e.Height) } diff --git a/internal/state/execution.go b/internal/state/execution.go index f7c9e02d1..06dfc0b5c 100644 --- a/internal/state/execution.go +++ b/internal/state/execution.go @@ -7,7 +7,9 @@ import ( abciclient "github.com/tendermint/tendermint/abci/client" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/encoding" + "github.com/tendermint/tendermint/crypto/merkle" "github.com/tendermint/tendermint/internal/eventbus" "github.com/tendermint/tendermint/internal/mempool" "github.com/tendermint/tendermint/libs/log" @@ -47,16 +49,7 @@ type BlockExecutor struct { cache map[string]struct{} } -type BlockExecutorOption func(executor *BlockExecutor) - -func BlockExecutorWithMetrics(metrics *Metrics) BlockExecutorOption { - return func(blockExec *BlockExecutor) { - blockExec.metrics = metrics - } -} - -// NewBlockExecutor returns a new BlockExecutor with a NopEventBus. -// Call SetEventBus to provide one. +// NewBlockExecutor returns a new BlockExecutor with the passed-in EventBus. func NewBlockExecutor( stateStore Store, logger log.Logger, @@ -64,37 +57,26 @@ func NewBlockExecutor( pool mempool.Mempool, evpool EvidencePool, blockStore BlockStore, - options ...BlockExecutorOption, + eventBus *eventbus.EventBus, + metrics *Metrics, ) *BlockExecutor { - res := &BlockExecutor{ + return &BlockExecutor{ + eventBus: eventBus, store: stateStore, appClient: appClient, - eventBus: eventbus.NopEventBus{}, mempool: pool, evpool: evpool, logger: logger, - metrics: NopMetrics(), + metrics: metrics, cache: make(map[string]struct{}), blockStore: blockStore, } - - for _, option := range options { - option(res) - } - - return res } func (blockExec *BlockExecutor) Store() Store { return blockExec.store } -// SetEventBus - sets the event bus for publishing block related events. -// If not called, it defaults to types.NopEventBus. -func (blockExec *BlockExecutor) SetEventBus(eventBus types.BlockEventPublisher) { - blockExec.eventBus = eventBus -} - // CreateProposalBlock calls state.MakeBlock with evidence from the evpool // and txs from the mempool. The max bytes must be big enough to fit the commit. // Up to 1/10th of the block space is allcoated for maximum sized evidence. @@ -104,10 +86,11 @@ func (blockExec *BlockExecutor) SetEventBus(eventBus types.BlockEventPublisher) func (blockExec *BlockExecutor) CreateProposalBlock( ctx context.Context, height int64, - state State, commit *types.Commit, + state State, + commit *types.Commit, proposerAddr []byte, votes []*types.Vote, -) (*types.Block, *types.PartSet, error) { +) (*types.Block, error) { maxBytes := state.ConsensusParams.Block.MaxBytes maxGas := state.ConsensusParams.Block.MaxGas @@ -118,13 +101,20 @@ func (blockExec *BlockExecutor) CreateProposalBlock( maxDataBytes := types.MaxDataBytes(maxBytes, evSize, state.Validators.Size()) txs := blockExec.mempool.ReapMaxBytesMaxGas(maxDataBytes, maxGas) + block := state.MakeBlock(height, txs, commit, evidence, proposerAddr) - preparedProposal, err := blockExec.appClient.PrepareProposal( + localLastCommit := buildLastCommitInfo(block, blockExec.store, state.InitialHeight) + rpp, err := blockExec.appClient.PrepareProposal( ctx, - abci.RequestPrepareProposal{ - BlockData: txs.ToSliceOfBytes(), - BlockDataSize: maxDataBytes, - Votes: types.VotesToProto(votes), + &abci.RequestPrepareProposal{ + MaxTxBytes: maxDataBytes, + Txs: block.Txs.ToSliceOfBytes(), + LocalLastCommit: extendedCommitInfo(localLastCommit, votes), + ByzantineValidators: block.Evidence.ToABCI(), + Height: block.Height, + Time: block.Time, + NextValidatorsHash: block.NextValidatorsHash, + ProposerAddress: block.ProposerAddress, }, ) if err != nil { @@ -133,24 +123,24 @@ func (blockExec *BlockExecutor) CreateProposalBlock( // transaction causing an error. // // Also, the App can simply skip any transaction that could cause any kind of trouble. - // Either way, we can not recover in a meaningful way, unless we skip proposing - // this block, repair what caused the error and try again. Hence, we panic on - // purpose for now. - panic(err) + // Either way, we cannot recover in a meaningful way, unless we skip proposing + // this block, repair what caused the error and try again. Hence, we return an + // error for now (the production code calling this function is expected to panic). + return nil, err } - newTxs := preparedProposal.GetBlockData() - var txSize int - for _, tx := range newTxs { - txSize += len(tx) + txrSet := types.NewTxRecordSet(rpp.TxRecords) - if maxDataBytes < int64(txSize) { - panic("block data exceeds max amount of allowed bytes") + if err := txrSet.Validate(maxDataBytes, block.Txs); err != nil { + return nil, err + } + + for _, rtx := range txrSet.RemovedTxs() { + if err := blockExec.mempool.RemoveTxByKey(rtx.Key()); err != nil { + blockExec.logger.Debug("error removing transaction from the mempool", "error", err, "tx hash", rtx.Hash()) } } - - modifiedTxs := types.ToTxs(preparedProposal.GetBlockData()) - - return state.MakeBlock(height, modifiedTxs, commit, evidence, proposerAddr) + itxs := txrSet.IncludedTxs() + return state.MakeBlock(height, itxs, commit, evidence, proposerAddr), nil } func (blockExec *BlockExecutor) ProcessProposal( @@ -158,20 +148,24 @@ func (blockExec *BlockExecutor) ProcessProposal( block *types.Block, state State, ) (bool, error) { - req := abci.RequestProcessProposal{ + resp, err := blockExec.appClient.ProcessProposal(ctx, &abci.RequestProcessProposal{ Hash: block.Header.Hash(), - Header: *block.Header.ToProto(), + Height: block.Header.Height, + Time: block.Header.Time, Txs: block.Data.Txs.ToSliceOfBytes(), ProposedLastCommit: buildLastCommitInfo(block, blockExec.store, state.InitialHeight), ByzantineValidators: block.Evidence.ToABCI(), - } - - resp, err := blockExec.appClient.ProcessProposal(ctx, req) + ProposerAddress: block.ProposerAddress, + NextValidatorsHash: block.NextValidatorsHash, + }) if err != nil { return false, ErrInvalidBlock(err) } + if resp.IsStatusUnknown() { + panic(fmt.Sprintf("ProcessProposal responded with status %s", resp.Status.String())) + } - return resp.Accept, nil + return resp.IsAccepted(), nil } // ValidateBlock validates the given block against the given state. @@ -213,15 +207,17 @@ func (blockExec *BlockExecutor) ApplyBlock( return state, ErrInvalidBlock(err) } startTime := time.Now().UnixNano() - pbh := block.Header.ToProto() finalizeBlockResponse, err := blockExec.appClient.FinalizeBlock( ctx, - abci.RequestFinalizeBlock{ + &abci.RequestFinalizeBlock{ Hash: block.Hash(), - Header: *pbh, + Height: block.Header.Height, + Time: block.Header.Time, Txs: block.Txs.ToSliceOfBytes(), DecidedLastCommit: buildLastCommitInfo(block, blockExec.store, state.InitialHeight), ByzantineValidators: block.Evidence.ToABCI(), + ProposerAddress: block.ProposerAddress, + NextValidatorsHash: block.NextValidatorsHash, }, ) endTime := time.Now().UnixNano() @@ -254,7 +250,12 @@ func (blockExec *BlockExecutor) ApplyBlock( } // Update the state with the block and responses. - state, err = state.Update(blockID, &block.Header, ABCIResponsesResultsHash(abciResponses), finalizeBlockResponse.ConsensusParamUpdates, validatorUpdates) + rs, err := abci.MarshalTxResults(finalizeBlockResponse.TxResults) + if err != nil { + return state, fmt.Errorf("marshaling TxResults: %w", err) + } + h := merkle.HashFromByteSlices(rs) + state, err = state.Update(blockID, &block.Header, h, finalizeBlockResponse.ConsensusParamUpdates, validatorUpdates) if err != nil { return state, fmt.Errorf("commit failed for application: %w", err) } @@ -289,34 +290,34 @@ func (blockExec *BlockExecutor) ApplyBlock( // Events are fired after everything else. // NOTE: if we crash between Commit and Save, events wont be fired during replay - fireEvents(ctx, blockExec.logger, blockExec.eventBus, block, blockID, finalizeBlockResponse, validatorUpdates) + fireEvents(blockExec.logger, blockExec.eventBus, block, blockID, finalizeBlockResponse, validatorUpdates) return state, nil } -func (blockExec *BlockExecutor) ExtendVote(ctx context.Context, vote *types.Vote) (types.VoteExtension, error) { - req := abci.RequestExtendVote{ - Vote: vote.ToProto(), - } - - resp, err := blockExec.appClient.ExtendVote(ctx, req) +func (blockExec *BlockExecutor) ExtendVote(ctx context.Context, vote *types.Vote) ([]byte, error) { + resp, err := blockExec.appClient.ExtendVote(ctx, &abci.RequestExtendVote{ + Hash: vote.BlockID.Hash, + Height: vote.Height, + }) if err != nil { - return types.VoteExtension{}, err + panic(fmt.Errorf("ExtendVote call failed: %w", err)) } - return types.VoteExtensionFromProto(resp.VoteExtension), nil + return resp.VoteExtension, nil } func (blockExec *BlockExecutor) VerifyVoteExtension(ctx context.Context, vote *types.Vote) error { - req := abci.RequestVerifyVoteExtension{ - Vote: vote.ToProto(), - } - - resp, err := blockExec.appClient.VerifyVoteExtension(ctx, req) + resp, err := blockExec.appClient.VerifyVoteExtension(ctx, &abci.RequestVerifyVoteExtension{ + Hash: vote.BlockID.Hash, + ValidatorAddress: vote.ValidatorAddress, + Height: vote.Height, + VoteExtension: vote.Extension, + }) if err != nil { - return err + panic(fmt.Errorf("VerifyVoteExtension call failed: %w", err)) } - if resp.IsErr() { + if !resp.IsOK() { return types.ErrVoteInvalidExtension } @@ -415,6 +416,47 @@ func buildLastCommitInfo(block *types.Block, store Store, initialHeight int64) a } } +// extendedCommitInfo expects a CommitInfo struct along with all of the +// original votes relating to that commit, including their vote extensions. The +// order of votes does not matter. +func extendedCommitInfo(c abci.CommitInfo, votes []*types.Vote) abci.ExtendedCommitInfo { + if len(c.Votes) != len(votes) { + panic(fmt.Sprintf("extendedCommitInfo: number of votes from commit differ from the number of votes supplied (%d != %d)", len(c.Votes), len(votes))) + } + votesByVal := make(map[string]*types.Vote) + for _, vote := range votes { + if vote != nil { + valAddr := vote.ValidatorAddress.String() + if _, ok := votesByVal[valAddr]; ok { + panic(fmt.Sprintf("extendedCommitInfo: found duplicate vote for validator with address %s", valAddr)) + } + votesByVal[valAddr] = vote + } + } + vs := make([]abci.ExtendedVoteInfo, len(c.Votes)) + for i := range vs { + var ext []byte + // votes[i] will be nil if c.Votes[i].SignedLastBlock is false + if c.Votes[i].SignedLastBlock { + valAddr := crypto.Address(c.Votes[i].Validator.Address).String() + vote, ok := votesByVal[valAddr] + if !ok || vote == nil { + panic(fmt.Sprintf("extendedCommitInfo: validator with address %s signed last block, but could not find vote for it", valAddr)) + } + ext = vote.Extension + } + vs[i] = abci.ExtendedVoteInfo{ + Validator: c.Votes[i].Validator, + SignedLastBlock: c.Votes[i].SignedLastBlock, + VoteExtension: ext, + } + } + return abci.ExtendedCommitInfo{ + Round: c.Round, + Votes: vs, + } +} + func validateValidatorUpdates(abciUpdates []abci.ValidatorUpdate, params types.ValidatorParams) error { for _, valUpdate := range abciUpdates { @@ -510,7 +552,6 @@ func (state State) Update( // Fire TxEvent for every tx. // NOTE: if Tendermint crashes before commit, some or all of these events may be published again. func fireEvents( - ctx context.Context, logger log.Logger, eventBus types.BlockEventPublisher, block *types.Block, @@ -518,7 +559,7 @@ func fireEvents( finalizeBlockResponse *abci.ResponseFinalizeBlock, validatorUpdates []*types.Validator, ) { - if err := eventBus.PublishEventNewBlock(ctx, types.EventDataNewBlock{ + if err := eventBus.PublishEventNewBlock(types.EventDataNewBlock{ Block: block, BlockID: blockID, ResultFinalizeBlock: *finalizeBlockResponse, @@ -526,7 +567,7 @@ func fireEvents( logger.Error("failed publishing new block", "err", err) } - if err := eventBus.PublishEventNewBlockHeader(ctx, types.EventDataNewBlockHeader{ + if err := eventBus.PublishEventNewBlockHeader(types.EventDataNewBlockHeader{ Header: block.Header, NumTxs: int64(len(block.Txs)), ResultFinalizeBlock: *finalizeBlockResponse, @@ -536,7 +577,7 @@ func fireEvents( if len(block.Evidence) != 0 { for _, ev := range block.Evidence { - if err := eventBus.PublishEventNewEvidence(ctx, types.EventDataNewEvidence{ + if err := eventBus.PublishEventNewEvidence(types.EventDataNewEvidence{ Evidence: ev, Height: block.Height, }); err != nil { @@ -552,7 +593,7 @@ func fireEvents( } for i, tx := range block.Data.Txs { - if err := eventBus.PublishEventTx(ctx, types.EventDataTx{ + if err := eventBus.PublishEventTx(types.EventDataTx{ TxResult: abci.TxResult{ Height: block.Height, Index: uint32(i), @@ -565,7 +606,7 @@ func fireEvents( } if len(finalizeBlockResponse.ValidatorUpdates) > 0 { - if err := eventBus.PublishEventValidatorSetUpdates(ctx, + if err := eventBus.PublishEventValidatorSetUpdates( types.EventDataValidatorSetUpdates{ValidatorUpdates: validatorUpdates}); err != nil { logger.Error("failed publishing event", "err", err) } @@ -580,19 +621,19 @@ func fireEvents( func ExecCommitBlock( ctx context.Context, be *BlockExecutor, - appConnConsensus abciclient.Client, + appConn abciclient.Client, block *types.Block, logger log.Logger, store Store, initialHeight int64, s State, ) ([]byte, error) { - pbh := block.Header.ToProto() - finalizeBlockResponse, err := appConnConsensus.FinalizeBlock( + finalizeBlockResponse, err := appConn.FinalizeBlock( ctx, - abci.RequestFinalizeBlock{ + &abci.RequestFinalizeBlock{ Hash: block.Hash(), - Header: *pbh, + Height: block.Height, + Time: block.Time, Txs: block.Txs.ToSliceOfBytes(), DecidedLastCommit: buildLastCommitInfo(block, store, initialHeight), ByzantineValidators: block.Evidence.ToABCI(), @@ -624,11 +665,11 @@ func ExecCommitBlock( } blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} - fireEvents(ctx, be.logger, be.eventBus, block, blockID, finalizeBlockResponse, validatorUpdates) + fireEvents(be.logger, be.eventBus, block, blockID, finalizeBlockResponse, validatorUpdates) } // Commit block, get hash back - res, err := appConnConsensus.Commit(ctx) + res, err := appConn.Commit(ctx) if err != nil { logger.Error("client error during proxyAppConn.Commit", "err", res) return nil, err diff --git a/internal/state/execution_test.go b/internal/state/execution_test.go index 8dd384b61..0937b9990 100644 --- a/internal/state/execution_test.go +++ b/internal/state/execution_test.go @@ -2,6 +2,7 @@ package state_test import ( "context" + "errors" "testing" "time" @@ -11,14 +12,14 @@ import ( dbm "github.com/tendermint/tm-db" abciclient "github.com/tendermint/tendermint/abci/client" + abciclientmocks "github.com/tendermint/tendermint/abci/client/mocks" abci "github.com/tendermint/tendermint/abci/types" abcimocks "github.com/tendermint/tendermint/abci/types/mocks" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/encoding" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/eventbus" - mmock "github.com/tendermint/tendermint/internal/mempool/mock" + mpmocks "github.com/tendermint/tendermint/internal/mempool/mocks" "github.com/tendermint/tendermint/internal/proxy" "github.com/tendermint/tendermint/internal/pubsub" sm "github.com/tendermint/tendermint/internal/state" @@ -38,24 +39,35 @@ var ( func TestApplyBlock(t *testing.T) { app := &testApp{} - logger := log.TestingLogger() + logger := log.NewNopLogger() cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - err := proxyApp.Start(ctx) - require.NoError(t, err) + require.NoError(t, proxyApp.Start(ctx)) + + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) state, stateDB, _ := makeState(t, 1, 1) stateStore := sm.NewStore(stateDB) blockStore := store.NewBlockStore(dbm.NewMemDB()) - blockExec := sm.NewBlockExecutor(stateStore, logger, proxyApp, - mmock.Mempool{}, sm.EmptyEvidencePool{}, blockStore) + mp := &mpmocks.Mempool{} + mp.On("Lock").Return() + mp.On("Unlock").Return() + mp.On("FlushAppConn", mock.Anything).Return(nil) + mp.On("Update", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything).Return(nil) + blockExec := sm.NewBlockExecutor(stateStore, logger, proxyApp, mp, sm.EmptyEvidencePool{}, blockStore, eventBus, sm.NopMetrics()) - block, err := sf.MakeBlock(state, 1, new(types.Commit)) - require.NoError(t, err) + block := sf.MakeBlock(state, 1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} @@ -74,7 +86,7 @@ func TestFinalizeBlockDecidedLastCommit(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.TestingLogger() + logger := log.NewNopLogger() app := &testApp{} cc := abciclient.NewLocalClient(logger, app) appClient := proxy.New(cc, logger, proxy.NopMetrics()) @@ -102,8 +114,22 @@ func TestFinalizeBlockDecidedLastCommit(t *testing.T) { evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, 0) evpool.On("Update", ctx, mock.Anything, mock.Anything).Return() evpool.On("CheckEvidence", ctx, mock.Anything).Return(nil) + mp := &mpmocks.Mempool{} + mp.On("Lock").Return() + mp.On("Unlock").Return() + mp.On("FlushAppConn", mock.Anything).Return(nil) + mp.On("Update", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything).Return(nil) - blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), appClient, mmock.Mempool{}, evpool, blockStore) + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) + + blockExec := sm.NewBlockExecutor(stateStore, log.NewNopLogger(), appClient, mp, evpool, blockStore, eventBus, sm.NopMetrics()) state, _, lastCommit := makeAndCommitGoodBlock(ctx, t, state, 1, new(types.Commit), state.NextValidators.Validators[0].Address, blockExec, privVals, nil) for idx, isAbsent := range tc.absentCommitSigs { @@ -113,8 +139,7 @@ func TestFinalizeBlockDecidedLastCommit(t *testing.T) { } // block for height 2 - block, err := sf.MakeBlock(state, 2, lastCommit) - require.NoError(t, err) + block := sf.MakeBlock(state, 2, lastCommit) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} @@ -136,7 +161,7 @@ func TestFinalizeBlockByzantineValidators(t *testing.T) { defer cancel() app := &testApp{} - logger := log.TestingLogger() + logger := log.NewNopLogger() cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) err := proxyApp.Start(ctx) @@ -154,14 +179,14 @@ func TestFinalizeBlockByzantineValidators(t *testing.T) { Height: 10, Time: defaultEvidenceTime, LastBlockID: blockID, - LastCommitHash: crypto.CRandBytes(tmhash.Size), - DataHash: crypto.CRandBytes(tmhash.Size), + LastCommitHash: crypto.CRandBytes(crypto.HashSize), + DataHash: crypto.CRandBytes(crypto.HashSize), ValidatorsHash: state.Validators.Hash(), NextValidatorsHash: state.Validators.Hash(), - ConsensusHash: crypto.CRandBytes(tmhash.Size), - AppHash: crypto.CRandBytes(tmhash.Size), - LastResultsHash: crypto.CRandBytes(tmhash.Size), - EvidenceHash: crypto.CRandBytes(tmhash.Size), + ConsensusHash: crypto.CRandBytes(crypto.HashSize), + AppHash: crypto.CRandBytes(crypto.HashSize), + LastResultsHash: crypto.CRandBytes(crypto.HashSize), + EvidenceHash: crypto.CRandBytes(crypto.HashSize), ProposerAddress: crypto.CRandBytes(crypto.AddressSize), } @@ -190,16 +215,16 @@ func TestFinalizeBlockByzantineValidators(t *testing.T) { ev := []types.Evidence{dve, lcae} - abciEv := []abci.Evidence{ + abciMb := []abci.Misbehavior{ { - Type: abci.EvidenceType_DUPLICATE_VOTE, + Type: abci.MisbehaviorType_DUPLICATE_VOTE, Height: 3, Time: defaultEvidenceTime, Validator: types.TM2PB.Validator(state.Validators.Validators[0]), TotalVotingPower: 10, }, { - Type: abci.EvidenceType_LIGHT_CLIENT_ATTACK, + Type: abci.MisbehaviorType_LIGHT_CLIENT_ATTACK, Height: 8, Time: defaultEvidenceTime, Validator: types.TM2PB.Validator(state.Validators.Validators[0]), @@ -211,14 +236,26 @@ func TestFinalizeBlockByzantineValidators(t *testing.T) { evpool.On("PendingEvidence", mock.AnythingOfType("int64")).Return(ev, int64(100)) evpool.On("Update", ctx, mock.AnythingOfType("state.State"), mock.AnythingOfType("types.EvidenceList")).Return() evpool.On("CheckEvidence", ctx, mock.AnythingOfType("types.EvidenceList")).Return(nil) + mp := &mpmocks.Mempool{} + mp.On("Lock").Return() + mp.On("Unlock").Return() + mp.On("FlushAppConn", mock.Anything).Return(nil) + mp.On("Update", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything).Return(nil) + + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) blockStore := store.NewBlockStore(dbm.NewMemDB()) - blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp, - mmock.Mempool{}, evpool, blockStore) + blockExec := sm.NewBlockExecutor(stateStore, log.NewNopLogger(), proxyApp, mp, evpool, blockStore, eventBus, sm.NopMetrics()) - block, err := sf.MakeBlock(state, 1, new(types.Commit)) - require.NoError(t, err) + block := sf.MakeBlock(state, 1, new(types.Commit)) block.Evidence = ev block.Header.EvidenceHash = block.Evidence.Hash() bps, err := block.MakePartSet(testPartSize) @@ -230,17 +267,17 @@ func TestFinalizeBlockByzantineValidators(t *testing.T) { require.NoError(t, err) // TODO check state and mempool - assert.Equal(t, abciEv, app.ByzantineValidators) + assert.Equal(t, abciMb, app.ByzantineValidators) } func TestProcessProposal(t *testing.T) { const height = 2 - txs := factory.MakeTenTxs(height) + txs := factory.MakeNTxs(height, 10) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - app := abcimocks.NewBaseMock() - logger := log.TestingLogger() + app := abcimocks.NewApplication(t) + logger := log.NewNopLogger() cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) err := proxyApp.Start(ctx) @@ -250,17 +287,21 @@ func TestProcessProposal(t *testing.T) { stateStore := sm.NewStore(stateDB) blockStore := store.NewBlockStore(dbm.NewMemDB()) + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) + blockExec := sm.NewBlockExecutor( stateStore, logger, proxyApp, - mmock.Mempool{}, + new(mpmocks.Mempool), sm.EmptyEvidencePool{}, blockStore, + eventBus, + sm.NopMetrics(), ) - block0, err := sf.MakeBlock(state, height-1, new(types.Commit)) - require.NoError(t, err) + block0 := sf.MakeBlock(state, height-1, new(types.Commit)) lastCommitSig := []types.CommitSig{} partSet, err := block0.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) @@ -284,27 +325,29 @@ func TestProcessProposal(t *testing.T) { } lastCommit := types.NewCommit(height-1, 0, types.BlockID{}, lastCommitSig) - block1, err := sf.MakeBlock(state, height, lastCommit) - require.NoError(t, err) + block1 := sf.MakeBlock(state, height, lastCommit) block1.Txs = txs - expectedRpp := abci.RequestProcessProposal{ - Hash: block1.Hash(), - Header: *block1.Header.ToProto(), + expectedRpp := &abci.RequestProcessProposal{ Txs: block1.Txs.ToSliceOfBytes(), + Hash: block1.Hash(), + Height: block1.Header.Height, + Time: block1.Header.Time, ByzantineValidators: block1.Evidence.ToABCI(), ProposedLastCommit: abci.CommitInfo{ Round: 0, Votes: voteInfos, }, + NextValidatorsHash: block1.NextValidatorsHash, + ProposerAddress: block1.ProposerAddress, } - app.On("ProcessProposal", mock.Anything).Return(abci.ResponseProcessProposal{Accept: true}) + app.On("ProcessProposal", mock.Anything, mock.Anything).Return(&abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil) acceptBlock, err := blockExec.ProcessProposal(ctx, block1, state) require.NoError(t, err) require.True(t, acceptBlock) app.AssertExpectations(t) - app.AssertCalled(t, "ProcessProposal", expectedRpp) + app.AssertCalled(t, "ProcessProposal", ctx, expectedRpp) } func TestValidateValidatorUpdates(t *testing.T) { @@ -443,7 +486,7 @@ func TestFinalizeBlockValidatorUpdates(t *testing.T) { defer cancel() app := &testApp{} - logger := log.TestingLogger() + logger := log.NewNopLogger() cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) err := proxyApp.Start(ctx) @@ -452,31 +495,40 @@ func TestFinalizeBlockValidatorUpdates(t *testing.T) { state, stateDB, _ := makeState(t, 1, 1) stateStore := sm.NewStore(stateDB) blockStore := store.NewBlockStore(dbm.NewMemDB()) + mp := &mpmocks.Mempool{} + mp.On("Lock").Return() + mp.On("Unlock").Return() + mp.On("FlushAppConn", mock.Anything).Return(nil) + mp.On("Update", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything).Return(nil) + mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs{}) + + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) blockExec := sm.NewBlockExecutor( stateStore, logger, proxyApp, - mmock.Mempool{}, + mp, sm.EmptyEvidencePool{}, blockStore, + eventBus, + sm.NopMetrics(), ) - eventBus := eventbus.NewDefault(logger) - err = eventBus.Start(ctx) - require.NoError(t, err) - defer eventBus.Stop() - - blockExec.SetEventBus(eventBus) - updatesSub, err := eventBus.SubscribeWithArgs(ctx, pubsub.SubscribeArgs{ ClientID: "TestFinalizeBlockValidatorUpdates", Query: types.EventQueryValidatorSetUpdates, }) require.NoError(t, err) - block, err := sf.MakeBlock(state, 1, new(types.Commit)) - require.NoError(t, err) + block := sf.MakeBlock(state, 1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} @@ -518,26 +570,30 @@ func TestFinalizeBlockValidatorUpdatesResultingInEmptySet(t *testing.T) { defer cancel() app := &testApp{} - logger := log.TestingLogger() + logger := log.NewNopLogger() cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) err := proxyApp.Start(ctx) require.NoError(t, err) + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) + state, stateDB, _ := makeState(t, 1, 1) stateStore := sm.NewStore(stateDB) blockStore := store.NewBlockStore(dbm.NewMemDB()) blockExec := sm.NewBlockExecutor( stateStore, - log.TestingLogger(), + log.NewNopLogger(), proxyApp, - mmock.Mempool{}, + new(mpmocks.Mempool), sm.EmptyEvidencePool{}, blockStore, + eventBus, + sm.NopMetrics(), ) - block, err := sf.MakeBlock(state, 1, new(types.Commit)) - require.NoError(t, err) + block := sf.MakeBlock(state, 1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} @@ -554,10 +610,400 @@ func TestFinalizeBlockValidatorUpdatesResultingInEmptySet(t *testing.T) { assert.NotEmpty(t, state.NextValidators.Validators) } +func TestEmptyPrepareProposal(t *testing.T) { + const height = 2 + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + logger := log.NewNopLogger() + + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) + + app := abcimocks.NewApplication(t) + app.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{}, nil) + cc := abciclient.NewLocalClient(logger, app) + proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) + err := proxyApp.Start(ctx) + require.NoError(t, err) + + state, stateDB, privVals := makeState(t, 1, height) + stateStore := sm.NewStore(stateDB) + mp := &mpmocks.Mempool{} + mp.On("Lock").Return() + mp.On("Unlock").Return() + mp.On("FlushAppConn", mock.Anything).Return(nil) + mp.On("Update", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything).Return(nil) + mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs{}) + + blockExec := sm.NewBlockExecutor( + stateStore, + logger, + proxyApp, + mp, + sm.EmptyEvidencePool{}, + nil, + eventBus, + sm.NopMetrics(), + ) + pa, _ := state.Validators.GetByIndex(0) + commit, votes := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) + _, err = blockExec.CreateProposalBlock(ctx, height, state, commit, pa, votes) + require.NoError(t, err) +} + +// TestPrepareProposalErrorOnNonExistingRemoved tests that the block creation logic returns +// an error if the ResponsePrepareProposal returned from the application marks +// a transaction as REMOVED that was not present in the original proposal. +func TestPrepareProposalErrorOnNonExistingRemoved(t *testing.T) { + const height = 2 + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + logger := log.NewNopLogger() + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) + + state, stateDB, privVals := makeState(t, 1, height) + stateStore := sm.NewStore(stateDB) + + evpool := &mocks.EvidencePool{} + evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0)) + + mp := &mpmocks.Mempool{} + mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs{}) + + app := abcimocks.NewApplication(t) + + // create an invalid ResponsePrepareProposal + rpp := &abci.ResponsePrepareProposal{ + TxRecords: []*abci.TxRecord{ + { + Action: abci.TxRecord_REMOVED, + Tx: []byte("new tx"), + }, + }, + } + app.On("PrepareProposal", mock.Anything, mock.Anything).Return(rpp, nil) + + cc := abciclient.NewLocalClient(logger, app) + proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) + err := proxyApp.Start(ctx) + require.NoError(t, err) + + blockExec := sm.NewBlockExecutor( + stateStore, + logger, + proxyApp, + mp, + evpool, + nil, + eventBus, + sm.NopMetrics(), + ) + pa, _ := state.Validators.GetByIndex(0) + commit, votes := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) + block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, votes) + require.ErrorContains(t, err, "new transaction incorrectly marked as removed") + require.Nil(t, block) + + mp.AssertExpectations(t) +} + +// TestPrepareProposalRemoveTxs tests that any transactions marked as REMOVED +// are not included in the block produced by CreateProposalBlock. The test also +// ensures that any transactions removed are also removed from the mempool. +func TestPrepareProposalRemoveTxs(t *testing.T) { + const height = 2 + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + logger := log.NewNopLogger() + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) + + state, stateDB, privVals := makeState(t, 1, height) + stateStore := sm.NewStore(stateDB) + + evpool := &mocks.EvidencePool{} + evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0)) + + txs := factory.MakeNTxs(height, 10) + mp := &mpmocks.Mempool{} + mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs)) + + trs := txsToTxRecords(types.Txs(txs)) + trs[0].Action = abci.TxRecord_REMOVED + trs[1].Action = abci.TxRecord_REMOVED + mp.On("RemoveTxByKey", mock.Anything).Return(nil).Twice() + + app := abcimocks.NewApplication(t) + app.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{ + TxRecords: trs, + }, nil) + + cc := abciclient.NewLocalClient(logger, app) + proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) + err := proxyApp.Start(ctx) + require.NoError(t, err) + + blockExec := sm.NewBlockExecutor( + stateStore, + logger, + proxyApp, + mp, + evpool, + nil, + eventBus, + sm.NopMetrics(), + ) + pa, _ := state.Validators.GetByIndex(0) + commit, votes := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) + block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, votes) + require.NoError(t, err) + require.Len(t, block.Data.Txs.ToSliceOfBytes(), len(trs)-2) + + require.Equal(t, -1, block.Data.Txs.Index(types.Tx(trs[0].Tx))) + require.Equal(t, -1, block.Data.Txs.Index(types.Tx(trs[1].Tx))) + + mp.AssertCalled(t, "RemoveTxByKey", types.Tx(trs[0].Tx).Key()) + mp.AssertCalled(t, "RemoveTxByKey", types.Tx(trs[1].Tx).Key()) + mp.AssertExpectations(t) +} + +// TestPrepareProposalAddedTxsIncluded tests that any transactions marked as ADDED +// in the prepare proposal response are included in the block. +func TestPrepareProposalAddedTxsIncluded(t *testing.T) { + const height = 2 + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + logger := log.NewNopLogger() + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) + + state, stateDB, privVals := makeState(t, 1, height) + stateStore := sm.NewStore(stateDB) + + evpool := &mocks.EvidencePool{} + evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0)) + + txs := factory.MakeNTxs(height, 10) + mp := &mpmocks.Mempool{} + mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs[2:])) + + trs := txsToTxRecords(types.Txs(txs)) + trs[0].Action = abci.TxRecord_ADDED + trs[1].Action = abci.TxRecord_ADDED + + app := abcimocks.NewApplication(t) + app.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{ + TxRecords: trs, + }, nil) + + cc := abciclient.NewLocalClient(logger, app) + proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) + err := proxyApp.Start(ctx) + require.NoError(t, err) + + blockExec := sm.NewBlockExecutor( + stateStore, + logger, + proxyApp, + mp, + evpool, + nil, + eventBus, + sm.NopMetrics(), + ) + pa, _ := state.Validators.GetByIndex(0) + commit, votes := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) + block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, votes) + require.NoError(t, err) + + require.Equal(t, txs[0], block.Data.Txs[0]) + require.Equal(t, txs[1], block.Data.Txs[1]) + + mp.AssertExpectations(t) +} + +// TestPrepareProposalReorderTxs tests that CreateBlock produces a block with transactions +// in the order matching the order they are returned from PrepareProposal. +func TestPrepareProposalReorderTxs(t *testing.T) { + const height = 2 + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + logger := log.NewNopLogger() + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) + + state, stateDB, privVals := makeState(t, 1, height) + stateStore := sm.NewStore(stateDB) + + evpool := &mocks.EvidencePool{} + evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0)) + + txs := factory.MakeNTxs(height, 10) + mp := &mpmocks.Mempool{} + mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs)) + + trs := txsToTxRecords(types.Txs(txs)) + trs = trs[2:] + trs = append(trs[len(trs)/2:], trs[:len(trs)/2]...) + + app := abcimocks.NewApplication(t) + app.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{ + TxRecords: trs, + }, nil) + + cc := abciclient.NewLocalClient(logger, app) + proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) + err := proxyApp.Start(ctx) + require.NoError(t, err) + + blockExec := sm.NewBlockExecutor( + stateStore, + logger, + proxyApp, + mp, + evpool, + nil, + eventBus, + sm.NopMetrics(), + ) + pa, _ := state.Validators.GetByIndex(0) + commit, votes := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) + block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, votes) + require.NoError(t, err) + for i, tx := range block.Data.Txs { + require.Equal(t, types.Tx(trs[i].Tx), tx) + } + + mp.AssertExpectations(t) + +} + +// TestPrepareProposalErrorOnTooManyTxs tests that the block creation logic returns +// an error if the ResponsePrepareProposal returned from the application is invalid. +func TestPrepareProposalErrorOnTooManyTxs(t *testing.T) { + const height = 2 + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + logger := log.NewNopLogger() + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) + + state, stateDB, privVals := makeState(t, 1, height) + // limit max block size + state.ConsensusParams.Block.MaxBytes = 60 * 1024 + stateStore := sm.NewStore(stateDB) + + evpool := &mocks.EvidencePool{} + evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0)) + + const nValidators = 1 + var bytesPerTx int64 = 3 + maxDataBytes := types.MaxDataBytes(state.ConsensusParams.Block.MaxBytes, 0, nValidators) + txs := factory.MakeNTxs(height, maxDataBytes/bytesPerTx+2) // +2 so that tx don't fit + mp := &mpmocks.Mempool{} + mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs)) + + trs := txsToTxRecords(types.Txs(txs)) + + app := abcimocks.NewApplication(t) + app.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{ + TxRecords: trs, + }, nil) + + cc := abciclient.NewLocalClient(logger, app) + proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) + err := proxyApp.Start(ctx) + require.NoError(t, err) + + blockExec := sm.NewBlockExecutor( + stateStore, + logger, + proxyApp, + mp, + evpool, + nil, + eventBus, + sm.NopMetrics(), + ) + pa, _ := state.Validators.GetByIndex(0) + commit, votes := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) + + block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, votes) + require.ErrorContains(t, err, "transaction data size exceeds maximum") + require.Nil(t, block, "") + + mp.AssertExpectations(t) +} + +// TestPrepareProposalErrorOnPrepareProposalError tests when the client returns an error +// upon calling PrepareProposal on it. +func TestPrepareProposalErrorOnPrepareProposalError(t *testing.T) { + const height = 2 + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + logger := log.NewNopLogger() + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) + + state, stateDB, privVals := makeState(t, 1, height) + stateStore := sm.NewStore(stateDB) + + evpool := &mocks.EvidencePool{} + evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0)) + + txs := factory.MakeNTxs(height, 10) + mp := &mpmocks.Mempool{} + mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs)) + + cm := &abciclientmocks.Client{} + cm.On("IsRunning").Return(true) + cm.On("Error").Return(nil) + cm.On("Start", mock.Anything).Return(nil).Once() + cm.On("Wait").Return(nil).Once() + cm.On("PrepareProposal", mock.Anything, mock.Anything).Return(nil, errors.New("an injected error")).Once() + + proxyApp := proxy.New(cm, logger, proxy.NopMetrics()) + err := proxyApp.Start(ctx) + require.NoError(t, err) + + blockExec := sm.NewBlockExecutor( + stateStore, + logger, + proxyApp, + mp, + evpool, + nil, + eventBus, + sm.NopMetrics(), + ) + pa, _ := state.Validators.GetByIndex(0) + commit, votes := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) + + block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, votes) + require.Nil(t, block) + require.ErrorContains(t, err, "an injected error") + + mp.AssertExpectations(t) +} + func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.BlockID { var ( - h = make([]byte, tmhash.Size) - psH = make([]byte, tmhash.Size) + h = make([]byte, crypto.HashSize) + psH = make([]byte, crypto.HashSize) ) copy(h, hash) copy(psH, partSetHash) @@ -569,3 +1015,14 @@ func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.Bloc }, } } + +func txsToTxRecords(txs []types.Tx) []*abci.TxRecord { + trs := make([]*abci.TxRecord, len(txs)) + for i, tx := range txs { + trs[i] = &abci.TxRecord{ + Action: abci.TxRecord_UNMODIFIED, + Tx: tx, + } + } + return trs +} diff --git a/internal/state/helpers_test.go b/internal/state/helpers_test.go index 6df958908..07dd0d865 100644 --- a/internal/state/helpers_test.go +++ b/internal/state/helpers_test.go @@ -18,7 +18,6 @@ import ( sm "github.com/tendermint/tendermint/internal/state" sf "github.com/tendermint/tendermint/internal/state/test/factory" "github.com/tendermint/tendermint/internal/test/factory" - tmrand "github.com/tendermint/tendermint/libs/rand" tmtime "github.com/tendermint/tendermint/libs/time" tmstate "github.com/tendermint/tendermint/proto/tendermint/state" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -47,7 +46,7 @@ func makeAndCommitGoodBlock( state, blockID := makeAndApplyGoodBlock(ctx, t, state, height, lastCommit, proposerAddr, blockExec, evidence) // Simulate a lastCommit for this block from all validators for the next height - commit := makeValidCommit(ctx, t, height, blockID, state.Validators, privVals) + commit, _ := makeValidCommit(ctx, t, height, blockID, state.Validators, privVals) return state, blockID, commit } @@ -63,12 +62,13 @@ func makeAndApplyGoodBlock( evidence []types.Evidence, ) (sm.State, types.BlockID) { t.Helper() - block, _, err := state.MakeBlock(height, factory.MakeTenTxs(height), lastCommit, evidence, proposerAddr) + block := state.MakeBlock(height, factory.MakeNTxs(height, 10), lastCommit, evidence, proposerAddr) + partSet, err := block.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) require.NoError(t, blockExec.ValidateBlock(ctx, state, block)) blockID := types.BlockID{Hash: block.Hash(), - PartSetHeader: types.PartSetHeader{Total: 3, Hash: tmrand.Bytes(32)}} + PartSetHeader: partSet.Header()} state, err = blockExec.ApplyBlock(ctx, state, blockID, block) require.NoError(t, err) @@ -82,17 +82,19 @@ func makeValidCommit( blockID types.BlockID, vals *types.ValidatorSet, privVals map[string]types.PrivValidator, -) *types.Commit { +) (*types.Commit, []*types.Vote) { t.Helper() - sigs := make([]types.CommitSig, 0) + sigs := make([]types.CommitSig, vals.Size()) + votes := make([]*types.Vote, vals.Size()) for i := 0; i < vals.Size(); i++ { _, val := vals.GetByIndex(int32(i)) vote, err := factory.MakeVote(ctx, privVals[val.Address.String()], chainID, int32(i), height, 0, 2, blockID, time.Now()) require.NoError(t, err) - sigs = append(sigs, vote.CommitSig()) + sigs[i] = vote.CommitSig() + votes[i] = vote } - return types.NewCommit(height, 0, blockID, sigs) + return types.NewCommit(height, 0, blockID, sigs), votes } func makeState(t *testing.T, nVals, height int) (sm.State, dbm.DB, map[string]types.PrivValidator) { @@ -144,8 +146,7 @@ func makeHeaderPartsResponsesValPubKeyChange( pubkey crypto.PubKey, ) (types.Header, types.BlockID, *tmstate.ABCIResponses) { - block, err := sf.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit)) - require.NoError(t, err) + block := sf.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit)) abciResponses := &tmstate.ABCIResponses{} // If the pubkey is new, remove the old and add the new. _, val := state.NextValidators.GetByIndex(0) @@ -173,8 +174,7 @@ func makeHeaderPartsResponsesValPowerChange( ) (types.Header, types.BlockID, *tmstate.ABCIResponses) { t.Helper() - block, err := sf.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit)) - require.NoError(t, err) + block := sf.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit)) abciResponses := &tmstate.ABCIResponses{} @@ -202,8 +202,7 @@ func makeHeaderPartsResponsesParams( ) (types.Header, types.BlockID, *tmstate.ABCIResponses) { t.Helper() - block, err := sf.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit)) - require.NoError(t, err) + block := sf.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit)) pbParams := params.ToProto() abciResponses := &tmstate.ABCIResponses{ FinalizeBlock: &abci.ResponseFinalizeBlock{ConsensusParamUpdates: &pbParams}, @@ -273,17 +272,17 @@ type testApp struct { abci.BaseApplication CommitVotes []abci.VoteInfo - ByzantineValidators []abci.Evidence + ByzantineValidators []abci.Misbehavior ValidatorUpdates []abci.ValidatorUpdate } var _ abci.Application = (*testApp)(nil) -func (app *testApp) Info(req abci.RequestInfo) (resInfo abci.ResponseInfo) { - return abci.ResponseInfo{} +func (app *testApp) Info(_ context.Context, req *abci.RequestInfo) (*abci.ResponseInfo, error) { + return &abci.ResponseInfo{}, nil } -func (app *testApp) FinalizeBlock(req abci.RequestFinalizeBlock) abci.ResponseFinalizeBlock { +func (app *testApp) FinalizeBlock(_ context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { app.CommitVotes = req.DecidedLastCommit.Votes app.ByzantineValidators = req.ByzantineValidators @@ -296,7 +295,7 @@ func (app *testApp) FinalizeBlock(req abci.RequestFinalizeBlock) abci.ResponseFi } } - return abci.ResponseFinalizeBlock{ + return &abci.ResponseFinalizeBlock{ ValidatorUpdates: app.ValidatorUpdates, ConsensusParamUpdates: &tmproto.ConsensusParams{ Version: &tmproto.VersionParams{ @@ -305,26 +304,26 @@ func (app *testApp) FinalizeBlock(req abci.RequestFinalizeBlock) abci.ResponseFi }, Events: []abci.Event{}, TxResults: resTxs, - } + }, nil } -func (app *testApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { - return abci.ResponseCheckTx{} +func (app *testApp) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) { + return &abci.ResponseCheckTx{}, nil } -func (app *testApp) Commit() abci.ResponseCommit { - return abci.ResponseCommit{RetainHeight: 1} +func (app *testApp) Commit(context.Context) (*abci.ResponseCommit, error) { + return &abci.ResponseCommit{RetainHeight: 1}, nil } -func (app *testApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) { - return +func (app *testApp) Query(_ context.Context, req *abci.RequestQuery) (*abci.ResponseQuery, error) { + return &abci.ResponseQuery{}, nil } -func (app *testApp) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal { +func (app *testApp) ProcessProposal(_ context.Context, req *abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error) { for _, tx := range req.Txs { if len(tx) == 0 { - return abci.ResponseProcessProposal{Accept: false} + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, nil } } - return abci.ResponseProcessProposal{Accept: true} + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil } diff --git a/internal/state/indexer/indexer_service_test.go b/internal/state/indexer/indexer_service_test.go index f6261c519..6126ae259 100644 --- a/internal/state/indexer/indexer_service_test.go +++ b/internal/state/indexer/indexer_service_test.go @@ -43,7 +43,7 @@ func TestIndexerServiceIndexesBlocks(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := tmlog.TestingLogger() + logger := tmlog.NewNopLogger() // event bus eventBus := eventbus.NewDefault(logger) err := eventBus.Start(ctx) @@ -54,8 +54,7 @@ func TestIndexerServiceIndexesBlocks(t *testing.T) { assert.False(t, indexer.IndexingEnabled([]indexer.EventSink{})) // event sink setup - pool, err := setupDB(t) - assert.NoError(t, err) + pool := setupDB(t) store := dbm.NewMemDB() eventSinks := []indexer.EventSink{kv.NewEventSink(store), pSink} @@ -71,7 +70,7 @@ func TestIndexerServiceIndexesBlocks(t *testing.T) { t.Cleanup(service.Wait) // publish block with txs - err = eventBus.PublishEventNewBlockHeader(ctx, types.EventDataNewBlockHeader{ + err = eventBus.PublishEventNewBlockHeader(types.EventDataNewBlockHeader{ Header: types.Header{Height: 1}, NumTxs: int64(2), }) @@ -82,7 +81,7 @@ func TestIndexerServiceIndexesBlocks(t *testing.T) { Tx: types.Tx("foo"), Result: abci.ExecTxResult{Code: 0}, } - err = eventBus.PublishEventTx(ctx, types.EventDataTx{TxResult: *txResult1}) + err = eventBus.PublishEventTx(types.EventDataTx{TxResult: *txResult1}) require.NoError(t, err) txResult2 := &abci.TxResult{ Height: 1, @@ -90,7 +89,7 @@ func TestIndexerServiceIndexesBlocks(t *testing.T) { Tx: types.Tx("bar"), Result: abci.ExecTxResult{Code: 0}, } - err = eventBus.PublishEventTx(ctx, types.EventDataTx{TxResult: *txResult2}) + err = eventBus.PublishEventTx(types.EventDataTx{TxResult: *txResult2}) require.NoError(t, err) time.Sleep(100 * time.Millisecond) @@ -133,7 +132,7 @@ func resetDB(t *testing.T) { assert.NoError(t, err) } -func setupDB(t *testing.T) (*dockertest.Pool, error) { +func setupDB(t *testing.T) *dockertest.Pool { t.Helper() pool, err := dockertest.NewPool(os.Getenv("DOCKER_URL")) assert.NoError(t, err) @@ -187,7 +186,7 @@ func setupDB(t *testing.T) (*dockertest.Pool, error) { err = migrator.Apply(psqldb, sm) assert.NoError(t, err) - return pool, nil + return pool } func teardown(t *testing.T, pool *dockertest.Pool) error { diff --git a/internal/state/indexer/mocks/event_sink.go b/internal/state/indexer/mocks/event_sink.go index d5555a417..decf551ab 100644 --- a/internal/state/indexer/mocks/event_sink.go +++ b/internal/state/indexer/mocks/event_sink.go @@ -12,6 +12,8 @@ import ( tenderminttypes "github.com/tendermint/tendermint/types" + testing "testing" + types "github.com/tendermint/tendermint/abci/types" ) @@ -165,3 +167,13 @@ func (_m *EventSink) Type() indexer.EventSinkType { return r0 } + +// NewEventSink creates a new instance of EventSink. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewEventSink(t testing.TB) *EventSink { + mock := &EventSink{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/state/mocks/block_store.go b/internal/state/mocks/block_store.go index 563183437..7cc7fa883 100644 --- a/internal/state/mocks/block_store.go +++ b/internal/state/mocks/block_store.go @@ -5,6 +5,8 @@ package mocks import ( mock "github.com/stretchr/testify/mock" + testing "testing" + types "github.com/tendermint/tendermint/types" ) @@ -208,3 +210,13 @@ func (_m *BlockStore) Size() int64 { return r0 } + +// NewBlockStore creates a new instance of BlockStore. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewBlockStore(t testing.TB) *BlockStore { + mock := &BlockStore{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/state/mocks/evidence_pool.go b/internal/state/mocks/evidence_pool.go index 04e8be7bc..49633269b 100644 --- a/internal/state/mocks/evidence_pool.go +++ b/internal/state/mocks/evidence_pool.go @@ -8,6 +8,8 @@ import ( mock "github.com/stretchr/testify/mock" state "github.com/tendermint/tendermint/internal/state" + testing "testing" + types "github.com/tendermint/tendermint/types" ) @@ -71,3 +73,13 @@ func (_m *EvidencePool) PendingEvidence(maxBytes int64) ([]types.Evidence, int64 func (_m *EvidencePool) Update(_a0 context.Context, _a1 state.State, _a2 types.EvidenceList) { _m.Called(_a0, _a1, _a2) } + +// NewEvidencePool creates a new instance of EvidencePool. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewEvidencePool(t testing.TB) *EvidencePool { + mock := &EvidencePool{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/state/mocks/store.go b/internal/state/mocks/store.go index 02c69d3e0..9b41f3c1b 100644 --- a/internal/state/mocks/store.go +++ b/internal/state/mocks/store.go @@ -7,6 +7,8 @@ import ( state "github.com/tendermint/tendermint/internal/state" tendermintstate "github.com/tendermint/tendermint/proto/tendermint/state" + testing "testing" + types "github.com/tendermint/tendermint/types" ) @@ -186,3 +188,13 @@ func (_m *Store) SaveValidatorSets(_a0 int64, _a1 int64, _a2 *types.ValidatorSet return r0 } + +// NewStore creates a new instance of Store. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewStore(t testing.TB) *Store { + mock := &Store{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/state/state.go b/internal/state/state.go index 6a39e2c08..a31d8baad 100644 --- a/internal/state/state.go +++ b/internal/state/state.go @@ -129,23 +129,30 @@ func (state State) Copy() State { } // Equals returns true if the States are identical. -func (state State) Equals(state2 State) bool { - sbz, s2bz := state.Bytes(), state2.Bytes() - return bytes.Equal(sbz, s2bz) +func (state State) Equals(state2 State) (bool, error) { + sbz, err := state.Bytes() + if err != nil { + return false, err + } + s2bz, err := state2.Bytes() + if err != nil { + return false, err + } + return bytes.Equal(sbz, s2bz), nil } -// Bytes serializes the State using protobuf. -// It panics if either casting to protobuf or serialization fails. -func (state State) Bytes() []byte { +// Bytes serializes the State using protobuf, propagating marshaling +// errors +func (state State) Bytes() ([]byte, error) { sm, err := state.ToProto() if err != nil { - panic(err) + return nil, err } bz, err := proto.Marshal(sm) if err != nil { - panic(err) + return nil, err } - return bz + return bz, nil } // IsEmpty returns true if the State is equal to the empty State. @@ -260,7 +267,7 @@ func (state State) MakeBlock( commit *types.Commit, evidence []types.Evidence, proposerAddress []byte, -) (*types.Block, *types.PartSet, error) { +) *types.Block { // Build base block with block data. block := types.MakeBlock(height, txs, commit, evidence) @@ -274,12 +281,7 @@ func (state State) MakeBlock( proposerAddress, ) - bps, err := block.MakePartSet(types.BlockPartSizeBytes) - if err != nil { - return nil, nil, err - } - - return block, bps, nil + return block } //------------------------------------------------------------------------ diff --git a/internal/state/state_test.go b/internal/state/state_test.go index 9c2f6e3db..f38d37ea4 100644 --- a/internal/state/state_test.go +++ b/internal/state/state_test.go @@ -18,6 +18,7 @@ import ( "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/encoding" + "github.com/tendermint/tendermint/crypto/merkle" sm "github.com/tendermint/tendermint/internal/state" statefactory "github.com/tendermint/tendermint/internal/state/test/factory" tmstate "github.com/tendermint/tendermint/proto/tendermint/state" @@ -54,13 +55,18 @@ func TestStateCopy(t *testing.T) { stateCopy := state.Copy() - assert.True(t, state.Equals(stateCopy), + seq, err := state.Equals(stateCopy) + require.NoError(t, err) + assert.True(t, seq, "expected state and its copy to be identical.\ngot: %v\nexpected: %v", stateCopy, state) stateCopy.LastBlockHeight++ stateCopy.LastValidators = state.Validators - assert.False(t, state.Equals(stateCopy), "expected states to be different. got same %v", state) + + seq, err = state.Equals(stateCopy) + require.NoError(t, err) + assert.False(t, seq, "expected states to be different. got same %v", state) } // TestMakeGenesisStateNilValidators tests state's consistency when genesis file's validators field is nil. @@ -89,7 +95,9 @@ func TestStateSaveLoad(t *testing.T) { loadedState, err := stateStore.Load() require.NoError(t, err) - assert.True(t, state.Equals(loadedState), + seq, err := state.Equals(loadedState) + require.NoError(t, err) + assert.True(t, seq, "expected state and its copy to be identical.\ngot: %v\nexpected: %v", loadedState, state) } @@ -103,8 +111,7 @@ func TestABCIResponsesSaveLoad1(t *testing.T) { state.LastBlockHeight++ // Build mock responses. - block, err := statefactory.MakeBlock(state, 2, new(types.Commit)) - require.NoError(t, err) + block := statefactory.MakeBlock(state, 2, new(types.Commit)) abciResponses := new(tmstate.ABCIResponses) dtxs := make([]*abci.ExecTxResult, 2) @@ -205,14 +212,13 @@ func TestABCIResponsesSaveLoad2(t *testing.T) { res, err := stateStore.LoadABCIResponses(h) if assert.NoError(t, err, "%d", i) { t.Log(res) - responses := &tmstate.ABCIResponses{ - FinalizeBlock: &abci.ResponseFinalizeBlock{ - TxResults: tc.expected, - }, - } - sm.ABCIResponsesResultsHash(res) - sm.ABCIResponsesResultsHash(responses) - assert.Equal(t, sm.ABCIResponsesResultsHash(responses), sm.ABCIResponsesResultsHash(res), "%d", i) + e, err := abci.MarshalTxResults(tc.expected) + require.NoError(t, err) + he := merkle.HashFromByteSlices(e) + rs, err := abci.MarshalTxResults(res.FinalizeBlock.TxResults) + hrs := merkle.HashFromByteSlices(rs) + require.NoError(t, err) + assert.Equal(t, he, hrs, "%d", i) } } } @@ -278,9 +284,12 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { header, blockID, responses := makeHeaderPartsResponsesValPowerChange(t, state, power) validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.FinalizeBlock.ValidatorUpdates) require.NoError(t, err) - state, err = state.Update(blockID, &header, sm.ABCIResponsesResultsHash(responses), responses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates) + rs, err := abci.MarshalTxResults(responses.FinalizeBlock.TxResults) require.NoError(t, err) - err := stateStore.Save(state) + h := merkle.HashFromByteSlices(rs) + state, err = state.Update(blockID, &header, h, responses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates) + require.NoError(t, err) + err = stateStore.Save(state) require.NoError(t, err) } @@ -451,19 +460,19 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) { // NewValidatorSet calls IncrementProposerPriority but uses on a copy of val1 assert.EqualValues(t, 0, val1.ProposerPriority) - block, err := statefactory.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit)) - require.NoError(t, err) + block := statefactory.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} - abciResponses := &tmstate.ABCIResponses{ - FinalizeBlock: &abci.ResponseFinalizeBlock{ - ValidatorUpdates: nil, - }, + fb := &abci.ResponseFinalizeBlock{ + ValidatorUpdates: nil, } - validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.FinalizeBlock.ValidatorUpdates) + validatorUpdates, err := types.PB2TM.ValidatorUpdates(fb.ValidatorUpdates) require.NoError(t, err) - updatedState, err := state.Update(blockID, &block.Header, sm.ABCIResponsesResultsHash(abciResponses), abciResponses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates) + rs, err := abci.MarshalTxResults(fb.TxResults) + require.NoError(t, err) + h := merkle.HashFromByteSlices(rs) + updatedState, err := state.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates) assert.NoError(t, err) curTotal := val1VotingPower // one increment step and one validator: 0 + power - total_power == 0 @@ -478,7 +487,10 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) { updateAddVal := abci.ValidatorUpdate{PubKey: fvp, Power: val2VotingPower} validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateAddVal}) assert.NoError(t, err) - updatedState2, err := updatedState.Update(blockID, &block.Header, sm.ABCIResponsesResultsHash(abciResponses), abciResponses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates) + rs, err = abci.MarshalTxResults(fb.TxResults) + require.NoError(t, err) + h = merkle.HashFromByteSlices(rs) + updatedState2, err := updatedState.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates) assert.NoError(t, err) require.Equal(t, len(updatedState2.NextValidators.Validators), 2) @@ -517,7 +529,10 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) { // this will cause the diff of priorities (77) // to be larger than threshold == 2*totalVotingPower (22): - updatedState3, err := updatedState2.Update(blockID, &block.Header, sm.ABCIResponsesResultsHash(abciResponses), abciResponses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates) + rs, err = abci.MarshalTxResults(fb.TxResults) + require.NoError(t, err) + h = merkle.HashFromByteSlices(rs) + updatedState3, err := updatedState2.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates) assert.NoError(t, err) require.Equal(t, len(updatedState3.NextValidators.Validators), 2) @@ -569,21 +584,21 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { // we only have one validator: assert.Equal(t, val1PubKey.Address(), state.Validators.Proposer.Address) - block, err := statefactory.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit)) - require.NoError(t, err) + block := statefactory.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} // no updates: - abciResponses := &tmstate.ABCIResponses{ - FinalizeBlock: &abci.ResponseFinalizeBlock{ - ValidatorUpdates: nil, - }, + fb := &abci.ResponseFinalizeBlock{ + ValidatorUpdates: nil, } - validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.FinalizeBlock.ValidatorUpdates) + validatorUpdates, err := types.PB2TM.ValidatorUpdates(fb.ValidatorUpdates) require.NoError(t, err) - updatedState, err := state.Update(blockID, &block.Header, sm.ABCIResponsesResultsHash(abciResponses), abciResponses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates) + rs, err := abci.MarshalTxResults(fb.TxResults) + require.NoError(t, err) + h := merkle.HashFromByteSlices(rs) + updatedState, err := state.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates) assert.NoError(t, err) // 0 + 10 (initial prio) - 10 (avg) - 10 (mostest - total) = -10 @@ -600,7 +615,10 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateAddVal}) assert.NoError(t, err) - updatedState2, err := updatedState.Update(blockID, &block.Header, sm.ABCIResponsesResultsHash(abciResponses), abciResponses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates) + rs, err = abci.MarshalTxResults(fb.TxResults) + require.NoError(t, err) + h = merkle.HashFromByteSlices(rs) + updatedState2, err := updatedState.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates) assert.NoError(t, err) require.Equal(t, len(updatedState2.NextValidators.Validators), 2) @@ -640,10 +658,13 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { updatedVal2, ) - validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.FinalizeBlock.ValidatorUpdates) + validatorUpdates, err = types.PB2TM.ValidatorUpdates(fb.ValidatorUpdates) require.NoError(t, err) - updatedState3, err := updatedState2.Update(blockID, &block.Header, sm.ABCIResponsesResultsHash(abciResponses), abciResponses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates) + rs, err = abci.MarshalTxResults(fb.TxResults) + require.NoError(t, err) + h = merkle.HashFromByteSlices(rs) + updatedState3, err := updatedState2.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates) assert.NoError(t, err) assert.Equal(t, updatedState3.Validators.Proposer.Address, updatedState3.NextValidators.Proposer.Address) @@ -679,15 +700,16 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { // no changes in voting power and both validators have same voting power // -> proposers should alternate: oldState := updatedState3 - abciResponses = &tmstate.ABCIResponses{ - FinalizeBlock: &abci.ResponseFinalizeBlock{ - ValidatorUpdates: nil, - }, + fb = &abci.ResponseFinalizeBlock{ + ValidatorUpdates: nil, } - validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.FinalizeBlock.ValidatorUpdates) + validatorUpdates, err = types.PB2TM.ValidatorUpdates(fb.ValidatorUpdates) require.NoError(t, err) - oldState, err = oldState.Update(blockID, &block.Header, sm.ABCIResponsesResultsHash(abciResponses), abciResponses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates) + rs, err = abci.MarshalTxResults(fb.TxResults) + require.NoError(t, err) + h = merkle.HashFromByteSlices(rs) + oldState, err = oldState.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates) assert.NoError(t, err) expectedVal1Prio2 = 1 expectedVal2Prio2 = -1 @@ -696,15 +718,16 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { for i := 0; i < 1000; i++ { // no validator updates: - abciResponses := &tmstate.ABCIResponses{ - FinalizeBlock: &abci.ResponseFinalizeBlock{ - ValidatorUpdates: nil, - }, + fb := &abci.ResponseFinalizeBlock{ + ValidatorUpdates: nil, } - validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.FinalizeBlock.ValidatorUpdates) + validatorUpdates, err = types.PB2TM.ValidatorUpdates(fb.ValidatorUpdates) require.NoError(t, err) - updatedState, err := oldState.Update(blockID, &block.Header, sm.ABCIResponsesResultsHash(abciResponses), abciResponses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates) + rs, err := abci.MarshalTxResults(fb.TxResults) + require.NoError(t, err) + h := merkle.HashFromByteSlices(rs) + updatedState, err := oldState.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates) assert.NoError(t, err) // alternate (and cyclic priorities): assert.NotEqual( @@ -755,21 +778,21 @@ func TestLargeGenesisValidator(t *testing.T) { oldState := state for i := 0; i < 10; i++ { // no updates: - abciResponses := &tmstate.ABCIResponses{ - FinalizeBlock: &abci.ResponseFinalizeBlock{ - ValidatorUpdates: nil, - }, + fb := &abci.ResponseFinalizeBlock{ + ValidatorUpdates: nil, } - validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.FinalizeBlock.ValidatorUpdates) + validatorUpdates, err := types.PB2TM.ValidatorUpdates(fb.ValidatorUpdates) require.NoError(t, err) - block, err := statefactory.MakeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit)) - require.NoError(t, err) + block := statefactory.MakeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} - updatedState, err := oldState.Update(blockID, &block.Header, sm.ABCIResponsesResultsHash(abciResponses), abciResponses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates) + rs, err := abci.MarshalTxResults(fb.TxResults) + require.NoError(t, err) + h := merkle.HashFromByteSlices(rs) + updatedState, err := oldState.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates) require.NoError(t, err) // no changes in voting power (ProposerPrio += VotingPower == Voting in 1st round; than shiftByAvg == 0, // than -Total == -Voting) @@ -791,41 +814,41 @@ func TestLargeGenesisValidator(t *testing.T) { firstAddedVal := abci.ValidatorUpdate{PubKey: fvp, Power: firstAddedValVotingPower} validatorUpdates, err := types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{firstAddedVal}) assert.NoError(t, err) - abciResponses := &tmstate.ABCIResponses{ - FinalizeBlock: &abci.ResponseFinalizeBlock{ - ValidatorUpdates: []abci.ValidatorUpdate{firstAddedVal}, - }, + fb := &abci.ResponseFinalizeBlock{ + ValidatorUpdates: []abci.ValidatorUpdate{firstAddedVal}, } - block, err := statefactory.MakeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit)) - require.NoError(t, err) + block := statefactory.MakeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} - updatedState, err := oldState.Update(blockID, &block.Header, sm.ABCIResponsesResultsHash(abciResponses), abciResponses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates) + rs, err := abci.MarshalTxResults(fb.TxResults) + require.NoError(t, err) + h := merkle.HashFromByteSlices(rs) + updatedState, err := oldState.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates) require.NoError(t, err) lastState := updatedState for i := 0; i < 200; i++ { // no updates: - abciResponses := &tmstate.ABCIResponses{ - FinalizeBlock: &abci.ResponseFinalizeBlock{ - ValidatorUpdates: nil, - }, + fb := &abci.ResponseFinalizeBlock{ + ValidatorUpdates: nil, } - validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.FinalizeBlock.ValidatorUpdates) + validatorUpdates, err := types.PB2TM.ValidatorUpdates(fb.ValidatorUpdates) require.NoError(t, err) - block, err := statefactory.MakeBlock(lastState, lastState.LastBlockHeight+1, new(types.Commit)) - require.NoError(t, err) + block := statefactory.MakeBlock(lastState, lastState.LastBlockHeight+1, new(types.Commit)) bps, err = block.MakePartSet(testPartSize) require.NoError(t, err) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} - updatedStateInner, err := lastState.Update(blockID, &block.Header, sm.ABCIResponsesResultsHash(abciResponses), abciResponses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates) + rs, err := abci.MarshalTxResults(fb.TxResults) + require.NoError(t, err) + h := merkle.HashFromByteSlices(rs) + updatedStateInner, err := lastState.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates) require.NoError(t, err) lastState = updatedStateInner } @@ -851,18 +874,18 @@ func TestLargeGenesisValidator(t *testing.T) { validatorUpdates, err := types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{addedVal}) assert.NoError(t, err) - abciResponses := &tmstate.ABCIResponses{ - FinalizeBlock: &abci.ResponseFinalizeBlock{ - ValidatorUpdates: []abci.ValidatorUpdate{addedVal}, - }, + fb := &abci.ResponseFinalizeBlock{ + ValidatorUpdates: []abci.ValidatorUpdate{addedVal}, } - block, err := statefactory.MakeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit)) - require.NoError(t, err) + block := statefactory.MakeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} - state, err = state.Update(blockID, &block.Header, sm.ABCIResponsesResultsHash(abciResponses), abciResponses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates) + rs, err := abci.MarshalTxResults(fb.TxResults) + require.NoError(t, err) + h := merkle.HashFromByteSlices(rs) + state, err = state.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates) require.NoError(t, err) } require.Equal(t, 10+2, len(state.NextValidators.Validators)) @@ -871,22 +894,23 @@ func TestLargeGenesisValidator(t *testing.T) { gp, err := encoding.PubKeyToProto(genesisPubKey) require.NoError(t, err) removeGenesisVal := abci.ValidatorUpdate{PubKey: gp, Power: 0} - abciResponses = &tmstate.ABCIResponses{ - FinalizeBlock: &abci.ResponseFinalizeBlock{ - ValidatorUpdates: []abci.ValidatorUpdate{removeGenesisVal}, - }, + fb = &abci.ResponseFinalizeBlock{ + ValidatorUpdates: []abci.ValidatorUpdate{removeGenesisVal}, } - block, err = statefactory.MakeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit)) + block = statefactory.MakeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit)) require.NoError(t, err) bps, err = block.MakePartSet(testPartSize) require.NoError(t, err) blockID = types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} - validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.FinalizeBlock.ValidatorUpdates) + validatorUpdates, err = types.PB2TM.ValidatorUpdates(fb.ValidatorUpdates) require.NoError(t, err) - updatedState, err = state.Update(blockID, &block.Header, sm.ABCIResponsesResultsHash(abciResponses), abciResponses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates) + rs, err = abci.MarshalTxResults(fb.TxResults) + require.NoError(t, err) + h = merkle.HashFromByteSlices(rs) + updatedState, err = state.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates) require.NoError(t, err) // only the first added val (not the genesis val) should be left assert.Equal(t, 11, len(updatedState.NextValidators.Validators)) @@ -897,21 +921,21 @@ func TestLargeGenesisValidator(t *testing.T) { count := 0 isProposerUnchanged := true for isProposerUnchanged { - abciResponses := &tmstate.ABCIResponses{ - FinalizeBlock: &abci.ResponseFinalizeBlock{ - ValidatorUpdates: nil, - }, + fb = &abci.ResponseFinalizeBlock{ + ValidatorUpdates: nil, } - validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.FinalizeBlock.ValidatorUpdates) - require.NoError(t, err) - block, err = statefactory.MakeBlock(curState, curState.LastBlockHeight+1, new(types.Commit)) + validatorUpdates, err = types.PB2TM.ValidatorUpdates(fb.ValidatorUpdates) require.NoError(t, err) + block = statefactory.MakeBlock(curState, curState.LastBlockHeight+1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID = types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} - curState, err = curState.Update(blockID, &block.Header, sm.ABCIResponsesResultsHash(abciResponses), abciResponses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates) + rs, err := abci.MarshalTxResults(fb.TxResults) + require.NoError(t, err) + h := merkle.HashFromByteSlices(rs) + curState, err = curState.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates) require.NoError(t, err) if !bytes.Equal(curState.Validators.Proposer.Address, curState.NextValidators.Proposer.Address) { isProposerUnchanged = false @@ -927,23 +951,23 @@ func TestLargeGenesisValidator(t *testing.T) { proposers := make([]*types.Validator, numVals) for i := 0; i < 100; i++ { // no updates: - abciResponses := &tmstate.ABCIResponses{ - FinalizeBlock: &abci.ResponseFinalizeBlock{ - ValidatorUpdates: nil, - }, + fb := &abci.ResponseFinalizeBlock{ + ValidatorUpdates: nil, } - validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.FinalizeBlock.ValidatorUpdates) + validatorUpdates, err := types.PB2TM.ValidatorUpdates(fb.ValidatorUpdates) require.NoError(t, err) - block, err := statefactory.MakeBlock(updatedState, updatedState.LastBlockHeight+1, new(types.Commit)) - require.NoError(t, err) + block := statefactory.MakeBlock(updatedState, updatedState.LastBlockHeight+1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} - updatedState, err = updatedState.Update(blockID, &block.Header, sm.ABCIResponsesResultsHash(abciResponses), abciResponses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates) + rs, err := abci.MarshalTxResults(fb.TxResults) + require.NoError(t, err) + h := merkle.HashFromByteSlices(rs) + updatedState, err = updatedState.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates) require.NoError(t, err) if i > numVals { // expect proposers to cycle through after the first iteration (of numVals blocks): if proposers[i%numVals] == nil { @@ -1002,7 +1026,10 @@ func TestManyValidatorChangesSaveLoad(t *testing.T) { var validatorUpdates []*types.Validator validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.FinalizeBlock.ValidatorUpdates) require.NoError(t, err) - state, err = state.Update(blockID, &header, sm.ABCIResponsesResultsHash(responses), responses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates) + rs, err := abci.MarshalTxResults(responses.FinalizeBlock.TxResults) + require.NoError(t, err) + h := merkle.HashFromByteSlices(rs) + state, err = state.Update(blockID, &header, h, responses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates) require.NoError(t, err) nextHeight := state.LastBlockHeight + 1 err = stateStore.Save(state) @@ -1035,8 +1062,7 @@ func TestStateMakeBlock(t *testing.T) { proposerAddress := state.Validators.GetProposer().Address stateVersion := state.Version.Consensus - block, err := statefactory.MakeBlock(state, 2, new(types.Commit)) - require.NoError(t, err) + block := statefactory.MakeBlock(state, 2, new(types.Commit)) // test we set some fields assert.Equal(t, stateVersion, block.Version) @@ -1080,10 +1106,13 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) { header, blockID, responses := makeHeaderPartsResponsesParams(t, state, &cp) validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.FinalizeBlock.ValidatorUpdates) require.NoError(t, err) - state, err = state.Update(blockID, &header, sm.ABCIResponsesResultsHash(responses), responses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates) + rs, err := abci.MarshalTxResults(responses.FinalizeBlock.TxResults) + require.NoError(t, err) + h := merkle.HashFromByteSlices(rs) + state, err = state.Update(blockID, &header, h, responses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates) require.NoError(t, err) - err := stateStore.Save(state) + err = stateStore.Save(state) require.NoError(t, err) } diff --git a/internal/state/store.go b/internal/state/store.go index dbde5e0d1..87f5e0c4f 100644 --- a/internal/state/store.go +++ b/internal/state/store.go @@ -170,7 +170,12 @@ func (store dbStore) save(state State, key []byte) error { return err } - if err := batch.Set(key, state.Bytes()); err != nil { + stateBz, err := state.Bytes() + if err != nil { + return err + } + + if err := batch.Set(key, stateBz); err != nil { return err } @@ -206,7 +211,12 @@ func (store dbStore) Bootstrap(state State) error { return err } - if err := batch.Set(stateKey, state.Bytes()); err != nil { + stateBz, err := state.Bytes() + if err != nil { + return err + } + + if err := batch.Set(stateKey, stateBz); err != nil { return err } @@ -396,14 +406,6 @@ func (store dbStore) reverseBatchDelete(batch dbm.Batch, start, end []byte) ([]b //------------------------------------------------------------------------ -// ABCIResponsesResultsHash returns the root hash of a Merkle tree of -// ResponseDeliverTx responses (see ABCIResults.Hash) -// -// See merkle.SimpleHashFromByteSlices -func ABCIResponsesResultsHash(ar *tmstate.ABCIResponses) []byte { - return types.NewResults(ar.FinalizeBlock.TxResults).Hash() -} - // LoadABCIResponses loads the ABCIResponses for the given height from the // database. If not found, ErrNoABCIResponsesForHeight is returned. // @@ -486,7 +488,7 @@ func (store dbStore) LoadValidators(height int64) (*types.ValidatorSet, error) { valInfo, err := loadValidatorsInfo(store.db, height) if err != nil { - return nil, ErrNoValSetForHeight{height} + return nil, ErrNoValSetForHeight{Height: height, Err: err} } if valInfo.ValidatorSet == nil { lastStoredHeight := lastStoredHeightFor(height, valInfo.LastHeightChanged) diff --git a/internal/state/store_test.go b/internal/state/store_test.go index 0134d5987..59084fd10 100644 --- a/internal/state/store_test.go +++ b/internal/state/store_test.go @@ -7,7 +7,6 @@ import ( "os" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" dbm "github.com/tendermint/tm-db" @@ -299,25 +298,3 @@ func TestPruneStates(t *testing.T) { }) } } - -func TestABCIResponsesResultsHash(t *testing.T) { - responses := &tmstate.ABCIResponses{ - FinalizeBlock: &abci.ResponseFinalizeBlock{ - TxResults: []*abci.ExecTxResult{ - {Code: 32, Data: []byte("Hello"), Log: "Huh?"}, - }, - }, - } - - root := sm.ABCIResponsesResultsHash(responses) - - // root should be Merkle tree root of FinalizeBlock tx responses - results := types.NewResults(responses.FinalizeBlock.TxResults) - assert.Equal(t, root, results.Hash()) - - // test we can prove first tx in FinalizeBlock - proof := results.ProveResult(0) - bz, err := results[0].Marshal() - require.NoError(t, err) - assert.NoError(t, proof.Verify(root, bz)) -} diff --git a/internal/state/test/factory/block.go b/internal/state/test/factory/block.go index 5154d170a..1b3351363 100644 --- a/internal/state/test/factory/block.go +++ b/internal/state/test/factory/block.go @@ -42,19 +42,14 @@ func MakeBlocks(ctx context.Context, t *testing.T, n int, state *sm.State, privV return blocks } -func MakeBlock(state sm.State, height int64, c *types.Commit) (*types.Block, error) { - block, _, err := state.MakeBlock( +func MakeBlock(state sm.State, height int64, c *types.Commit) *types.Block { + return state.MakeBlock( height, - factory.MakeTenTxs(state.LastBlockHeight), + factory.MakeNTxs(state.LastBlockHeight, 10), c, nil, state.Validators.GetProposer().Address, ) - if err != nil { - return nil, err - } - - return block, nil } func makeBlockAndPartSet( @@ -82,7 +77,8 @@ func makeBlockAndPartSet( lastBlockMeta.BlockID, []types.CommitSig{vote.CommitSig()}) } - block, partSet, err := state.MakeBlock(height, []types.Tx{}, lastCommit, nil, state.Validators.GetProposer().Address) + block := state.MakeBlock(height, []types.Tx{}, lastCommit, nil, state.Validators.GetProposer().Address) + partSet, err := block.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) return block, partSet diff --git a/internal/state/time.go b/internal/state/time.go deleted file mode 100644 index c0770b3af..000000000 --- a/internal/state/time.go +++ /dev/null @@ -1,46 +0,0 @@ -package state - -import ( - "sort" - "time" -) - -// weightedTime for computing a median. -type weightedTime struct { - Time time.Time - Weight int64 -} - -// newWeightedTime with time and weight. -func newWeightedTime(time time.Time, weight int64) *weightedTime { - return &weightedTime{ - Time: time, - Weight: weight, - } -} - -// weightedMedian computes weighted median time for a given array of WeightedTime and the total voting power. -func weightedMedian(weightedTimes []*weightedTime, totalVotingPower int64) (res time.Time) { - median := totalVotingPower / 2 - - sort.Slice(weightedTimes, func(i, j int) bool { - if weightedTimes[i] == nil { - return false - } - if weightedTimes[j] == nil { - return true - } - return weightedTimes[i].Time.UnixNano() < weightedTimes[j].Time.UnixNano() - }) - - for _, weightedTime := range weightedTimes { - if weightedTime != nil { - if median <= weightedTime.Weight { - res = weightedTime.Time - break - } - median -= weightedTime.Weight - } - } - return -} diff --git a/internal/state/time_test.go b/internal/state/time_test.go deleted file mode 100644 index 5da97e819..000000000 --- a/internal/state/time_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package state - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" - - tmtime "github.com/tendermint/tendermint/libs/time" -) - -func TestWeightedMedian(t *testing.T) { - m := make([]*weightedTime, 3) - - t1 := tmtime.Now() - t2 := t1.Add(5 * time.Second) - t3 := t1.Add(10 * time.Second) - - m[2] = newWeightedTime(t1, 33) // faulty processes - m[0] = newWeightedTime(t2, 40) // correct processes - m[1] = newWeightedTime(t3, 27) // correct processes - totalVotingPower := int64(100) - - median := weightedMedian(m, totalVotingPower) - assert.Equal(t, t2, median) - // median always returns value between values of correct processes - assert.Equal(t, true, (median.After(t1) || median.Equal(t1)) && - (median.Before(t3) || median.Equal(t3))) - - m[1] = newWeightedTime(t1, 40) // correct processes - m[2] = newWeightedTime(t2, 27) // correct processes - m[0] = newWeightedTime(t3, 33) // faulty processes - totalVotingPower = int64(100) - - median = weightedMedian(m, totalVotingPower) - assert.Equal(t, t2, median) - // median always returns value between values of correct processes - assert.Equal(t, true, (median.After(t1) || median.Equal(t1)) && - (median.Before(t2) || median.Equal(t2))) - - m = make([]*weightedTime, 8) - t4 := t1.Add(15 * time.Second) - t5 := t1.Add(60 * time.Second) - - m[3] = newWeightedTime(t1, 10) // correct processes - m[1] = newWeightedTime(t2, 10) // correct processes - m[5] = newWeightedTime(t2, 10) // correct processes - m[4] = newWeightedTime(t3, 23) // faulty processes - m[0] = newWeightedTime(t4, 20) // correct processes - m[7] = newWeightedTime(t5, 10) // faulty processes - totalVotingPower = int64(83) - - median = weightedMedian(m, totalVotingPower) - assert.Equal(t, t3, median) - // median always returns value between values of correct processes - assert.Equal(t, true, (median.After(t1) || median.Equal(t1)) && - (median.Before(t4) || median.Equal(t4))) -} diff --git a/internal/state/validation_test.go b/internal/state/validation_test.go index d24b5098a..376ce61bc 100644 --- a/internal/state/validation_test.go +++ b/internal/state/validation_test.go @@ -12,9 +12,10 @@ import ( abciclient "github.com/tendermint/tendermint/abci/client" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/tmhash" - memmock "github.com/tendermint/tendermint/internal/mempool/mock" + "github.com/tendermint/tendermint/internal/eventbus" + mpmocks "github.com/tendermint/tendermint/internal/mempool/mocks" "github.com/tendermint/tendermint/internal/proxy" sm "github.com/tendermint/tendermint/internal/state" "github.com/tendermint/tendermint/internal/state/mocks" @@ -32,25 +33,42 @@ const validationTestsStopHeight int64 = 10 func TestValidateBlockHeader(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.TestingLogger() + logger := log.NewNopLogger() proxyApp := proxy.New(abciclient.NewLocalClient(logger, &testApp{}), logger, proxy.NopMetrics()) require.NoError(t, proxyApp.Start(ctx)) + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) + state, stateDB, privVals := makeState(t, 3, 1) stateStore := sm.NewStore(stateDB) + mp := &mpmocks.Mempool{} + mp.On("Lock").Return() + mp.On("Unlock").Return() + mp.On("FlushAppConn", mock.Anything).Return(nil) + mp.On("Update", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything).Return(nil) + blockStore := store.NewBlockStore(dbm.NewMemDB()) blockExec := sm.NewBlockExecutor( stateStore, logger, proxyApp, - memmock.Mempool{}, + mp, sm.EmptyEvidencePool{}, blockStore, + eventBus, + sm.NopMetrics(), ) lastCommit := types.NewCommit(0, 0, types.BlockID{}, nil) // some bad values - wrongHash := tmhash.Sum([]byte("this hash is wrong")) + wrongHash := crypto.Checksum([]byte("this hash is wrong")) wrongVersion1 := state.Version.Consensus wrongVersion1.Block += 2 wrongVersion2 := state.Version.Consensus @@ -93,10 +111,9 @@ func TestValidateBlockHeader(t *testing.T) { Invalid blocks don't pass */ for _, tc := range testCases { - block, err := statefactory.MakeBlock(state, height, lastCommit) - require.NoError(t, err) + block := statefactory.MakeBlock(state, height, lastCommit) tc.malleateBlock(block) - err = blockExec.ValidateBlock(ctx, state, block) + err := blockExec.ValidateBlock(ctx, state, block) t.Logf("%s: %v", tc.name, err) require.Error(t, err, tc.name) } @@ -109,10 +126,9 @@ func TestValidateBlockHeader(t *testing.T) { } nextHeight := validationTestsStopHeight - block, err := statefactory.MakeBlock(state, nextHeight, lastCommit) - require.NoError(t, err) + block := statefactory.MakeBlock(state, nextHeight, lastCommit) state.InitialHeight = nextHeight + 1 - err = blockExec.ValidateBlock(ctx, state, block) + err := blockExec.ValidateBlock(ctx, state, block) require.Error(t, err, "expected an error when state is ahead of block") assert.Contains(t, err.Error(), "lower than initial height") } @@ -121,20 +137,37 @@ func TestValidateBlockCommit(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.TestingLogger() + logger := log.NewNopLogger() proxyApp := proxy.New(abciclient.NewLocalClient(logger, &testApp{}), logger, proxy.NopMetrics()) require.NoError(t, proxyApp.Start(ctx)) + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) + state, stateDB, privVals := makeState(t, 1, 1) stateStore := sm.NewStore(stateDB) + mp := &mpmocks.Mempool{} + mp.On("Lock").Return() + mp.On("Unlock").Return() + mp.On("FlushAppConn", mock.Anything).Return(nil) + mp.On("Update", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything).Return(nil) + blockStore := store.NewBlockStore(dbm.NewMemDB()) blockExec := sm.NewBlockExecutor( stateStore, logger, proxyApp, - memmock.Mempool{}, + mp, sm.EmptyEvidencePool{}, blockStore, + eventBus, + sm.NopMetrics(), ) lastCommit := types.NewCommit(0, 0, types.BlockID{}, nil) wrongSigsCommit := types.NewCommit(1, 0, types.BlockID{}, nil) @@ -165,8 +198,7 @@ func TestValidateBlockCommit(t *testing.T) { state.LastBlockID, []types.CommitSig{wrongHeightVote.CommitSig()}, ) - block, err := statefactory.MakeBlock(state, height, wrongHeightCommit) - require.NoError(t, err) + block := statefactory.MakeBlock(state, height, wrongHeightCommit) err = blockExec.ValidateBlock(ctx, state, block) _, isErrInvalidCommitHeight := err.(types.ErrInvalidCommitHeight) require.True(t, isErrInvalidCommitHeight, "expected ErrInvalidCommitHeight at height %d but got: %v", height, err) @@ -174,8 +206,7 @@ func TestValidateBlockCommit(t *testing.T) { /* #2589: test len(block.LastCommit.Signatures) == state.LastValidators.Size() */ - block, err = statefactory.MakeBlock(state, height, wrongSigsCommit) - require.NoError(t, err) + block = statefactory.MakeBlock(state, height, wrongSigsCommit) err = blockExec.ValidateBlock(ctx, state, block) _, isErrInvalidCommitSignatures := err.(types.ErrInvalidCommitSignatures) require.True(t, isErrInvalidCommitSignatures, @@ -248,7 +279,7 @@ func TestValidateBlockEvidence(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.TestingLogger() + logger := log.NewNopLogger() proxyApp := proxy.New(abciclient.NewLocalClient(logger, &testApp{}), logger, proxy.NopMetrics()) require.NoError(t, proxyApp.Start(ctx)) @@ -261,16 +292,32 @@ func TestValidateBlockEvidence(t *testing.T) { evpool.On("CheckEvidence", ctx, mock.AnythingOfType("types.EvidenceList")).Return(nil) evpool.On("Update", ctx, mock.AnythingOfType("state.State"), mock.AnythingOfType("types.EvidenceList")).Return() evpool.On("ABCIEvidence", mock.AnythingOfType("int64"), mock.AnythingOfType("[]types.Evidence")).Return( - []abci.Evidence{}) + []abci.Misbehavior{}) + + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) + mp := &mpmocks.Mempool{} + mp.On("Lock").Return() + mp.On("Unlock").Return() + mp.On("FlushAppConn", mock.Anything).Return(nil) + mp.On("Update", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything).Return(nil) state.ConsensusParams.Evidence.MaxBytes = 1000 blockExec := sm.NewBlockExecutor( stateStore, - log.TestingLogger(), + log.NewNopLogger(), proxyApp, - memmock.Mempool{}, + mp, evpool, blockStore, + eventBus, + sm.NopMetrics(), ) lastCommit := types.NewCommit(0, 0, types.BlockID{}, nil) @@ -291,10 +338,9 @@ func TestValidateBlockEvidence(t *testing.T) { evidence = append(evidence, newEv) currentBytes += int64(len(newEv.Bytes())) } - block, _, err := state.MakeBlock(height, testfactory.MakeTenTxs(height), lastCommit, evidence, proposerAddr) - require.NoError(t, err) + block := state.MakeBlock(height, testfactory.MakeNTxs(height, 10), lastCommit, evidence, proposerAddr) - err = blockExec.ValidateBlock(ctx, state, block) + err := blockExec.ValidateBlock(ctx, state, block) if assert.Error(t, err) { _, ok := err.(*types.ErrEvidenceOverflow) require.True(t, ok, "expected error to be of type ErrEvidenceOverflow at height %d but got %v", height, err) diff --git a/internal/statesync/dispatcher_test.go b/internal/statesync/dispatcher_test.go index 65c517be4..8ec074bd1 100644 --- a/internal/statesync/dispatcher_test.go +++ b/internal/statesync/dispatcher_test.go @@ -30,7 +30,7 @@ func testChannel(size int) (*channelInternal, *p2p.Channel) { Out: make(chan p2p.Envelope, size), Error: make(chan p2p.PeerError, size), } - return in, p2p.NewChannel(0, nil, in.In, in.Out, in.Error) + return in, p2p.NewChannel(0, in.In, in.Out, in.Error) } func TestDispatcherBasic(t *testing.T) { diff --git a/internal/statesync/mocks/state_provider.go b/internal/statesync/mocks/state_provider.go index b8d681631..582ebcd9c 100644 --- a/internal/statesync/mocks/state_provider.go +++ b/internal/statesync/mocks/state_provider.go @@ -8,6 +8,8 @@ import ( mock "github.com/stretchr/testify/mock" state "github.com/tendermint/tendermint/internal/state" + testing "testing" + types "github.com/tendermint/tendermint/types" ) @@ -82,3 +84,13 @@ func (_m *StateProvider) State(ctx context.Context, height uint64) (state.State, return r0, r1 } + +// NewStateProvider creates a new instance of StateProvider. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewStateProvider(t testing.TB) *StateProvider { + mock := &StateProvider{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/statesync/reactor.go b/internal/statesync/reactor.go index 51f626027..f4d72d017 100644 --- a/internal/statesync/reactor.go +++ b/internal/statesync/reactor.go @@ -75,13 +75,13 @@ const ( func getChannelDescriptors() map[p2p.ChannelID]*p2p.ChannelDescriptor { return map[p2p.ChannelID]*p2p.ChannelDescriptor{ SnapshotChannel: { - ID: SnapshotChannel, MessageType: new(ssproto.Message), Priority: 6, SendQueueCapacity: 10, RecvMessageCapacity: snapshotMsgSize, RecvBufferCapacity: 128, + Name: "snapshot", }, ChunkChannel: { ID: ChunkChannel, @@ -90,6 +90,7 @@ func getChannelDescriptors() map[p2p.ChannelID]*p2p.ChannelDescriptor { SendQueueCapacity: 4, RecvMessageCapacity: chunkMsgSize, RecvBufferCapacity: 128, + Name: "chunk", }, LightBlockChannel: { ID: LightBlockChannel, @@ -98,6 +99,7 @@ func getChannelDescriptors() map[p2p.ChannelID]*p2p.ChannelDescriptor { SendQueueCapacity: 10, RecvMessageCapacity: lightBlockMsgSize, RecvBufferCapacity: 128, + Name: "light-block", }, ParamsChannel: { ID: ParamsChannel, @@ -106,6 +108,7 @@ func getChannelDescriptors() map[p2p.ChannelID]*p2p.ChannelDescriptor { SendQueueCapacity: 10, RecvMessageCapacity: paramMsgSize, RecvBufferCapacity: 128, + Name: "params", }, } @@ -135,13 +138,17 @@ type Reactor struct { stateStore sm.Store blockStore *store.BlockStore - conn abciclient.Client - tempDir string - snapshotCh *p2p.Channel - chunkCh *p2p.Channel - blockCh *p2p.Channel - paramsCh *p2p.Channel - peerUpdates *p2p.PeerUpdates + conn abciclient.Client + tempDir string + peerEvents p2p.PeerEventSubscriber + chCreator p2p.ChannelCreator + sendBlockError func(context.Context, p2p.PeerError) error + postSyncHook func(context.Context, sm.State) error + + // when true, the reactor will, during startup perform a + // statesync for this node, and otherwise just provide + // snapshots to other nodes. + needsStateSync bool // Dispatcher is used to multiplex light block requests and responses over multiple // peers used by the p2p state provider and in reverse sync. @@ -151,10 +158,13 @@ type Reactor struct { // These will only be set when a state sync is in progress. It is used to feed // received snapshots and chunks into the syncer and manage incoming and outgoing // providers. - mtx sync.RWMutex - syncer *syncer - providers map[types.NodeID]*BlockProvider - stateProvider StateProvider + mtx sync.RWMutex + initSyncer func() *syncer + requestSnaphot func() error + syncer *syncer + providers map[types.NodeID]*BlockProvider + initStateProvider func(ctx context.Context, chainID string, initialHeight int64) error + stateProvider StateProvider eventBus *eventbus.EventBus metrics *Metrics @@ -167,63 +177,42 @@ type Reactor struct { // and querying, references to p2p Channels and a channel to listen for peer // updates on. Note, the reactor will close all p2p Channels when stopping. func NewReactor( - ctx context.Context, chainID string, initialHeight int64, cfg config.StateSyncConfig, logger log.Logger, conn abciclient.Client, channelCreator p2p.ChannelCreator, - peerUpdates *p2p.PeerUpdates, + peerEvents p2p.PeerEventSubscriber, stateStore sm.Store, blockStore *store.BlockStore, tempDir string, ssMetrics *Metrics, eventBus *eventbus.EventBus, -) (*Reactor, error) { - - chDesc := getChannelDescriptors() - - snapshotCh, err := channelCreator(ctx, chDesc[SnapshotChannel]) - if err != nil { - return nil, err - } - chunkCh, err := channelCreator(ctx, chDesc[ChunkChannel]) - if err != nil { - return nil, err - } - blockCh, err := channelCreator(ctx, chDesc[LightBlockChannel]) - if err != nil { - return nil, err - } - paramsCh, err := channelCreator(ctx, chDesc[ParamsChannel]) - if err != nil { - return nil, err - } - + postSyncHook func(context.Context, sm.State) error, + needsStateSync bool, +) *Reactor { r := &Reactor{ - logger: logger, - chainID: chainID, - initialHeight: initialHeight, - cfg: cfg, - conn: conn, - snapshotCh: snapshotCh, - chunkCh: chunkCh, - blockCh: blockCh, - paramsCh: paramsCh, - peerUpdates: peerUpdates, - tempDir: tempDir, - stateStore: stateStore, - blockStore: blockStore, - peers: newPeerList(), - dispatcher: NewDispatcher(blockCh), - providers: make(map[types.NodeID]*BlockProvider), - metrics: ssMetrics, - eventBus: eventBus, + logger: logger, + chainID: chainID, + initialHeight: initialHeight, + cfg: cfg, + conn: conn, + chCreator: channelCreator, + peerEvents: peerEvents, + tempDir: tempDir, + stateStore: stateStore, + blockStore: blockStore, + peers: newPeerList(), + providers: make(map[types.NodeID]*BlockProvider), + metrics: ssMetrics, + eventBus: eventBus, + postSyncHook: postSyncHook, + needsStateSync: needsStateSync, } r.BaseService = *service.NewBaseService(logger, "StateSync", r) - return r, nil + return r } // OnStart starts separate go routines for each p2p Channel and listens for @@ -233,11 +222,104 @@ func NewReactor( // The caller must be sure to execute OnStop to ensure the outbound p2p Channels are // closed. No error is returned. func (r *Reactor) OnStart(ctx context.Context) error { - go r.processCh(ctx, r.snapshotCh, "snapshot") - go r.processCh(ctx, r.chunkCh, "chunk") - go r.processCh(ctx, r.blockCh, "light block") - go r.processCh(ctx, r.paramsCh, "consensus params") - go r.processPeerUpdates(ctx) + // construct channels + chDesc := getChannelDescriptors() + snapshotCh, err := r.chCreator(ctx, chDesc[SnapshotChannel]) + if err != nil { + return err + } + chunkCh, err := r.chCreator(ctx, chDesc[ChunkChannel]) + if err != nil { + return err + } + blockCh, err := r.chCreator(ctx, chDesc[LightBlockChannel]) + if err != nil { + return err + } + paramsCh, err := r.chCreator(ctx, chDesc[ParamsChannel]) + if err != nil { + return err + } + + // define constructor and helper functions, that hold + // references to these channels for use later. This is not + // ideal. + r.initSyncer = func() *syncer { + return &syncer{ + logger: r.logger, + stateProvider: r.stateProvider, + conn: r.conn, + snapshots: newSnapshotPool(), + snapshotCh: snapshotCh, + chunkCh: chunkCh, + tempDir: r.tempDir, + fetchers: r.cfg.Fetchers, + retryTimeout: r.cfg.ChunkRequestTimeout, + metrics: r.metrics, + } + } + r.dispatcher = NewDispatcher(blockCh) + r.requestSnaphot = func() error { + // request snapshots from all currently connected peers + return snapshotCh.Send(ctx, p2p.Envelope{ + Broadcast: true, + Message: &ssproto.SnapshotsRequest{}, + }) + } + r.sendBlockError = blockCh.SendError + + r.initStateProvider = func(ctx context.Context, chainID string, initialHeight int64) error { + to := light.TrustOptions{ + Period: r.cfg.TrustPeriod, + Height: r.cfg.TrustHeight, + Hash: r.cfg.TrustHashBytes(), + } + spLogger := r.logger.With("module", "stateprovider") + spLogger.Info("initializing state provider", "trustPeriod", to.Period, + "trustHeight", to.Height, "useP2P", r.cfg.UseP2P) + + if r.cfg.UseP2P { + if err := r.waitForEnoughPeers(ctx, 2); err != nil { + return err + } + + peers := r.peers.All() + providers := make([]provider.Provider, len(peers)) + for idx, p := range peers { + providers[idx] = NewBlockProvider(p, chainID, r.dispatcher) + } + + stateProvider, err := NewP2PStateProvider(ctx, chainID, initialHeight, providers, to, paramsCh, r.logger.With("module", "stateprovider")) + if err != nil { + return fmt.Errorf("failed to initialize P2P state provider: %w", err) + } + r.stateProvider = stateProvider + return nil + } + + stateProvider, err := NewRPCStateProvider(ctx, chainID, initialHeight, r.cfg.RPCServers, to, spLogger) + if err != nil { + return fmt.Errorf("failed to initialize RPC state provider: %w", err) + } + r.stateProvider = stateProvider + return nil + } + + go r.processChannels(ctx, map[p2p.ChannelID]*p2p.Channel{ + SnapshotChannel: snapshotCh, + ChunkChannel: chunkCh, + LightBlockChannel: blockCh, + ParamsChannel: paramsCh, + }) + go r.processPeerUpdates(ctx, r.peerEvents(ctx)) + + if r.needsStateSync { + r.logger.Info("starting state sync") + if _, err := r.Sync(ctx); err != nil { + r.logger.Error("state sync failed; shutting down this node", "err", err) + return err + } + } return nil } @@ -249,20 +331,21 @@ func (r *Reactor) OnStop() { r.dispatcher.Close() } -func (r *Reactor) PublishStatus(ctx context.Context, event types.EventDataStateSyncStatus) error { - if r.eventBus == nil { - return errors.New("event system is not configured") - } - - return r.eventBus.PublishEventStateSyncStatus(ctx, event) -} - // Sync runs a state sync, fetching snapshots and providing chunks to the // application. At the close of the operation, Sync will bootstrap the state // store and persist the commit at that height so that either consensus or // blocksync can commence. It will then proceed to backfill the necessary amount // of historical blocks before participating in consensus func (r *Reactor) Sync(ctx context.Context) (sm.State, error) { + if r.eventBus != nil { + if err := r.eventBus.PublishEventStateSyncStatus(types.EventDataStateSyncStatus{ + Complete: false, + Height: r.initialHeight, + }); err != nil { + return sm.State{}, err + } + } + // We need at least two peers (for cross-referencing of light blocks) before we can // begin state sync if err := r.waitForEnoughPeers(ctx, 2); err != nil { @@ -280,17 +363,9 @@ func (r *Reactor) Sync(ctx context.Context) (sm.State, error) { return sm.State{}, err } - r.syncer = newSyncer( - r.cfg, - r.logger, - r.conn, - r.stateProvider, - r.snapshotCh, - r.chunkCh, - r.tempDir, - r.metrics, - ) + r.syncer = r.initSyncer() r.mtx.Unlock() + defer func() { r.mtx.Lock() // reset syncing objects at the close of Sync @@ -299,34 +374,38 @@ func (r *Reactor) Sync(ctx context.Context) (sm.State, error) { r.mtx.Unlock() }() - requestSnapshotsHook := func() error { - // request snapshots from all currently connected peers - return r.snapshotCh.Send(ctx, p2p.Envelope{ - Broadcast: true, - Message: &ssproto.SnapshotsRequest{}, - }) - } - - state, commit, err := r.syncer.SyncAny(ctx, r.cfg.DiscoveryTime, requestSnapshotsHook) + state, commit, err := r.syncer.SyncAny(ctx, r.cfg.DiscoveryTime, r.requestSnaphot) if err != nil { return sm.State{}, err } - err = r.stateStore.Bootstrap(state) - if err != nil { + if err := r.stateStore.Bootstrap(state); err != nil { return sm.State{}, fmt.Errorf("failed to bootstrap node with new state: %w", err) } - err = r.blockStore.SaveSeenCommit(state.LastBlockHeight, commit) - if err != nil { + if err := r.blockStore.SaveSeenCommit(state.LastBlockHeight, commit); err != nil { return sm.State{}, fmt.Errorf("failed to store last seen commit: %w", err) } - err = r.Backfill(ctx, state) - if err != nil { + if err := r.Backfill(ctx, state); err != nil { r.logger.Error("backfill failed. Proceeding optimistically...", "err", err) } + if r.eventBus != nil { + if err := r.eventBus.PublishEventStateSyncStatus(types.EventDataStateSyncStatus{ + Complete: true, + Height: state.LastBlockHeight, + }); err != nil { + return sm.State{}, err + } + } + + if r.postSyncHook != nil { + if err := r.postSyncHook(ctx, state); err != nil { + return sm.State{}, err + } + } + return state, nil } @@ -434,7 +513,7 @@ func (r *Reactor) backfill( r.logger.Info("backfill: fetched light block failed validate basic, removing peer...", "err", err, "height", height) queue.retry(height) - if serr := r.blockCh.SendError(ctx, p2p.PeerError{ + if serr := r.sendBlockError(ctx, p2p.PeerError{ NodeID: peer, Err: fmt.Errorf("received invalid light block: %w", err), }); serr != nil { @@ -471,7 +550,7 @@ func (r *Reactor) backfill( if w, g := trustedBlockID.Hash, resp.block.Hash(); !bytes.Equal(w, g) { r.logger.Info("received invalid light block. header hash doesn't match trusted LastBlockID", "trustedHash", w, "receivedHash", g, "height", resp.block.Height) - if err := r.blockCh.SendError(ctx, p2p.PeerError{ + if err := r.sendBlockError(ctx, p2p.PeerError{ NodeID: resp.peer, Err: fmt.Errorf("received invalid light block. Expected hash %v, got: %v", w, g), }); err != nil { @@ -532,7 +611,7 @@ func (r *Reactor) backfill( // handleSnapshotMessage handles envelopes sent from peers on the // SnapshotChannel. It returns an error only if the Envelope.Message is unknown // for this channel. This should never be called outside of handleMessage. -func (r *Reactor) handleSnapshotMessage(ctx context.Context, envelope *p2p.Envelope) error { +func (r *Reactor) handleSnapshotMessage(ctx context.Context, envelope *p2p.Envelope, snapshotCh *p2p.Channel) error { logger := r.logger.With("peer", envelope.From) switch msg := envelope.Message.(type) { @@ -551,7 +630,7 @@ func (r *Reactor) handleSnapshotMessage(ctx context.Context, envelope *p2p.Envel "peer", envelope.From, ) - if err := r.snapshotCh.Send(ctx, p2p.Envelope{ + if err := snapshotCh.Send(ctx, p2p.Envelope{ To: envelope.From, Message: &ssproto.SnapshotsResponse{ Height: snapshot.Height, @@ -587,8 +666,8 @@ func (r *Reactor) handleSnapshotMessage(ctx context.Context, envelope *p2p.Envel "failed to add snapshot", "height", msg.Height, "format", msg.Format, + "channel", envelope.ChannelID, "err", err, - "channel", r.snapshotCh.ID, ) return nil } @@ -604,7 +683,7 @@ func (r *Reactor) handleSnapshotMessage(ctx context.Context, envelope *p2p.Envel // handleChunkMessage handles envelopes sent from peers on the ChunkChannel. // It returns an error only if the Envelope.Message is unknown for this channel. // This should never be called outside of handleMessage. -func (r *Reactor) handleChunkMessage(ctx context.Context, envelope *p2p.Envelope) error { +func (r *Reactor) handleChunkMessage(ctx context.Context, envelope *p2p.Envelope, chunkCh *p2p.Channel) error { switch msg := envelope.Message.(type) { case *ssproto.ChunkRequest: r.logger.Debug( @@ -614,7 +693,7 @@ func (r *Reactor) handleChunkMessage(ctx context.Context, envelope *p2p.Envelope "chunk", msg.Index, "peer", envelope.From, ) - resp, err := r.conn.LoadSnapshotChunk(ctx, abci.RequestLoadSnapshotChunk{ + resp, err := r.conn.LoadSnapshotChunk(ctx, &abci.RequestLoadSnapshotChunk{ Height: msg.Height, Format: msg.Format, Chunk: msg.Index, @@ -638,7 +717,7 @@ func (r *Reactor) handleChunkMessage(ctx context.Context, envelope *p2p.Envelope "chunk", msg.Index, "peer", envelope.From, ) - if err := r.chunkCh.Send(ctx, p2p.Envelope{ + if err := chunkCh.Send(ctx, p2p.Envelope{ To: envelope.From, Message: &ssproto.ChunkResponse{ Height: msg.Height, @@ -693,7 +772,7 @@ func (r *Reactor) handleChunkMessage(ctx context.Context, envelope *p2p.Envelope return nil } -func (r *Reactor) handleLightBlockMessage(ctx context.Context, envelope *p2p.Envelope) error { +func (r *Reactor) handleLightBlockMessage(ctx context.Context, envelope *p2p.Envelope, blockCh *p2p.Channel) error { switch msg := envelope.Message.(type) { case *ssproto.LightBlockRequest: r.logger.Info("received light block request", "height", msg.Height) @@ -703,7 +782,7 @@ func (r *Reactor) handleLightBlockMessage(ctx context.Context, envelope *p2p.Env return err } if lb == nil { - if err := r.blockCh.Send(ctx, p2p.Envelope{ + if err := blockCh.Send(ctx, p2p.Envelope{ To: envelope.From, Message: &ssproto.LightBlockResponse{ LightBlock: nil, @@ -722,7 +801,7 @@ func (r *Reactor) handleLightBlockMessage(ctx context.Context, envelope *p2p.Env // NOTE: If we don't have the light block we will send a nil light block // back to the requested node, indicating that we don't have it. - if err := r.blockCh.Send(ctx, p2p.Envelope{ + if err := blockCh.Send(ctx, p2p.Envelope{ To: envelope.From, Message: &ssproto.LightBlockResponse{ LightBlock: lbproto, @@ -750,7 +829,7 @@ func (r *Reactor) handleLightBlockMessage(ctx context.Context, envelope *p2p.Env return nil } -func (r *Reactor) handleParamsMessage(ctx context.Context, envelope *p2p.Envelope) error { +func (r *Reactor) handleParamsMessage(ctx context.Context, envelope *p2p.Envelope, paramsCh *p2p.Channel) error { switch msg := envelope.Message.(type) { case *ssproto.ParamsRequest: r.logger.Debug("received consensus params request", "height", msg.Height) @@ -761,7 +840,7 @@ func (r *Reactor) handleParamsMessage(ctx context.Context, envelope *p2p.Envelop } cpproto := cp.ToProto() - if err := r.paramsCh.Send(ctx, p2p.Envelope{ + if err := paramsCh.Send(ctx, p2p.Envelope{ To: envelope.From, Message: &ssproto.ParamsResponse{ Height: msg.Height, @@ -780,6 +859,8 @@ func (r *Reactor) handleParamsMessage(ctx context.Context, envelope *p2p.Envelop if sp, ok := r.stateProvider.(*stateProviderP2P); ok { select { case sp.paramsRecvCh <- cp: + case <-ctx.Done(): + return ctx.Err() case <-time.After(time.Second): return errors.New("failed to send consensus params, stateprovider not ready for response") } @@ -797,7 +878,7 @@ func (r *Reactor) handleParamsMessage(ctx context.Context, envelope *p2p.Envelop // handleMessage handles an Envelope sent from a peer on a specific p2p Channel. // It will handle errors and any possible panics gracefully. A caller can handle // any error returned by sending a PeerError on the respective channel. -func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelope *p2p.Envelope) (err error) { +func (r *Reactor) handleMessage(ctx context.Context, envelope *p2p.Envelope, chans map[p2p.ChannelID]*p2p.Channel) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("panic in processing message: %v", e) @@ -811,17 +892,17 @@ func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelop r.logger.Debug("received message", "message", reflect.TypeOf(envelope.Message), "peer", envelope.From) - switch chID { + switch envelope.ChannelID { case SnapshotChannel: - err = r.handleSnapshotMessage(ctx, envelope) + err = r.handleSnapshotMessage(ctx, envelope, chans[SnapshotChannel]) case ChunkChannel: - err = r.handleChunkMessage(ctx, envelope) + err = r.handleChunkMessage(ctx, envelope, chans[ChunkChannel]) case LightBlockChannel: - err = r.handleLightBlockMessage(ctx, envelope) + err = r.handleLightBlockMessage(ctx, envelope, chans[LightBlockChannel]) case ParamsChannel: - err = r.handleParamsMessage(ctx, envelope) + err = r.handleParamsMessage(ctx, envelope, chans[ParamsChannel]) default: - err = fmt.Errorf("unknown channel ID (%d) for envelope (%v)", chID, envelope) + err = fmt.Errorf("unknown channel ID (%d) for envelope (%v)", envelope.ChannelID, envelope) } return err @@ -831,15 +912,34 @@ func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelop // encountered during message execution will result in a PeerError being sent on // the respective channel. When the reactor is stopped, we will catch the signal // and close the p2p Channel gracefully. -func (r *Reactor) processCh(ctx context.Context, ch *p2p.Channel, chName string) { - iter := ch.Receive(ctx) +func (r *Reactor) processChannels(ctx context.Context, chanTable map[p2p.ChannelID]*p2p.Channel) { + // make sure that the iterator gets cleaned up in case of error + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + chs := make([]*p2p.Channel, 0, len(chanTable)) + for key := range chanTable { + chs = append(chs, chanTable[key]) + } + + iter := p2p.MergedChannelIterator(ctx, chs...) for iter.Next(ctx) { envelope := iter.Envelope() - if err := r.handleMessage(ctx, ch.ID, envelope); err != nil { + if err := r.handleMessage(ctx, envelope, chanTable); err != nil { + ch, ok := chanTable[envelope.ChannelID] + if !ok { + r.logger.Error("received impossible message", + "envelope_from", envelope.From, + "envelope_ch", envelope.ChannelID, + "num_chs", len(chanTable), + "err", err, + ) + return + } r.logger.Error("failed to process message", "err", err, - "channel", chName, - "ch_id", ch.ID, + "channel", ch.String(), + "ch_id", envelope.ChannelID, "envelope", envelope) if serr := ch.SendError(ctx, p2p.PeerError{ NodeID: envelope.From, @@ -875,14 +975,15 @@ func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpda r.mtx.Lock() defer r.mtx.Unlock() + if r.syncer == nil { return } switch peerUpdate.Status { case p2p.PeerStatusUp: - newProvider := NewBlockProvider(peerUpdate.NodeID, r.chainID, r.dispatcher) + r.providers[peerUpdate.NodeID] = newProvider err := r.syncer.AddPeer(ctx, peerUpdate.NodeID) if err != nil { @@ -905,12 +1006,12 @@ func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpda // processPeerUpdates initiates a blocking process where we listen for and handle // PeerUpdate messages. When the reactor is stopped, we will catch the signal and // close the p2p PeerUpdatesCh gracefully. -func (r *Reactor) processPeerUpdates(ctx context.Context) { +func (r *Reactor) processPeerUpdates(ctx context.Context, peerUpdates *p2p.PeerUpdates) { for { select { case <-ctx.Done(): return - case peerUpdate := <-r.peerUpdates.Updates(): + case peerUpdate := <-peerUpdates.Updates(): r.processPeerUpdate(ctx, peerUpdate) } } @@ -918,7 +1019,7 @@ func (r *Reactor) processPeerUpdates(ctx context.Context) { // recentSnapshots fetches the n most recent snapshots from the app func (r *Reactor) recentSnapshots(ctx context.Context, n uint32) ([]*snapshot, error) { - resp, err := r.conn.ListSnapshots(ctx, abci.RequestListSnapshots{}) + resp, err := r.conn.ListSnapshots(ctx, &abci.RequestListSnapshots{}) if err != nil { return nil, err } @@ -1015,41 +1116,6 @@ func (r *Reactor) waitForEnoughPeers(ctx context.Context, numPeers int) error { return nil } -func (r *Reactor) initStateProvider(ctx context.Context, chainID string, initialHeight int64) error { - var err error - to := light.TrustOptions{ - Period: r.cfg.TrustPeriod, - Height: r.cfg.TrustHeight, - Hash: r.cfg.TrustHashBytes(), - } - spLogger := r.logger.With("module", "stateprovider") - spLogger.Info("initializing state provider", "trustPeriod", to.Period, - "trustHeight", to.Height, "useP2P", r.cfg.UseP2P) - - if r.cfg.UseP2P { - if err := r.waitForEnoughPeers(ctx, 2); err != nil { - return err - } - - peers := r.peers.All() - providers := make([]provider.Provider, len(peers)) - for idx, p := range peers { - providers[idx] = NewBlockProvider(p, chainID, r.dispatcher) - } - - r.stateProvider, err = NewP2PStateProvider(ctx, chainID, initialHeight, providers, to, r.paramsCh, spLogger) - if err != nil { - return fmt.Errorf("failed to initialize P2P state provider: %w", err) - } - } else { - r.stateProvider, err = NewRPCStateProvider(ctx, chainID, initialHeight, r.cfg.RPCServers, to, spLogger) - if err != nil { - return fmt.Errorf("failed to initialize RPC state provider: %w", err) - } - } - return nil -} - func (r *Reactor) TotalSnapshots() int64 { r.mtx.RLock() defer r.mtx.RUnlock() diff --git a/internal/statesync/reactor_test.go b/internal/statesync/reactor_test.go index dc7a73bf9..55a9fcf8c 100644 --- a/internal/statesync/reactor_test.go +++ b/internal/statesync/reactor_test.go @@ -102,7 +102,6 @@ func setup( rts.snapshotChannel = p2p.NewChannel( SnapshotChannel, - new(ssproto.Message), rts.snapshotInCh, rts.snapshotOutCh, rts.snapshotPeerErrCh, @@ -110,7 +109,6 @@ func setup( rts.chunkChannel = p2p.NewChannel( ChunkChannel, - new(ssproto.Message), rts.chunkInCh, rts.chunkOutCh, rts.chunkPeerErrCh, @@ -118,7 +116,6 @@ func setup( rts.blockChannel = p2p.NewChannel( LightBlockChannel, - new(ssproto.Message), rts.blockInCh, rts.blockOutCh, rts.blockPeerErrCh, @@ -126,7 +123,6 @@ func setup( rts.paramsChannel = p2p.NewChannel( ParamsChannel, - new(ssproto.Message), rts.paramsInCh, rts.paramsOutCh, rts.paramsPeerErrCh, @@ -154,34 +150,35 @@ func setup( logger := log.NewNopLogger() - var err error - rts.reactor, err = NewReactor( - ctx, + rts.reactor = NewReactor( factory.DefaultTestChainID, 1, *cfg, logger.With("component", "reactor"), conn, chCreator, - rts.peerUpdates, + func(context.Context) *p2p.PeerUpdates { return rts.peerUpdates }, rts.stateStore, rts.blockStore, "", m, - nil, // eventbus can be nil + nil, // eventbus can be nil + nil, // post-sync-hook + false, // run Sync during Start() ) - require.NoError(t, err) - rts.syncer = newSyncer( - *cfg, - logger.With("component", "syncer"), - conn, - stateProvider, - rts.snapshotChannel, - rts.chunkChannel, - "", - rts.reactor.metrics, - ) + rts.syncer = &syncer{ + logger: logger, + stateProvider: stateProvider, + conn: conn, + snapshots: newSnapshotPool(), + snapshotCh: rts.snapshotChannel, + chunkCh: rts.chunkChannel, + tempDir: t.TempDir(), + fetchers: cfg.Fetchers, + retryTimeout: cfg.ChunkRequestTimeout, + metrics: rts.reactor.metrics, + } ctx, cancel := context.WithCancel(ctx) @@ -196,22 +193,22 @@ func setup( } func TestReactor_Sync(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() const snapshotHeight = 7 - rts := setup(ctx, t, nil, nil, 2) + rts := setup(ctx, t, nil, nil, 100) chain := buildLightBlockChain(ctx, t, 1, 10, time.Now()) // app accepts any snapshot - rts.conn.On("OfferSnapshot", ctx, mock.AnythingOfType("types.RequestOfferSnapshot")). + rts.conn.On("OfferSnapshot", ctx, mock.IsType(&abci.RequestOfferSnapshot{})). Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ACCEPT}, nil) // app accepts every chunk - rts.conn.On("ApplySnapshotChunk", ctx, mock.AnythingOfType("types.RequestApplySnapshotChunk")). + rts.conn.On("ApplySnapshotChunk", ctx, mock.IsType(&abci.RequestApplySnapshotChunk{})). Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil) // app query returns valid state app hash - rts.conn.On("Info", mock.Anything, proxy.RequestInfo).Return(&abci.ResponseInfo{ + rts.conn.On("Info", mock.Anything, &proxy.RequestInfo).Return(&abci.ResponseInfo{ AppVersion: testAppVersion, LastBlockHeight: snapshotHeight, LastBlockAppHash: chain[snapshotHeight+1].AppHash, @@ -224,8 +221,7 @@ func TestReactor_Sync(t *testing.T) { closeCh := make(chan struct{}) defer close(closeCh) - go handleLightBlockRequests(ctx, t, chain, rts.blockOutCh, - rts.blockInCh, closeCh, 0) + go handleLightBlockRequests(ctx, t, chain, rts.blockOutCh, rts.blockInCh, closeCh, 0) go graduallyAddPeers(ctx, t, rts.peerUpdateCh, closeCh, 1*time.Second) go handleSnapshotRequests(ctx, t, rts.snapshotOutCh, rts.snapshotInCh, closeCh, []snapshot{ { @@ -257,8 +253,9 @@ func TestReactor_ChunkRequest_InvalidRequest(t *testing.T) { rts := setup(ctx, t, nil, nil, 2) rts.chunkInCh <- p2p.Envelope{ - From: types.NodeID("aa"), - Message: &ssproto.SnapshotsRequest{}, + From: types.NodeID("aa"), + ChannelID: ChunkChannel, + Message: &ssproto.SnapshotsRequest{}, } response := <-rts.chunkPeerErrCh @@ -306,7 +303,7 @@ func TestReactor_ChunkRequest(t *testing.T) { // mock ABCI connection to return local snapshots conn := &clientmocks.Client{} - conn.On("LoadSnapshotChunk", mock.Anything, abci.RequestLoadSnapshotChunk{ + conn.On("LoadSnapshotChunk", mock.Anything, &abci.RequestLoadSnapshotChunk{ Height: tc.request.Height, Format: tc.request.Format, Chunk: tc.request.Index, @@ -315,8 +312,9 @@ func TestReactor_ChunkRequest(t *testing.T) { rts := setup(ctx, t, conn, nil, 2) rts.chunkInCh <- p2p.Envelope{ - From: types.NodeID("aa"), - Message: tc.request, + From: types.NodeID("aa"), + ChannelID: ChunkChannel, + Message: tc.request, } response := <-rts.chunkOutCh @@ -335,8 +333,9 @@ func TestReactor_SnapshotsRequest_InvalidRequest(t *testing.T) { rts := setup(ctx, t, nil, nil, 2) rts.snapshotInCh <- p2p.Envelope{ - From: types.NodeID("aa"), - Message: &ssproto.ChunkRequest{}, + From: types.NodeID("aa"), + ChannelID: SnapshotChannel, + Message: &ssproto.ChunkRequest{}, } response := <-rts.snapshotPeerErrCh @@ -393,15 +392,16 @@ func TestReactor_SnapshotsRequest(t *testing.T) { // mock ABCI connection to return local snapshots conn := &clientmocks.Client{} - conn.On("ListSnapshots", mock.Anything, abci.RequestListSnapshots{}).Return(&abci.ResponseListSnapshots{ + conn.On("ListSnapshots", mock.Anything, &abci.RequestListSnapshots{}).Return(&abci.ResponseListSnapshots{ Snapshots: tc.snapshots, }, nil) rts := setup(ctx, t, conn, nil, 100) rts.snapshotInCh <- p2p.Envelope{ - From: types.NodeID("aa"), - Message: &ssproto.SnapshotsRequest{}, + From: types.NodeID("aa"), + ChannelID: SnapshotChannel, + Message: &ssproto.SnapshotsRequest{}, } if len(tc.expectResponses) > 0 { @@ -457,7 +457,8 @@ func TestReactor_LightBlockResponse(t *testing.T) { rts.stateStore.On("LoadValidators", height).Return(vals, nil) rts.blockInCh <- p2p.Envelope{ - From: types.NodeID("aa"), + From: types.NodeID("aa"), + ChannelID: LightBlockChannel, Message: &ssproto.LightBlockRequest{ Height: 10, }, @@ -731,11 +732,16 @@ func handleLightBlockRequests( if requests%10 >= failureRate { lb, err := chain[int64(msg.Height)].ToProto() require.NoError(t, err) - sending <- p2p.Envelope{ - From: envelope.To, + select { + case sending <- p2p.Envelope{ + From: envelope.To, + ChannelID: LightBlockChannel, Message: &ssproto.LightBlockResponse{ LightBlock: lb, }, + }: + case <-ctx.Done(): + return } } else { switch errorCount % 3 { @@ -744,18 +750,28 @@ func handleLightBlockRequests( _, _, lb := mockLB(ctx, t, int64(msg.Height), factory.DefaultTestTime, factory.MakeBlockID(), vals, pv) differntLB, err := lb.ToProto() require.NoError(t, err) - sending <- p2p.Envelope{ - From: envelope.To, + select { + case sending <- p2p.Envelope{ + From: envelope.To, + ChannelID: LightBlockChannel, Message: &ssproto.LightBlockResponse{ LightBlock: differntLB, }, + }: + case <-ctx.Done(): + return } case 1: // send nil block i.e. pretend we don't have it - sending <- p2p.Envelope{ - From: envelope.To, + select { + case sending <- p2p.Envelope{ + From: envelope.To, + ChannelID: LightBlockChannel, Message: &ssproto.LightBlockResponse{ LightBlock: nil, }, + }: + case <-ctx.Done(): + return } case 2: // don't do anything } @@ -783,19 +799,24 @@ func handleConsensusParamsRequest( case <-ctx.Done(): return case envelope := <-receiving: - if ctx.Err() != nil { + msg, ok := envelope.Message.(*ssproto.ParamsRequest) + if !ok { + t.Errorf("message was %T which is not a params request", envelope.Message) return } - - t.Log("received consensus params request") - msg, ok := envelope.Message.(*ssproto.ParamsRequest) - require.True(t, ok) - sending <- p2p.Envelope{ - From: envelope.To, + select { + case sending <- p2p.Envelope{ + From: envelope.To, + ChannelID: ParamsChannel, Message: &ssproto.ParamsResponse{ Height: msg.Height, ConsensusParams: paramsProto, }, + }: + case <-ctx.Done(): + return + case <-closeCh: + return } case <-closeCh: @@ -897,7 +918,8 @@ func handleSnapshotRequests( require.True(t, ok) for _, snapshot := range snapshots { sendingCh <- p2p.Envelope{ - From: envelope.To, + From: envelope.To, + ChannelID: SnapshotChannel, Message: &ssproto.SnapshotsResponse{ Height: snapshot.Height, Format: snapshot.Format, @@ -930,7 +952,8 @@ func handleChunkRequests( msg, ok := envelope.Message.(*ssproto.ChunkRequest) require.True(t, ok) sendingCh <- p2p.Envelope{ - From: envelope.To, + From: envelope.To, + ChannelID: ChunkChannel, Message: &ssproto.ChunkResponse{ Height: msg.Height, Format: msg.Format, diff --git a/internal/statesync/stateprovider.go b/internal/statesync/stateprovider.go index 3e58ca5f4..a796b0b2e 100644 --- a/internal/statesync/stateprovider.go +++ b/internal/statesync/stateprovider.go @@ -379,7 +379,7 @@ func (s *stateProviderP2P) consensusParams(ctx context.Context, height int64) (t } wg.Add(1) - go func(p *BlockProvider, peer types.NodeID) { + go func(peer types.NodeID) { defer wg.Done() timer := time.NewTimer(0) @@ -424,7 +424,7 @@ func (s *stateProviderP2P) consensusParams(ctx context.Context, height int64) (t } } - }(p, peer) + }(peer) } sig := make(chan struct{}) go func() { wg.Wait(); close(sig) }() diff --git a/internal/statesync/syncer.go b/internal/statesync/syncer.go index 78eb8d53a..47e058c19 100644 --- a/internal/statesync/syncer.go +++ b/internal/statesync/syncer.go @@ -10,7 +10,6 @@ import ( abciclient "github.com/tendermint/tendermint/abci/client" abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/internal/p2p" "github.com/tendermint/tendermint/internal/proxy" sm "github.com/tendermint/tendermint/internal/state" @@ -72,31 +71,6 @@ type syncer struct { processingSnapshot *snapshot } -// newSyncer creates a new syncer. -func newSyncer( - cfg config.StateSyncConfig, - logger log.Logger, - conn abciclient.Client, - stateProvider StateProvider, - snapshotCh *p2p.Channel, - chunkCh *p2p.Channel, - tempDir string, - metrics *Metrics, -) *syncer { - return &syncer{ - logger: logger, - stateProvider: stateProvider, - conn: conn, - snapshots: newSnapshotPool(), - snapshotCh: snapshotCh, - chunkCh: chunkCh, - tempDir: tempDir, - fetchers: cfg.Fetchers, - retryTimeout: cfg.ChunkRequestTimeout, - metrics: metrics, - } -} - // AddChunk adds a chunk to the chunk queue, if any. It returns false if the chunk has already // been added to the queue, or an error if there's no sync in progress. func (s *syncer) AddChunk(chunk *chunk) (bool, error) { @@ -363,7 +337,7 @@ func (s *syncer) Sync(ctx context.Context, snapshot *snapshot, chunks *chunkQueu func (s *syncer) offerSnapshot(ctx context.Context, snapshot *snapshot) error { s.logger.Info("Offering snapshot to ABCI app", "height", snapshot.Height, "format", snapshot.Format, "hash", snapshot.Hash) - resp, err := s.conn.OfferSnapshot(ctx, abci.RequestOfferSnapshot{ + resp, err := s.conn.OfferSnapshot(ctx, &abci.RequestOfferSnapshot{ Snapshot: &abci.Snapshot{ Height: snapshot.Height, Format: snapshot.Format, @@ -405,7 +379,7 @@ func (s *syncer) applyChunks(ctx context.Context, chunks *chunkQueue, start time return fmt.Errorf("failed to fetch chunk: %w", err) } - resp, err := s.conn.ApplySnapshotChunk(ctx, abci.RequestApplySnapshotChunk{ + resp, err := s.conn.ApplySnapshotChunk(ctx, &abci.RequestApplySnapshotChunk{ Index: chunk.Index, Chunk: chunk.Chunk, Sender: string(chunk.Sender), @@ -545,7 +519,7 @@ func (s *syncer) requestChunk(ctx context.Context, snapshot *snapshot, chunk uin // verifyApp verifies the sync, checking the app hash, last block height and app version func (s *syncer) verifyApp(ctx context.Context, snapshot *snapshot, appVersion uint64) error { - resp, err := s.conn.Info(ctx, proxy.RequestInfo) + resp, err := s.conn.Info(ctx, &proxy.RequestInfo) if err != nil { return fmt.Errorf("failed to query ABCI app for appHash: %w", err) } diff --git a/internal/statesync/syncer_test.go b/internal/statesync/syncer_test.go index e3bf49259..3fc3f0db4 100644 --- a/internal/statesync/syncer_test.go +++ b/internal/statesync/syncer_test.go @@ -109,7 +109,7 @@ func TestSyncer_SyncAny(t *testing.T) { // We start a sync, with peers sending back chunks when requested. We first reject the snapshot // with height 2 format 2, and accept the snapshot at height 1. - conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: &abci.Snapshot{ Height: 2, Format: 2, @@ -118,7 +118,7 @@ func TestSyncer_SyncAny(t *testing.T) { }, AppHash: []byte("app_hash_2"), }).Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT_FORMAT}, nil) - conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: &abci.Snapshot{ Height: s.Height, Format: s.Format, @@ -170,7 +170,7 @@ func TestSyncer_SyncAny(t *testing.T) { // The first time we're applying chunk 2 we tell it to retry the snapshot and discard chunk 1, // which should cause it to keep the existing chunk 0 and 2, and restart restoration from // beginning. We also wait for a little while, to exercise the retry logic in fetchChunks(). - conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 2, Chunk: []byte{1, 1, 2}, }).Once().Run(func(args mock.Arguments) { time.Sleep(1 * time.Second) }).Return( &abci.ResponseApplySnapshotChunk{ @@ -178,16 +178,16 @@ func TestSyncer_SyncAny(t *testing.T) { RefetchChunks: []uint32{1}, }, nil) - conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 0, Chunk: []byte{1, 1, 0}, }).Times(2).Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil) - conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 1, Chunk: []byte{1, 1, 1}, }).Times(2).Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil) - conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 2, Chunk: []byte{1, 1, 2}, }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil) - conn.On("Info", mock.Anything, proxy.RequestInfo).Return(&abci.ResponseInfo{ + conn.On("Info", mock.Anything, &proxy.RequestInfo).Return(&abci.ResponseInfo{ AppVersion: testAppVersion, LastBlockHeight: 1, LastBlockAppHash: []byte("app_hash"), @@ -247,7 +247,7 @@ func TestSyncer_SyncAny_abort(t *testing.T) { _, err := rts.syncer.AddSnapshot(peerID, s) require.NoError(t, err) - rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + rts.conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: toABCI(s), AppHash: []byte("app_hash"), }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ABORT}, nil) @@ -281,15 +281,15 @@ func TestSyncer_SyncAny_reject(t *testing.T) { _, err = rts.syncer.AddSnapshot(peerID, s11) require.NoError(t, err) - rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + rts.conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: toABCI(s22), AppHash: []byte("app_hash"), }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}, nil) - rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + rts.conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: toABCI(s12), AppHash: []byte("app_hash"), }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}, nil) - rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + rts.conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: toABCI(s11), AppHash: []byte("app_hash"), }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}, nil) @@ -323,11 +323,11 @@ func TestSyncer_SyncAny_reject_format(t *testing.T) { _, err = rts.syncer.AddSnapshot(peerID, s11) require.NoError(t, err) - rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + rts.conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: toABCI(s22), AppHash: []byte("app_hash"), }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT_FORMAT}, nil) - rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + rts.conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: toABCI(s11), AppHash: []byte("app_hash"), }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ABORT}, nil) @@ -372,11 +372,11 @@ func TestSyncer_SyncAny_reject_sender(t *testing.T) { _, err = rts.syncer.AddSnapshot(peerCID, sbc) require.NoError(t, err) - rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + rts.conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: toABCI(sbc), AppHash: []byte("app_hash"), }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT_SENDER}, nil) - rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + rts.conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: toABCI(sa), AppHash: []byte("app_hash"), }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}, nil) @@ -402,7 +402,7 @@ func TestSyncer_SyncAny_abciError(t *testing.T) { _, err := rts.syncer.AddSnapshot(peerID, s) require.NoError(t, err) - rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + rts.conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: toABCI(s), AppHash: []byte("app_hash"), }).Once().Return(nil, errBoom) @@ -445,7 +445,7 @@ func TestSyncer_offerSnapshot(t *testing.T) { rts := setup(ctx, t, nil, stateProvider, 2) s := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}, trustedAppHash: []byte("app_hash")} - rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + rts.conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: toABCI(s), AppHash: []byte("app_hash"), }).Return(&abci.ResponseOfferSnapshot{Result: tc.result}, tc.err) @@ -506,11 +506,11 @@ func TestSyncer_applyChunks_Results(t *testing.T) { _, err = chunks.Add(&chunk{Height: 1, Format: 1, Index: 0, Chunk: body}) require.NoError(t, err) - rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + rts.conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 0, Chunk: body, }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: tc.result}, tc.err) if tc.result == abci.ResponseApplySnapshotChunk_RETRY { - rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + rts.conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 0, Chunk: body, }).Once().Return(&abci.ResponseApplySnapshotChunk{ Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil) @@ -573,13 +573,13 @@ func TestSyncer_applyChunks_RefetchChunks(t *testing.T) { require.NoError(t, err) // The first two chunks are accepted, before the last one asks for 1 to be refetched - rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + rts.conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 0, Chunk: []byte{0}, }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil) - rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + rts.conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 1, Chunk: []byte{1}, }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil) - rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + rts.conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 2, Chunk: []byte{2}, }).Once().Return(&abci.ResponseApplySnapshotChunk{ Result: tc.result, @@ -673,13 +673,13 @@ func TestSyncer_applyChunks_RejectSenders(t *testing.T) { require.NoError(t, err) // The first two chunks are accepted, before the last one asks for b sender to be rejected - rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + rts.conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 0, Chunk: []byte{0}, Sender: "aa", }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil) - rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + rts.conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 1, Chunk: []byte{1}, Sender: "bb", }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil) - rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + rts.conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 2, Chunk: []byte{2}, Sender: "cc", }).Once().Return(&abci.ResponseApplySnapshotChunk{ Result: tc.result, @@ -688,7 +688,7 @@ func TestSyncer_applyChunks_RejectSenders(t *testing.T) { // On retry, the last chunk will be tried again, so we just accept it then. if tc.result == abci.ResponseApplySnapshotChunk_RETRY { - rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + rts.conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 2, Chunk: []byte{2}, Sender: "cc", }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil) } @@ -761,7 +761,7 @@ func TestSyncer_verifyApp(t *testing.T) { rts := setup(ctx, t, nil, nil, 2) - rts.conn.On("Info", mock.Anything, proxy.RequestInfo).Return(tc.response, tc.err) + rts.conn.On("Info", mock.Anything, &proxy.RequestInfo).Return(tc.response, tc.err) err := rts.syncer.verifyApp(ctx, s, appVersion) unwrapped := errors.Unwrap(err) if unwrapped != nil { diff --git a/internal/store/store_test.go b/internal/store/store_test.go index 8e1ee2db3..4fa577cc4 100644 --- a/internal/store/store_test.go +++ b/internal/store/store_test.go @@ -17,7 +17,6 @@ import ( "github.com/tendermint/tendermint/crypto" sm "github.com/tendermint/tendermint/internal/state" "github.com/tendermint/tendermint/internal/state/test/factory" - "github.com/tendermint/tendermint/libs/log" tmrand "github.com/tendermint/tendermint/libs/rand" tmtime "github.com/tendermint/tendermint/libs/time" "github.com/tendermint/tendermint/types" @@ -46,7 +45,7 @@ func makeTestCommit(height int64, timestamp time.Time) *types.Commit { commitSigs) } -func makeStateAndBlockStore(dir string, logger log.Logger) (sm.State, *BlockStore, cleanupFunc, error) { +func makeStateAndBlockStore(dir string) (sm.State, *BlockStore, cleanupFunc, error) { cfg, err := config.ResetTestRoot(dir, "blockchain_reactor_test") if err != nil { return sm.State{}, nil, nil, err @@ -81,16 +80,13 @@ func TestMain(m *testing.M) { } var cleanup cleanupFunc - state, _, cleanup, err = makeStateAndBlockStore(dir, log.NewNopLogger()) + state, _, cleanup, err = makeStateAndBlockStore(dir) if err != nil { stdlog.Fatal(err) } - block, err = factory.MakeBlock(state, 1, new(types.Commit)) + block = factory.MakeBlock(state, 1, new(types.Commit)) - if err != nil { - stdlog.Fatal(err) - } partSet, err = block.MakePartSet(2) if err != nil { stdlog.Fatal(err) @@ -106,7 +102,7 @@ func TestMain(m *testing.M) { // TODO: This test should be simplified ... func TestBlockStoreSaveLoadBlock(t *testing.T) { - state, bs, cleanup, err := makeStateAndBlockStore(t.TempDir(), log.NewNopLogger()) + state, bs, cleanup, err := makeStateAndBlockStore(t.TempDir()) defer cleanup() require.NoError(t, err) require.Equal(t, bs.Base(), int64(0), "initially the base should be zero") @@ -121,8 +117,7 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { } // save a block - block, err := factory.MakeBlock(state, bs.Height()+1, new(types.Commit)) - require.NoError(t, err) + block := factory.MakeBlock(state, bs.Height()+1, new(types.Commit)) validPartSet, err := block.MakePartSet(2) require.NoError(t, err) seenCommit := makeTestCommit(10, tmtime.Now()) @@ -326,8 +321,7 @@ func TestLoadBaseMeta(t *testing.T) { bs := NewBlockStore(dbm.NewMemDB()) for h := int64(1); h <= 10; h++ { - block, err := factory.MakeBlock(state, h, new(types.Commit)) - require.NoError(t, err) + block := factory.MakeBlock(state, h, new(types.Commit)) partSet, err := block.MakePartSet(2) require.NoError(t, err) seenCommit := makeTestCommit(h, tmtime.Now()) @@ -394,8 +388,7 @@ func TestPruneBlocks(t *testing.T) { // make more than 1000 blocks, to test batch deletions for h := int64(1); h <= 1500; h++ { - block, err := factory.MakeBlock(state, h, new(types.Commit)) - require.NoError(t, err) + block := factory.MakeBlock(state, h, new(types.Commit)) partSet, err := block.MakePartSet(2) require.NoError(t, err) seenCommit := makeTestCommit(h, tmtime.Now()) @@ -498,12 +491,11 @@ func TestLoadBlockMeta(t *testing.T) { } func TestBlockFetchAtHeight(t *testing.T) { - state, bs, cleanup, err := makeStateAndBlockStore(t.TempDir(), log.NewNopLogger()) + state, bs, cleanup, err := makeStateAndBlockStore(t.TempDir()) defer cleanup() require.NoError(t, err) require.Equal(t, bs.Height(), int64(0), "initially the height should be zero") - block, err := factory.MakeBlock(state, bs.Height()+1, new(types.Commit)) - require.NoError(t, err) + block := factory.MakeBlock(state, bs.Height()+1, new(types.Commit)) partSet, err := block.MakePartSet(2) require.NoError(t, err) @@ -545,8 +537,7 @@ func TestSeenAndCanonicalCommit(t *testing.T) { // are persisted. for h := int64(3); h <= 5; h++ { blockCommit := makeTestCommit(h-1, tmtime.Now()) - block, err := factory.MakeBlock(state, h, blockCommit) - require.NoError(t, err) + block := factory.MakeBlock(state, h, blockCommit) partSet, err := block.MakePartSet(2) require.NoError(t, err) seenCommit := makeTestCommit(h, tmtime.Now()) diff --git a/internal/test/factory/block.go b/internal/test/factory/block.go index 1d5709dcc..2cb6a3161 100644 --- a/internal/test/factory/block.go +++ b/internal/test/factory/block.go @@ -7,7 +7,6 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/version" ) @@ -25,7 +24,7 @@ func RandomAddress() []byte { } func RandomHash() []byte { - return crypto.CRandBytes(tmhash.Size) + return crypto.CRandBytes(crypto.HashSize) } func MakeBlockID() types.BlockID { diff --git a/internal/test/factory/commit.go b/internal/test/factory/commit.go index 1a8691855..bc4022499 100644 --- a/internal/test/factory/commit.go +++ b/internal/test/factory/commit.go @@ -31,6 +31,7 @@ func MakeCommit(ctx context.Context, blockID types.BlockID, height int64, round return nil, err } vote.Signature = v.Signature + vote.ExtensionSignature = v.ExtensionSignature if _, err := voteSet.AddVote(vote); err != nil { return nil, err } diff --git a/internal/test/factory/params.go b/internal/test/factory/params.go new file mode 100644 index 000000000..dda8e2b3c --- /dev/null +++ b/internal/test/factory/params.go @@ -0,0 +1,22 @@ +package factory + +import ( + "time" + + "github.com/tendermint/tendermint/types" +) + +// ConsensusParams returns a default set of ConsensusParams that are suitable +// for use in testing +func ConsensusParams() *types.ConsensusParams { + c := types.DefaultConsensusParams() + c.Timeout = types.TimeoutParams{ + Commit: 10 * time.Millisecond, + Propose: 40 * time.Millisecond, + ProposeDelta: 1 * time.Millisecond, + Vote: 10 * time.Millisecond, + VoteDelta: 1 * time.Millisecond, + BypassCommitTimeout: true, + } + return c +} diff --git a/internal/test/factory/tx.go b/internal/test/factory/tx.go index 035308349..725f3c720 100644 --- a/internal/test/factory/tx.go +++ b/internal/test/factory/tx.go @@ -2,10 +2,10 @@ package factory import "github.com/tendermint/tendermint/types" -func MakeTenTxs(height int64) []types.Tx { - txs := make([]types.Tx, 10) +func MakeNTxs(height, n int64) []types.Tx { + txs := make([]types.Tx, n) for i := range txs { - txs[i] = types.Tx([]byte{byte(height), byte(i)}) + txs[i] = types.Tx([]byte{byte(height), byte(i / 256), byte(i % 256)}) } return txs } diff --git a/internal/test/factory/vote.go b/internal/test/factory/vote.go index fc63e8d68..4e77473c2 100644 --- a/internal/test/factory/vote.go +++ b/internal/test/factory/vote.go @@ -40,5 +40,6 @@ func MakeVote( } v.Signature = vpb.Signature + v.ExtensionSignature = vpb.ExtensionSignature return v, nil } diff --git a/libs/bits/bit_array_test.go b/libs/bits/bit_array_test.go index b76085bee..e4bf45c38 100644 --- a/libs/bits/bit_array_test.go +++ b/libs/bits/bit_array_test.go @@ -13,25 +13,25 @@ import ( tmprotobits "github.com/tendermint/tendermint/proto/tendermint/libs/bits" ) -func randBitArray(bits int) (*BitArray, []byte) { +func randBitArray(bits int) *BitArray { src := tmrand.Bytes((bits + 7) / 8) bA := NewBitArray(bits) for i := 0; i < len(src); i++ { for j := 0; j < 8; j++ { if i*8+j >= bits { - return bA, src + return bA } setBit := src[i]&(1< 0 bA.SetIndex(i*8+j, setBit) } } - return bA, src + return bA } func TestAnd(t *testing.T) { - bA1, _ := randBitArray(51) - bA2, _ := randBitArray(31) + bA1 := randBitArray(51) + bA2 := randBitArray(31) bA3 := bA1.And(bA2) var bNil *BitArray @@ -54,9 +54,8 @@ func TestAnd(t *testing.T) { } func TestOr(t *testing.T) { - - bA1, _ := randBitArray(51) - bA2, _ := randBitArray(31) + bA1 := randBitArray(51) + bA2 := randBitArray(31) bA3 := bA1.Or(bA2) bNil := (*BitArray)(nil) @@ -191,10 +190,7 @@ func TestEmptyFull(t *testing.T) { } func TestUpdateNeverPanics(t *testing.T) { - newRandBitArray := func(n int) *BitArray { - ba, _ := randBitArray(n) - return ba - } + newRandBitArray := func(n int) *BitArray { return randBitArray(n) } pairs := []struct { a, b *BitArray }{ diff --git a/libs/bytes/bytes.go b/libs/bytes/bytes.go index dd8e39737..a6be32f05 100644 --- a/libs/bytes/bytes.go +++ b/libs/bytes/bytes.go @@ -1,21 +1,16 @@ package bytes import ( - "bytes" + "encoding/base64" "encoding/hex" - "encoding/json" "fmt" "strings" ) -// HexBytes enables HEX-encoding for json/encoding. +// HexBytes is a wrapper around []byte that encodes data as hexadecimal strings +// for use in JSON. type HexBytes []byte -var ( - _ json.Marshaler = HexBytes{} - _ json.Unmarshaler = &HexBytes{} -) - // Marshal needed for protobuf compatibility func (bz HexBytes) Marshal() ([]byte, error) { return bz, nil @@ -27,41 +22,30 @@ func (bz *HexBytes) Unmarshal(data []byte) error { return nil } -// MarshalJSON implements the json.Marshaler interface. The encoding is a JSON -// quoted string of hexadecimal digits. -func (bz HexBytes) MarshalJSON() ([]byte, error) { - size := hex.EncodedLen(len(bz)) + 2 // +2 for quotation marks - buf := make([]byte, size) - hex.Encode(buf[1:], []byte(bz)) - buf[0] = '"' - buf[size-1] = '"' - - // Ensure letter digits are capitalized. - for i := 1; i < size-1; i++ { - if buf[i] >= 'a' && buf[i] <= 'f' { - buf[i] = 'A' + (buf[i] - 'a') - } - } - return buf, nil +// MarshalText encodes a HexBytes value as hexadecimal digits. +// This method is used by json.Marshal. +func (bz HexBytes) MarshalText() ([]byte, error) { + enc := hex.EncodeToString([]byte(bz)) + return []byte(strings.ToUpper(enc)), nil } -// UnmarshalJSON implements the json.Umarshaler interface. -func (bz *HexBytes) UnmarshalJSON(data []byte) error { - if bytes.Equal(data, []byte("null")) { +// UnmarshalText handles decoding of HexBytes from JSON strings. +// This method is used by json.Unmarshal. +// It allows decoding of both hex and base64-encoded byte arrays. +func (bz *HexBytes) UnmarshalText(data []byte) error { + input := string(data) + if input == "" || input == "null" { return nil } - - if len(data) < 2 || data[0] != '"' || data[len(data)-1] != '"' { - return fmt.Errorf("invalid hex string: %s", data) - } - - bz2, err := hex.DecodeString(string(data[1 : len(data)-1])) + dec, err := hex.DecodeString(input) if err != nil { - return err + dec, err = base64.StdEncoding.DecodeString(input) + + if err != nil { + return err + } } - - *bz = bz2 - + *bz = HexBytes(dec) return nil } diff --git a/libs/bytes/bytes_test.go b/libs/bytes/bytes_test.go index ce39935e8..fb1200a04 100644 --- a/libs/bytes/bytes_test.go +++ b/libs/bytes/bytes_test.go @@ -61,7 +61,7 @@ func TestJSONMarshal(t *testing.T) { t.Fatal(err) } assert.Equal(t, ts2.B1, tc.input) - assert.Equal(t, ts2.B2, HexBytes(tc.input)) + assert.Equal(t, string(ts2.B2), string(tc.input)) }) } } diff --git a/libs/events/Makefile b/libs/events/Makefile deleted file mode 100644 index 696aafff1..000000000 --- a/libs/events/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -.PHONY: docs -REPO:=github.com/tendermint/tendermint/libs/events - -docs: - @go get github.com/davecheney/godoc2md - godoc2md $(REPO) > README.md - -test: - go test -v ./... diff --git a/libs/events/README.md b/libs/events/README.md deleted file mode 100644 index 9c4d7dc55..000000000 --- a/libs/events/README.md +++ /dev/null @@ -1,193 +0,0 @@ - - -# events - -`import "github.com/tendermint/tendermint/libs/events"` - -* [Overview](#pkg-overview) -* [Index](#pkg-index) - -## Overview - -Pub-Sub in go with event caching - -## Index - -* [type EventCache](#EventCache) - * [func NewEventCache(evsw Fireable) *EventCache](#NewEventCache) - * [func (evc *EventCache) FireEvent(event string, data EventData)](#EventCache.FireEvent) - * [func (evc *EventCache) Flush()](#EventCache.Flush) -* [type EventCallback](#EventCallback) -* [type EventData](#EventData) -* [type EventSwitch](#EventSwitch) - * [func NewEventSwitch() EventSwitch](#NewEventSwitch) -* [type Eventable](#Eventable) -* [type Fireable](#Fireable) - - -### Package files - -[event_cache.go](/src/github.com/tendermint/tendermint/libs/events/event_cache.go) [events.go](/src/github.com/tendermint/tendermint/libs/events/events.go) - - - - - - -## Type [EventCache](/src/target/event_cache.go?s=116:179#L5) - -``` go -type EventCache struct { - // contains filtered or unexported fields -} -``` - -An EventCache buffers events for a Fireable -All events are cached. Filtering happens on Flush - - - - - - - -### func [NewEventCache](/src/target/event_cache.go?s=239:284#L11) - -``` go -func NewEventCache(evsw Fireable) *EventCache -``` - -Create a new EventCache with an EventSwitch as backend - - - - - -### func (\*EventCache) [FireEvent](/src/target/event_cache.go?s=449:511#L24) - -``` go -func (evc *EventCache) FireEvent(event string, data EventData) -``` - -Cache an event to be fired upon finality. - - - - -### func (\*EventCache) [Flush](/src/target/event_cache.go?s=735:765#L31) - -``` go -func (evc *EventCache) Flush() -``` - -Fire events by running evsw.FireEvent on all cached events. Blocks. -Clears cached events - - - - -## Type [EventCallback](/src/target/events.go?s=4201:4240#L185) - -``` go -type EventCallback func(data EventData) -``` - - - - - - - - - -## Type [EventData](/src/target/events.go?s=243:294#L14) - -``` go -type EventData interface { -} -``` - -Generic event data can be typed and registered with tendermint/go-amino -via concrete implementation of this interface - - - - - - - - - - -## Type [EventSwitch](/src/target/events.go?s=560:771#L29) - -``` go -type EventSwitch interface { - service.Service - Fireable - - AddListenerForEvent(listenerID, event string, cb EventCallback) - RemoveListenerForEvent(event string, listenerID string) - RemoveListener(listenerID string) -} -``` - - - - - - -### func [NewEventSwitch](/src/target/events.go?s=917:950#L46) - -``` go -func NewEventSwitch() EventSwitch -``` - - - - -## Type [Eventable](/src/target/events.go?s=378:440#L20) - -``` go -type Eventable interface { - SetEventSwitch(evsw EventSwitch) -} -``` - -reactors and other modules should export -this interface to become eventable - - - - - - - - - - -## Type [Fireable](/src/target/events.go?s=490:558#L25) - -``` go -type Fireable interface { - FireEvent(event string, data EventData) -} -``` - -an event switch or cache implements fireable - - - - - - - - - - - - - - -- - - -Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md) diff --git a/libs/events/events.go b/libs/events/events.go index 636aa102d..1b8db09c4 100644 --- a/libs/events/events.go +++ b/libs/events/events.go @@ -2,24 +2,9 @@ package events import ( - "context" - "fmt" "sync" - - "github.com/tendermint/tendermint/libs/log" - "github.com/tendermint/tendermint/libs/service" ) -// ErrListenerWasRemoved is returned by AddEvent if the listener was removed. -type ErrListenerWasRemoved struct { - listenerID string -} - -// Error implements the error interface. -func (e ErrListenerWasRemoved) Error() string { - return fmt.Sprintf("listener #%s was removed", e.listenerID) -} - // EventData is a generic event data can be typed and registered with // tendermint/go-amino via concrete implementation of this interface. type EventData interface{} @@ -34,7 +19,7 @@ type Eventable interface { // // FireEvent fires an event with the given name and data. type Fireable interface { - FireEvent(ctx context.Context, eventValue string, data EventData) + FireEvent(eventValue string, data EventData) } // EventSwitch is the interface for synchronous pubsub, where listeners @@ -45,38 +30,22 @@ type Fireable interface { // They can be removed by calling either RemoveListenerForEvent or // RemoveListener (for all events). type EventSwitch interface { - service.Service Fireable - Stop() - AddListenerForEvent(listenerID, eventValue string, cb EventCallback) error - RemoveListenerForEvent(event string, listenerID string) - RemoveListener(listenerID string) } type eventSwitch struct { - service.BaseService - mtx sync.RWMutex eventCells map[string]*eventCell - listeners map[string]*eventListener } -func NewEventSwitch(logger log.Logger) EventSwitch { +func NewEventSwitch() EventSwitch { evsw := &eventSwitch{ eventCells: make(map[string]*eventCell), - listeners: make(map[string]*eventListener), } - evsw.BaseService = *service.NewBaseService(logger, "EventSwitch", evsw) return evsw } -func (evsw *eventSwitch) OnStart(ctx context.Context) error { - return nil -} - -func (evsw *eventSwitch) OnStop() {} - func (evsw *eventSwitch) AddListenerForEvent(listenerID, eventValue string, cb EventCallback) error { // Get/Create eventCell and listener. evsw.mtx.Lock() @@ -86,70 +55,13 @@ func (evsw *eventSwitch) AddListenerForEvent(listenerID, eventValue string, cb E eventCell = newEventCell() evsw.eventCells[eventValue] = eventCell } - - listener := evsw.listeners[listenerID] - if listener == nil { - listener = newEventListener(listenerID) - evsw.listeners[listenerID] = listener - } - evsw.mtx.Unlock() - if err := listener.AddEvent(eventValue); err != nil { - return err - } - - eventCell.AddListener(listenerID, cb) + eventCell.addListener(listenerID, cb) return nil } -func (evsw *eventSwitch) RemoveListener(listenerID string) { - // Get and remove listener. - evsw.mtx.RLock() - listener := evsw.listeners[listenerID] - evsw.mtx.RUnlock() - if listener == nil { - return - } - - evsw.mtx.Lock() - delete(evsw.listeners, listenerID) - evsw.mtx.Unlock() - - // Remove callback for each event. - listener.SetRemoved() - for _, event := range listener.GetEvents() { - evsw.RemoveListenerForEvent(event, listenerID) - } -} - -func (evsw *eventSwitch) RemoveListenerForEvent(event string, listenerID string) { - // Get eventCell - evsw.mtx.Lock() - eventCell := evsw.eventCells[event] - evsw.mtx.Unlock() - - if eventCell == nil { - return - } - - // Remove listenerID from eventCell - numListeners := eventCell.RemoveListener(listenerID) - - // Maybe garbage collect eventCell. - if numListeners == 0 { - // Lock again and double check. - evsw.mtx.Lock() // OUTER LOCK - eventCell.mtx.Lock() // INNER LOCK - if len(eventCell.listeners) == 0 { - delete(evsw.eventCells, event) - } - eventCell.mtx.Unlock() // INNER LOCK - evsw.mtx.Unlock() // OUTER LOCK - } -} - -func (evsw *eventSwitch) FireEvent(ctx context.Context, event string, data EventData) { +func (evsw *eventSwitch) FireEvent(event string, data EventData) { // Get the eventCell evsw.mtx.RLock() eventCell := evsw.eventCells[event] @@ -160,11 +72,13 @@ func (evsw *eventSwitch) FireEvent(ctx context.Context, event string, data Event } // Fire event for all listeners in eventCell - eventCell.FireEvent(ctx, data) + eventCell.fireEvent(data) } //----------------------------------------------------------------------------- +type EventCallback func(data EventData) error + // eventCell handles keeping track of listener callbacks for a given event. type eventCell struct { mtx sync.RWMutex @@ -177,21 +91,13 @@ func newEventCell() *eventCell { } } -func (cell *eventCell) AddListener(listenerID string, cb EventCallback) { +func (cell *eventCell) addListener(listenerID string, cb EventCallback) { cell.mtx.Lock() + defer cell.mtx.Unlock() cell.listeners[listenerID] = cb - cell.mtx.Unlock() } -func (cell *eventCell) RemoveListener(listenerID string) int { - cell.mtx.Lock() - delete(cell.listeners, listenerID) - numListeners := len(cell.listeners) - cell.mtx.Unlock() - return numListeners -} - -func (cell *eventCell) FireEvent(ctx context.Context, data EventData) { +func (cell *eventCell) fireEvent(data EventData) { cell.mtx.RLock() eventCallbacks := make([]EventCallback, 0, len(cell.listeners)) for _, cb := range cell.listeners { @@ -200,56 +106,9 @@ func (cell *eventCell) FireEvent(ctx context.Context, data EventData) { cell.mtx.RUnlock() for _, cb := range eventCallbacks { - if err := cb(ctx, data); err != nil { + if err := cb(data); err != nil { // should we log or abort here? continue } } } - -//----------------------------------------------------------------------------- - -type EventCallback func(ctx context.Context, data EventData) error - -type eventListener struct { - id string - - mtx sync.RWMutex - removed bool - events []string -} - -func newEventListener(id string) *eventListener { - return &eventListener{ - id: id, - removed: false, - events: nil, - } -} - -func (evl *eventListener) AddEvent(event string) error { - evl.mtx.Lock() - - if evl.removed { - evl.mtx.Unlock() - return ErrListenerWasRemoved{listenerID: evl.id} - } - - evl.events = append(evl.events, event) - evl.mtx.Unlock() - return nil -} - -func (evl *eventListener) GetEvents() []string { - evl.mtx.RLock() - events := make([]string, len(evl.events)) - copy(events, evl.events) - evl.mtx.RUnlock() - return events -} - -func (evl *eventListener) SetRemoved() { - evl.mtx.Lock() - evl.removed = true - evl.mtx.Unlock() -} diff --git a/libs/events/events_test.go b/libs/events/events_test.go index 6d0c8b4e7..17f8c56d1 100644 --- a/libs/events/events_test.go +++ b/libs/events/events_test.go @@ -7,10 +7,8 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" + "github.com/fortytw2/leaktest" "github.com/stretchr/testify/require" - - "github.com/tendermint/tendermint/libs/log" ) // TestAddListenerForEventFireOnce sets up an EventSwitch, subscribes a single @@ -19,17 +17,11 @@ func TestAddListenerForEventFireOnce(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.NewTestingLogger(t) - - evsw := NewEventSwitch(logger) - require.NoError(t, evsw.Start(ctx)) - t.Cleanup(evsw.Wait) + evsw := NewEventSwitch() messages := make(chan EventData) require.NoError(t, evsw.AddListenerForEvent("listener", "event", - func(ctx context.Context, data EventData) error { - // test there's no deadlock if we remove the listener inside a callback - evsw.RemoveListener("listener") + func(data EventData) error { select { case messages <- data: return nil @@ -37,7 +29,7 @@ func TestAddListenerForEventFireOnce(t *testing.T) { return ctx.Err() } })) - go evsw.FireEvent(ctx, "event", "data") + go evsw.FireEvent("event", "data") received := <-messages if received != "data" { t.Errorf("message received does not match: %v", received) @@ -50,18 +42,14 @@ func TestAddListenerForEventFireMany(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.NewTestingLogger(t) - - evsw := NewEventSwitch(logger) - require.NoError(t, evsw.Start(ctx)) - t.Cleanup(evsw.Wait) + evsw := NewEventSwitch() doneSum := make(chan uint64) doneSending := make(chan uint64) numbers := make(chan uint64, 4) // subscribe one listener for one event require.NoError(t, evsw.AddListenerForEvent("listener", "event", - func(ctx context.Context, data EventData) error { + func(data EventData) error { select { case numbers <- data.(uint64): return nil @@ -88,11 +76,9 @@ func TestAddListenerForDifferentEvents(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.NewTestingLogger(t) + t.Cleanup(leaktest.Check(t)) - evsw := NewEventSwitch(logger) - require.NoError(t, evsw.Start(ctx)) - t.Cleanup(evsw.Wait) + evsw := NewEventSwitch() doneSum := make(chan uint64) doneSending1 := make(chan uint64) @@ -101,7 +87,7 @@ func TestAddListenerForDifferentEvents(t *testing.T) { numbers := make(chan uint64, 4) // subscribe one listener to three events require.NoError(t, evsw.AddListenerForEvent("listener", "event1", - func(ctx context.Context, data EventData) error { + func(data EventData) error { select { case numbers <- data.(uint64): return nil @@ -110,7 +96,7 @@ func TestAddListenerForDifferentEvents(t *testing.T) { } })) require.NoError(t, evsw.AddListenerForEvent("listener", "event2", - func(ctx context.Context, data EventData) error { + func(data EventData) error { select { case numbers <- data.(uint64): return nil @@ -119,7 +105,7 @@ func TestAddListenerForDifferentEvents(t *testing.T) { } })) require.NoError(t, evsw.AddListenerForEvent("listener", "event3", - func(ctx context.Context, data EventData) error { + func(data EventData) error { select { case numbers <- data.(uint64): return nil @@ -152,11 +138,9 @@ func TestAddDifferentListenerForDifferentEvents(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.NewTestingLogger(t) - evsw := NewEventSwitch(logger) - require.NoError(t, evsw.Start(ctx)) + t.Cleanup(leaktest.Check(t)) - t.Cleanup(evsw.Wait) + evsw := NewEventSwitch() doneSum1 := make(chan uint64) doneSum2 := make(chan uint64) @@ -167,7 +151,7 @@ func TestAddDifferentListenerForDifferentEvents(t *testing.T) { numbers2 := make(chan uint64, 4) // subscribe two listener to three events require.NoError(t, evsw.AddListenerForEvent("listener1", "event1", - func(ctx context.Context, data EventData) error { + func(data EventData) error { select { case numbers1 <- data.(uint64): return nil @@ -176,7 +160,7 @@ func TestAddDifferentListenerForDifferentEvents(t *testing.T) { } })) require.NoError(t, evsw.AddListenerForEvent("listener1", "event2", - func(ctx context.Context, data EventData) error { + func(data EventData) error { select { case numbers1 <- data.(uint64): return nil @@ -185,7 +169,7 @@ func TestAddDifferentListenerForDifferentEvents(t *testing.T) { } })) require.NoError(t, evsw.AddListenerForEvent("listener1", "event3", - func(ctx context.Context, data EventData) error { + func(data EventData) error { select { case numbers1 <- data.(uint64): return nil @@ -194,7 +178,7 @@ func TestAddDifferentListenerForDifferentEvents(t *testing.T) { } })) require.NoError(t, evsw.AddListenerForEvent("listener2", "event2", - func(ctx context.Context, data EventData) error { + func(data EventData) error { select { case numbers2 <- data.(uint64): return nil @@ -203,7 +187,7 @@ func TestAddDifferentListenerForDifferentEvents(t *testing.T) { } })) require.NoError(t, evsw.AddListenerForEvent("listener2", "event3", - func(ctx context.Context, data EventData) error { + func(data EventData) error { select { case numbers2 <- data.(uint64): return nil @@ -234,171 +218,7 @@ func TestAddDifferentListenerForDifferentEvents(t *testing.T) { } } -func TestAddAndRemoveListenerConcurrency(t *testing.T) { - var ( - stopInputEvent = false - roundCount = 2000 - ) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - logger := log.NewTestingLogger(t) - - evsw := NewEventSwitch(logger) - require.NoError(t, evsw.Start(ctx)) - t.Cleanup(evsw.Wait) - - done1 := make(chan struct{}) - done2 := make(chan struct{}) - - // Must be executed concurrently to uncover the data race. - // 1. RemoveListener - go func() { - defer close(done1) - for i := 0; i < roundCount; i++ { - evsw.RemoveListener("listener") - } - }() - - // 2. AddListenerForEvent - go func() { - defer close(done2) - for i := 0; i < roundCount; i++ { - index := i - // we explicitly ignore errors here, since the listener will sometimes be removed - // (that's what we're testing) - _ = evsw.AddListenerForEvent("listener", fmt.Sprintf("event%d", index), - func(ctx context.Context, data EventData) error { - t.Errorf("should not run callback for %d.\n", index) - stopInputEvent = true - return nil - }) - } - }() - - <-done1 - <-done2 - - evsw.RemoveListener("listener") // remove the last listener - - for i := 0; i < roundCount && !stopInputEvent; i++ { - evsw.FireEvent(ctx, fmt.Sprintf("event%d", i), uint64(1001)) - } -} - -// TestAddAndRemoveListener sets up an EventSwitch, subscribes a listener to -// two events, fires a thousand integers for the first event, then unsubscribes -// the listener and fires a thousand integers for the second event. -func TestAddAndRemoveListener(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - logger := log.NewTestingLogger(t) - evsw := NewEventSwitch(logger) - require.NoError(t, evsw.Start(ctx)) - t.Cleanup(evsw.Wait) - - doneSum1 := make(chan uint64) - doneSum2 := make(chan uint64) - doneSending1 := make(chan uint64) - doneSending2 := make(chan uint64) - numbers1 := make(chan uint64, 4) - numbers2 := make(chan uint64, 4) - // subscribe two listener to three events - require.NoError(t, evsw.AddListenerForEvent("listener", "event1", - func(ctx context.Context, data EventData) error { - select { - case numbers1 <- data.(uint64): - return nil - case <-ctx.Done(): - return ctx.Err() - } - })) - require.NoError(t, evsw.AddListenerForEvent("listener", "event2", - func(ctx context.Context, data EventData) error { - select { - case numbers2 <- data.(uint64): - return nil - case <-ctx.Done(): - return ctx.Err() - } - })) - // collect received events for event1 - go sumReceivedNumbers(numbers1, doneSum1) - // collect received events for event2 - go sumReceivedNumbers(numbers2, doneSum2) - // go fire events - go fireEvents(ctx, evsw, "event1", doneSending1, uint64(1)) - checkSumEvent1 := <-doneSending1 - // after sending all event1, unsubscribe for all events - evsw.RemoveListener("listener") - go fireEvents(ctx, evsw, "event2", doneSending2, uint64(1001)) - checkSumEvent2 := <-doneSending2 - close(numbers1) - close(numbers2) - eventSum1 := <-doneSum1 - eventSum2 := <-doneSum2 - if checkSumEvent1 != eventSum1 || - // correct value asserted by preceding tests, suffices to be non-zero - checkSumEvent2 == uint64(0) || - eventSum2 != uint64(0) { - t.Errorf("not all messages sent were received or unsubscription did not register.\n") - } -} - -// TestRemoveListener does basic tests on adding and removing -func TestRemoveListener(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - logger := log.NewTestingLogger(t) - - evsw := NewEventSwitch(logger) - require.NoError(t, evsw.Start(ctx)) - t.Cleanup(evsw.Wait) - - count := 10 - sum1, sum2 := 0, 0 - // add some listeners and make sure they work - require.NoError(t, evsw.AddListenerForEvent("listener", "event1", - func(ctx context.Context, data EventData) error { - sum1++ - return nil - })) - require.NoError(t, evsw.AddListenerForEvent("listener", "event2", - func(ctx context.Context, data EventData) error { - sum2++ - return nil - })) - - for i := 0; i < count; i++ { - evsw.FireEvent(ctx, "event1", true) - evsw.FireEvent(ctx, "event2", true) - } - assert.Equal(t, count, sum1) - assert.Equal(t, count, sum2) - - // remove one by event and make sure it is gone - evsw.RemoveListenerForEvent("event2", "listener") - for i := 0; i < count; i++ { - evsw.FireEvent(ctx, "event1", true) - evsw.FireEvent(ctx, "event2", true) - } - assert.Equal(t, count*2, sum1) - assert.Equal(t, count, sum2) - - // remove the listener entirely and make sure both gone - evsw.RemoveListener("listener") - for i := 0; i < count; i++ { - evsw.FireEvent(ctx, "event1", true) - evsw.FireEvent(ctx, "event2", true) - } - assert.Equal(t, count*2, sum1) - assert.Equal(t, count, sum2) -} - -// TestAddAndRemoveListenersAsync sets up an EventSwitch, subscribes two +// TestManagerLiistenersAsync sets up an EventSwitch, subscribes two // listeners to three events, and fires a thousand integers for each event. // These two listeners serve as the baseline validation while other listeners // are randomly subscribed and unsubscribed. @@ -408,14 +228,11 @@ func TestRemoveListener(t *testing.T) { // at that point subscribed to. // NOTE: it is important to run this test with race conditions tracking on, // `go test -race`, to examine for possible race conditions. -func TestRemoveListenersAsync(t *testing.T) { +func TestManageListenersAsync(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.NewTestingLogger(t) - evsw := NewEventSwitch(logger) - require.NoError(t, evsw.Start(ctx)) - t.Cleanup(evsw.Wait) + evsw := NewEventSwitch() doneSum1 := make(chan uint64) doneSum2 := make(chan uint64) @@ -426,7 +243,7 @@ func TestRemoveListenersAsync(t *testing.T) { numbers2 := make(chan uint64, 4) // subscribe two listener to three events require.NoError(t, evsw.AddListenerForEvent("listener1", "event1", - func(ctx context.Context, data EventData) error { + func(data EventData) error { select { case numbers1 <- data.(uint64): return nil @@ -435,7 +252,7 @@ func TestRemoveListenersAsync(t *testing.T) { } })) require.NoError(t, evsw.AddListenerForEvent("listener1", "event2", - func(ctx context.Context, data EventData) error { + func(data EventData) error { select { case numbers1 <- data.(uint64): return nil @@ -444,7 +261,7 @@ func TestRemoveListenersAsync(t *testing.T) { } })) require.NoError(t, evsw.AddListenerForEvent("listener1", "event3", - func(ctx context.Context, data EventData) error { + func(data EventData) error { select { case numbers1 <- data.(uint64): return nil @@ -453,7 +270,7 @@ func TestRemoveListenersAsync(t *testing.T) { } })) require.NoError(t, evsw.AddListenerForEvent("listener2", "event1", - func(ctx context.Context, data EventData) error { + func(data EventData) error { select { case numbers2 <- data.(uint64): return nil @@ -462,7 +279,7 @@ func TestRemoveListenersAsync(t *testing.T) { } })) require.NoError(t, evsw.AddListenerForEvent("listener2", "event2", - func(ctx context.Context, data EventData) error { + func(data EventData) error { select { case numbers2 <- data.(uint64): return nil @@ -471,7 +288,7 @@ func TestRemoveListenersAsync(t *testing.T) { } })) require.NoError(t, evsw.AddListenerForEvent("listener2", "event3", - func(ctx context.Context, data EventData) error { + func(data EventData) error { select { case numbers2 <- data.(uint64): return nil @@ -491,21 +308,12 @@ func TestRemoveListenersAsync(t *testing.T) { eventNumber := r1.Intn(3) + 1 go evsw.AddListenerForEvent(fmt.Sprintf("listener%v", listenerNumber), //nolint:errcheck // ignore for tests fmt.Sprintf("event%v", eventNumber), - func(context.Context, EventData) error { return nil }) - } - } - removeListenersStress := func() { - r2 := rand.New(rand.NewSource(time.Now().Unix())) - r2.Seed(time.Now().UnixNano()) - for k := uint16(0); k < 80; k++ { - listenerNumber := r2.Intn(100) + 3 - go evsw.RemoveListener(fmt.Sprintf("listener%v", listenerNumber)) + func(EventData) error { return nil }) } } addListenersStress() // go fire events go fireEvents(ctx, evsw, "event1", doneSending1, uint64(1)) - removeListenersStress() go fireEvents(ctx, evsw, "event2", doneSending2, uint64(1001)) go fireEvents(ctx, evsw, "event3", doneSending3, uint64(2001)) checkSumEvent1 := <-doneSending1 @@ -555,7 +363,7 @@ func fireEvents(ctx context.Context, evsw Fireable, event string, doneChan chan break } - evsw.FireEvent(ctx, event, i) + evsw.FireEvent(event, i) sentSum += i } diff --git a/libs/log/default.go b/libs/log/default.go index ee729db23..706977659 100644 --- a/libs/log/default.go +++ b/libs/log/default.go @@ -60,17 +60,6 @@ func NewDefaultLogger(format, level string) (Logger, error) { }, nil } -// MustNewDefaultLogger delegates a call NewDefaultLogger where it panics on -// error. -func MustNewDefaultLogger(format, level string) Logger { - logger, err := NewDefaultLogger(format, level) - if err != nil { - panic(err) - } - - return logger -} - func (l defaultLogger) Info(msg string, keyVals ...interface{}) { l.Logger.Info().Fields(getLogFields(keyVals...)).Msg(msg) } diff --git a/libs/log/testing.go b/libs/log/testing.go index 649ebab40..464a7036f 100644 --- a/libs/log/testing.go +++ b/libs/log/testing.go @@ -6,24 +6,6 @@ import ( "github.com/rs/zerolog" ) -// TestingLogger was a legacy constructor that wrote logging output to -// standardoutput when in verbose mode, and no-op'ed test logs -// otherwise. Now it always no-ops, but if you need to see logs from -// tests, you can replace this call with `NewTestingLogger` -// constructor. -func TestingLogger() Logger { - return NewNopLogger() -} - -type testingWriter struct { - t testing.TB -} - -func (tw testingWriter) Write(in []byte) (int, error) { - tw.t.Log(string(in)) - return len(in), nil -} - // NewTestingLogger converts a testing.T into a logging interface to // make test failures and verbose provide better feedback associated // with test failures. This logging instance is safe for use from @@ -58,3 +40,12 @@ func NewTestingLoggerWithLevel(t testing.TB, level string) Logger { Logger: zerolog.New(newSyncWriter(testingWriter{t})).Level(logLevel), } } + +type testingWriter struct { + t testing.TB +} + +func (tw testingWriter) Write(in []byte) (int, error) { + tw.t.Log(string(in)) + return len(in), nil +} diff --git a/libs/service/service.go b/libs/service/service.go index f7701633a..3ce08e7da 100644 --- a/libs/service/service.go +++ b/libs/service/service.go @@ -12,6 +12,8 @@ var ( // errAlreadyStopped is returned when somebody tries to stop an already // stopped service (without resetting it). errAlreadyStopped = errors.New("already stopped") + + _ Service = (*BaseService)(nil) ) // Service defines a service that can be started, stopped, and reset. @@ -70,8 +72,7 @@ Typical usage: } func (fs *FooService) OnStop() { - // close/destroy private fields - // stop subroutines, etc. + // close/destroy private fields and releases resources } */ type BaseService struct { @@ -195,5 +196,5 @@ func (bs *BaseService) getWait() <-chan struct{} { // Wait blocks until the service is stopped. func (bs *BaseService) Wait() { <-bs.getWait() } -// String implements Service by returning a string representation of the service. +// String provides a human-friendly representation of the service. func (bs *BaseService) String() string { return bs.name } diff --git a/libs/time/mocks/source.go b/libs/time/mocks/source.go index a8e49b314..7878d86f5 100644 --- a/libs/time/mocks/source.go +++ b/libs/time/mocks/source.go @@ -3,9 +3,11 @@ package mocks import ( - time "time" + testing "testing" mock "github.com/stretchr/testify/mock" + + time "time" ) // Source is an autogenerated mock type for the Source type @@ -26,3 +28,13 @@ func (_m *Source) Now() time.Time { return r0 } + +// NewSource creates a new instance of Source. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewSource(t testing.TB) *Source { + mock := &Source{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/light/client_benchmark_test.go b/light/client_benchmark_test.go index 4a54e2b54..c92b70990 100644 --- a/light/client_benchmark_test.go +++ b/light/client_benchmark_test.go @@ -69,7 +69,7 @@ func BenchmarkSequence(b *testing.B) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - headers, vals, _ := genLightBlocksWithKeys(b, chainID, 1000, 100, 1, bTime) + headers, vals, _ := genLightBlocksWithKeys(b, 1000, 100, 1, bTime) benchmarkFullNode := newProviderBenchmarkImpl(headers, vals) genesisBlock, _ := benchmarkFullNode.LightBlock(ctx, 1) @@ -106,7 +106,7 @@ func BenchmarkBisection(b *testing.B) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - headers, vals, _ := genLightBlocksWithKeys(b, chainID, 1000, 100, 1, bTime) + headers, vals, _ := genLightBlocksWithKeys(b, 1000, 100, 1, bTime) benchmarkFullNode := newProviderBenchmarkImpl(headers, vals) genesisBlock, _ := benchmarkFullNode.LightBlock(ctx, 1) @@ -142,7 +142,7 @@ func BenchmarkBackwards(b *testing.B) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - headers, vals, _ := genLightBlocksWithKeys(b, chainID, 1000, 100, 1, bTime) + headers, vals, _ := genLightBlocksWithKeys(b, 1000, 100, 1, bTime) benchmarkFullNode := newProviderBenchmarkImpl(headers, vals) trustedBlock, _ := benchmarkFullNode.LightBlock(ctx, 0) diff --git a/light/client_test.go b/light/client_test.go index d2f5a137c..8ecd842c9 100644 --- a/light/client_test.go +++ b/light/client_test.go @@ -224,7 +224,7 @@ func TestClient(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() mockNode := mockNodeFromHeadersAndVals(testCase.otherHeaders, testCase.vals) mockNode.On("LightBlock", mock.Anything, mock.Anything).Return(nil, provider.ErrLightBlockNotFound) @@ -351,7 +351,7 @@ func TestClient(t *testing.T) { t.Run(tc.name, func(t *testing.T) { ctx, cancel := context.WithCancel(bctx) defer cancel() - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() mockNode := mockNodeFromHeadersAndVals(tc.otherHeaders, tc.vals) mockNode.On("LightBlock", mock.Anything, mock.Anything).Return(nil, provider.ErrLightBlockNotFound) @@ -387,7 +387,7 @@ func TestClient(t *testing.T) { // the appropriate range numBlocks := int64(300) - mockHeaders, mockVals, _ := genLightBlocksWithKeys(t, chainID, numBlocks, 101, 2, bTime) + mockHeaders, mockVals, _ := genLightBlocksWithKeys(t, numBlocks, 101, 2, bTime) lastBlock := &types.LightBlock{SignedHeader: mockHeaders[numBlocks], ValidatorSet: mockVals[numBlocks]} mockNode := &provider_mocks.Provider{} @@ -466,7 +466,7 @@ func TestClient(t *testing.T) { t.Run("Cleanup", func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() mockFullNode := &provider_mocks.Provider{} mockFullNode.On("LightBlock", mock.Anything, int64(1)).Return(l1, nil) @@ -503,7 +503,7 @@ func TestClient(t *testing.T) { ctx, cancel := context.WithCancel(bctx) defer cancel() - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() mockNode := &provider_mocks.Provider{} trustedStore := dbs.New(dbm.NewMemDB()) @@ -538,7 +538,7 @@ func TestClient(t *testing.T) { err := trustedStore.SaveLightBlock(l1) require.NoError(t, err) - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() // header1 != h1 header1 := keys.GenSignedHeader(t, chainID, 1, bTime.Add(1*time.Hour), nil, vals, vals, @@ -584,7 +584,7 @@ func TestClient(t *testing.T) { mockWitnessNode.On("LightBlock", mock.Anything, int64(1)).Return(l1, nil) mockWitnessNode.On("LightBlock", mock.Anything, int64(3)).Return(l3, nil) - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() c, err := light.NewClient( ctx, @@ -611,7 +611,7 @@ func TestClient(t *testing.T) { t.Run("Concurrency", func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() mockFullNode := &provider_mocks.Provider{} mockFullNode.On("LightBlock", mock.Anything, int64(2)).Return(l2, nil) @@ -664,7 +664,7 @@ func TestClient(t *testing.T) { 1: h1, 2: h2, }, valSet) - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() c, err := light.NewClient( ctx, @@ -705,7 +705,7 @@ func TestClient(t *testing.T) { mockDeadNode.On("LightBlock", mock.Anything, mock.Anything).Return(nil, provider.ErrNoResponse) mockDeadNode.On("ID").Return("mockDeadNode") - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() c, err := light.NewClient( ctx, @@ -738,7 +738,7 @@ func TestClient(t *testing.T) { mockFullNode.On("LightBlock", mock.Anything, mock.Anything).Return(l1, nil) mockFullNode.On("ID").Return("mockFullNode") - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() mockDeadNode1 := &provider_mocks.Provider{} mockDeadNode1.On("LightBlock", mock.Anything, mock.Anything).Return(nil, provider.ErrLightBlockNotFound) @@ -770,10 +770,10 @@ func TestClient(t *testing.T) { t.Run("BackwardsVerification", func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() { - headers, vals, _ := genLightBlocksWithKeys(t, chainID, 9, 3, 0, bTime) + headers, vals, _ := genLightBlocksWithKeys(t, 9, 3, 0, bTime) delete(headers, 1) delete(headers, 2) delete(vals, 1) @@ -886,7 +886,7 @@ func TestClient(t *testing.T) { mockWitness.AssertExpectations(t) }) t.Run("RemovesWitnessIfItSendsUsIncorrectHeader", func(t *testing.T) { - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() // different headers hash then primary plus less than 1/3 signed (no fork) headers1 := map[int64]*types.SignedHeader{ @@ -959,7 +959,7 @@ func TestClient(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() differentVals, _ := factory.ValidatorSet(ctx, t, 10, 100) mockBadValSetNode := mockNodeFromHeadersAndVals( @@ -1043,7 +1043,7 @@ func TestClient(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() c, err := light.NewClient( ctx, diff --git a/light/detector_test.go b/light/detector_test.go index 2e5304822..4a86b5b87 100644 --- a/light/detector_test.go +++ b/light/detector_test.go @@ -21,7 +21,7 @@ import ( ) func TestLightClientAttackEvidence_Lunatic(t *testing.T) { - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() // primary performs a lunatic attack var ( @@ -35,7 +35,7 @@ func TestLightClientAttackEvidence_Lunatic(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - witnessHeaders, witnessValidators, chainKeys := genLightBlocksWithKeys(t, chainID, latestHeight, valSize, 2, bTime) + witnessHeaders, witnessValidators, chainKeys := genLightBlocksWithKeys(t, latestHeight, valSize, 2, bTime) forgedKeys := chainKeys[divergenceHeight-1].ChangeKeys(3) // we change 3 out of the 5 validators (still 2/5 remain) forgedVals := forgedKeys.ToValidators(2, 0) @@ -144,7 +144,7 @@ func TestLightClientAttackEvidence_Equivocation(t *testing.T) { ctx, cancel := context.WithCancel(bctx) defer cancel() - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() // primary performs an equivocation attack var ( @@ -153,7 +153,7 @@ func TestLightClientAttackEvidence_Equivocation(t *testing.T) { // validators don't change in this network (however we still use a map just for convenience) primaryValidators = make(map[int64]*types.ValidatorSet, testCase.latestHeight) ) - witnessHeaders, witnessValidators, chainKeys := genLightBlocksWithKeys(t, chainID, + witnessHeaders, witnessValidators, chainKeys := genLightBlocksWithKeys(t, testCase.latestHeight+1, valSize, 2, bTime) for height := int64(1); height <= testCase.latestHeight; height++ { if height < testCase.divergenceHeight { @@ -248,9 +248,9 @@ func TestLightClientAttackEvidence_ForwardLunatic(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() - witnessHeaders, witnessValidators, chainKeys := genLightBlocksWithKeys(t, chainID, latestHeight, valSize, 2, bTime) + witnessHeaders, witnessValidators, chainKeys := genLightBlocksWithKeys(t, latestHeight, valSize, 2, bTime) for _, unusedHeader := range []int64{3, 5, 6, 8} { delete(witnessHeaders, unusedHeader) } @@ -401,17 +401,17 @@ func TestClientDivergentTraces1(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - headers, vals, _ := genLightBlocksWithKeys(t, chainID, 1, 5, 2, bTime) + headers, vals, _ := genLightBlocksWithKeys(t, 1, 5, 2, bTime) mockPrimary := mockNodeFromHeadersAndVals(headers, vals) mockPrimary.On("ID").Return("mockPrimary") firstBlock, err := mockPrimary.LightBlock(ctx, 1) require.NoError(t, err) - headers, vals, _ = genLightBlocksWithKeys(t, chainID, 1, 5, 2, bTime) + headers, vals, _ = genLightBlocksWithKeys(t, 1, 5, 2, bTime) mockWitness := mockNodeFromHeadersAndVals(headers, vals) mockWitness.On("ID").Return("mockWitness") - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() _, err = light.NewClient( ctx, @@ -437,9 +437,9 @@ func TestClientDivergentTraces1(t *testing.T) { func TestClientDivergentTraces2(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() - headers, vals, _ := genLightBlocksWithKeys(t, chainID, 2, 5, 2, bTime) + headers, vals, _ := genLightBlocksWithKeys(t, 2, 5, 2, bTime) mockPrimaryNode := mockNodeFromHeadersAndVals(headers, vals) mockPrimaryNode.On("ID").Return("mockPrimaryNode") @@ -482,10 +482,10 @@ func TestClientDivergentTraces2(t *testing.T) { // => creation should succeed, but the verification should fail //nolint: dupl func TestClientDivergentTraces3(t *testing.T) { - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() // - primaryHeaders, primaryVals, _ := genLightBlocksWithKeys(t, chainID, 2, 5, 2, bTime) + primaryHeaders, primaryVals, _ := genLightBlocksWithKeys(t, 2, 5, 2, bTime) mockPrimary := mockNodeFromHeadersAndVals(primaryHeaders, primaryVals) mockPrimary.On("ID").Return("mockPrimary") @@ -495,7 +495,7 @@ func TestClientDivergentTraces3(t *testing.T) { firstBlock, err := mockPrimary.LightBlock(ctx, 1) require.NoError(t, err) - mockHeaders, mockVals, _ := genLightBlocksWithKeys(t, chainID, 2, 5, 2, bTime) + mockHeaders, mockVals, _ := genLightBlocksWithKeys(t, 2, 5, 2, bTime) mockHeaders[1] = primaryHeaders[1] mockVals[1] = primaryVals[1] mockWitness := mockNodeFromHeadersAndVals(mockHeaders, mockVals) @@ -527,10 +527,10 @@ func TestClientDivergentTraces3(t *testing.T) { // It should be ignored //nolint: dupl func TestClientDivergentTraces4(t *testing.T) { - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() // - primaryHeaders, primaryVals, _ := genLightBlocksWithKeys(t, chainID, 2, 5, 2, bTime) + primaryHeaders, primaryVals, _ := genLightBlocksWithKeys(t, 2, 5, 2, bTime) mockPrimary := mockNodeFromHeadersAndVals(primaryHeaders, primaryVals) mockPrimary.On("ID").Return("mockPrimary") @@ -540,7 +540,7 @@ func TestClientDivergentTraces4(t *testing.T) { firstBlock, err := mockPrimary.LightBlock(ctx, 1) require.NoError(t, err) - witnessHeaders, witnessVals, _ := genLightBlocksWithKeys(t, chainID, 2, 5, 2, bTime) + witnessHeaders, witnessVals, _ := genLightBlocksWithKeys(t, 2, 5, 2, bTime) primaryHeaders[2] = witnessHeaders[2] primaryVals[2] = witnessVals[2] mockWitness := mockNodeFromHeadersAndVals(primaryHeaders, primaryVals) diff --git a/light/helpers_test.go b/light/helpers_test.go index 37b4b5bf3..d93735bb7 100644 --- a/light/helpers_test.go +++ b/light/helpers_test.go @@ -9,7 +9,6 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/tmhash" tmtime "github.com/tendermint/tendermint/libs/time" provider_mocks "github.com/tendermint/tendermint/light/provider/mocks" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -35,35 +34,12 @@ func genPrivKeys(n int) privKeys { return res } -// // Change replaces the key at index i. -// func (pkz privKeys) Change(i int) privKeys { -// res := make(privKeys, len(pkz)) -// copy(res, pkz) -// res[i] = ed25519.GenPrivKey() -// return res -// } - // Extend adds n more keys (to remove, just take a slice). func (pkz privKeys) Extend(n int) privKeys { extra := genPrivKeys(n) return append(pkz, extra...) } -// // GenSecpPrivKeys produces an array of secp256k1 private keys to generate commits. -// func GenSecpPrivKeys(n int) privKeys { -// res := make(privKeys, n) -// for i := range res { -// res[i] = secp256k1.GenPrivKey() -// } -// return res -// } - -// // ExtendSecp adds n more secp256k1 keys (to remove, just take a slice). -// func (pkz privKeys) ExtendSecp(n int) privKeys { -// extra := GenSecpPrivKeys(n) -// return append(pkz, extra...) -// } - // ToValidators produces a valset from the set of keys. // The first key has weight `init` and it increases by `inc` every step // so we can have all the same weight, or a simple linear distribution @@ -183,7 +159,6 @@ func (pkz privKeys) ChangeKeys(delta int) privKeys { // NOTE: Expected to have a large validator set size ~ 100 validators. func genLightBlocksWithKeys( t testing.TB, - chainID string, numBlocks int64, valSize int, valVariation float32, @@ -246,5 +221,5 @@ func mockNodeFromHeadersAndVals(headers map[int64]*types.SignedHeader, } func hash(s string) []byte { - return tmhash.Sum([]byte(s)) + return crypto.Checksum([]byte(s)) } diff --git a/light/light_test.go b/light/light_test.go index 35e6d1933..58fd9f0b1 100644 --- a/light/light_test.go +++ b/light/light_test.go @@ -30,7 +30,7 @@ func TestClientIntegration_Update(t *testing.T) { conf, err := rpctest.CreateConfig(t, t.Name()) require.NoError(t, err) - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() // Start a test application app := kvstore.NewApplication() @@ -90,7 +90,7 @@ func TestClientIntegration_VerifyLightBlockAtHeight(t *testing.T) { conf, err := rpctest.CreateConfig(t, t.Name()) require.NoError(t, err) - logger := log.NewTestingLogger(t) + logger := log.NewNopLogger() // Start a test application app := kvstore.NewApplication() @@ -201,7 +201,7 @@ func TestClientStatusRPC(t *testing.T) { primary, witnesses, dbs.New(db), - light.Logger(log.TestingLogger()), + light.Logger(log.NewNopLogger()), ) require.NoError(t, err) diff --git a/light/provider/mocks/provider.go b/light/provider/mocks/provider.go index 1b4e583de..e136046f9 100644 --- a/light/provider/mocks/provider.go +++ b/light/provider/mocks/provider.go @@ -7,6 +7,8 @@ import ( mock "github.com/stretchr/testify/mock" + testing "testing" + types "github.com/tendermint/tendermint/types" ) @@ -65,3 +67,13 @@ func (_m *Provider) ReportEvidence(_a0 context.Context, _a1 types.Evidence) erro return r0 } + +// NewProvider creates a new instance of Provider. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewProvider(t testing.TB) *Provider { + mock := &Provider{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/light/proxy/routes.go b/light/proxy/routes.go index 5dd934ed1..8331723e7 100644 --- a/light/proxy/routes.go +++ b/light/proxy/routes.go @@ -2,60 +2,148 @@ package proxy import ( "context" - "time" - "github.com/tendermint/tendermint/internal/eventlog/cursor" - tmbytes "github.com/tendermint/tendermint/libs/bytes" lrpc "github.com/tendermint/tendermint/light/rpc" rpcclient "github.com/tendermint/tendermint/rpc/client" "github.com/tendermint/tendermint/rpc/coretypes" ) // proxyService wraps a light RPC client to export the RPC service interfaces. -// This is needed because the service and the client use different signatures -// for some of the methods. +// The interfaces are implemented by delegating to the underlying node via the +// specified client. type proxyService struct { - *lrpc.Client + Client *lrpc.Client } -func (p proxyService) ABCIQuery(ctx context.Context, path string, data tmbytes.HexBytes, height int64, prove bool) (*coretypes.ResultABCIQuery, error) { - return p.ABCIQueryWithOptions(ctx, path, data, rpcclient.ABCIQueryOptions{ - Height: height, - Prove: prove, +func (p proxyService) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, error) { panic("ok") } + +func (p proxyService) ABCIQuery(ctx context.Context, req *coretypes.RequestABCIQuery) (*coretypes.ResultABCIQuery, error) { + return p.Client.ABCIQueryWithOptions(ctx, req.Path, req.Data, rpcclient.ABCIQueryOptions{ + Height: int64(req.Height), + Prove: req.Prove, }) } +func (p proxyService) Block(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlock, error) { + return p.Client.Block(ctx, (*int64)(req.Height)) +} + +func (p proxyService) BlockByHash(ctx context.Context, req *coretypes.RequestBlockByHash) (*coretypes.ResultBlock, error) { + return p.Client.BlockByHash(ctx, req.Hash) +} + +func (p proxyService) BlockResults(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlockResults, error) { + return p.Client.BlockResults(ctx, (*int64)(req.Height)) +} + +func (p proxyService) BlockSearch(ctx context.Context, req *coretypes.RequestBlockSearch) (*coretypes.ResultBlockSearch, error) { + return p.Client.BlockSearch(ctx, req.Query, req.Page.IntPtr(), req.PerPage.IntPtr(), req.OrderBy) +} + +func (p proxyService) BlockchainInfo(ctx context.Context, req *coretypes.RequestBlockchainInfo) (*coretypes.ResultBlockchainInfo, error) { + return p.Client.BlockchainInfo(ctx, int64(req.MinHeight), int64(req.MaxHeight)) +} + +func (p proxyService) BroadcastEvidence(ctx context.Context, req *coretypes.RequestBroadcastEvidence) (*coretypes.ResultBroadcastEvidence, error) { + return p.Client.BroadcastEvidence(ctx, req.Evidence) +} + +func (p proxyService) BroadcastTxAsync(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error) { + return p.Client.BroadcastTxAsync(ctx, req.Tx) +} + +func (p proxyService) BroadcastTxCommit(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTxCommit, error) { + return p.Client.BroadcastTxCommit(ctx, req.Tx) +} + +func (p proxyService) BroadcastTxSync(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error) { + return p.Client.BroadcastTxSync(ctx, req.Tx) +} + +func (p proxyService) CheckTx(ctx context.Context, req *coretypes.RequestCheckTx) (*coretypes.ResultCheckTx, error) { + return p.Client.CheckTx(ctx, req.Tx) +} + +func (p proxyService) Commit(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultCommit, error) { + return p.Client.Commit(ctx, (*int64)(req.Height)) +} + +func (p proxyService) ConsensusParams(ctx context.Context, req *coretypes.RequestConsensusParams) (*coretypes.ResultConsensusParams, error) { + return p.Client.ConsensusParams(ctx, (*int64)(req.Height)) +} + +func (p proxyService) DumpConsensusState(ctx context.Context) (*coretypes.ResultDumpConsensusState, error) { + return p.Client.DumpConsensusState(ctx) +} + +func (p proxyService) Events(ctx context.Context, req *coretypes.RequestEvents) (*coretypes.ResultEvents, error) { + return p.Client.Events(ctx, req) +} + +func (p proxyService) Genesis(ctx context.Context) (*coretypes.ResultGenesis, error) { + return p.Client.Genesis(ctx) +} + +func (p proxyService) GenesisChunked(ctx context.Context, req *coretypes.RequestGenesisChunked) (*coretypes.ResultGenesisChunk, error) { + return p.Client.GenesisChunked(ctx, uint(req.Chunk)) +} + func (p proxyService) GetConsensusState(ctx context.Context) (*coretypes.ResultConsensusState, error) { - return p.ConsensusState(ctx) + return p.Client.ConsensusState(ctx) } -func (p proxyService) Events(ctx context.Context, - filter *coretypes.EventFilter, - maxItems int, - before, after cursor.Cursor, - waitTime time.Duration, -) (*coretypes.ResultEvents, error) { - return p.Client.Events(ctx, &coretypes.RequestEvents{ - Filter: filter, - MaxItems: maxItems, - Before: before.String(), - After: after.String(), - WaitTime: waitTime, - }) +func (p proxyService) Header(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultHeader, error) { + return p.Client.Header(ctx, (*int64)(req.Height)) } -func (p proxyService) Subscribe(ctx context.Context, query string) (*coretypes.ResultSubscribe, error) { - return p.SubscribeWS(ctx, query) +func (p proxyService) HeaderByHash(ctx context.Context, req *coretypes.RequestBlockByHash) (*coretypes.ResultHeader, error) { + return p.Client.HeaderByHash(ctx, req.Hash) } -func (p proxyService) Unsubscribe(ctx context.Context, query string) (*coretypes.ResultUnsubscribe, error) { - return p.UnsubscribeWS(ctx, query) +func (p proxyService) Health(ctx context.Context) (*coretypes.ResultHealth, error) { + return p.Client.Health(ctx) +} + +func (p proxyService) NetInfo(ctx context.Context) (*coretypes.ResultNetInfo, error) { + return p.Client.NetInfo(ctx) +} + +func (p proxyService) NumUnconfirmedTxs(ctx context.Context) (*coretypes.ResultUnconfirmedTxs, error) { + return p.Client.NumUnconfirmedTxs(ctx) +} + +func (p proxyService) RemoveTx(ctx context.Context, req *coretypes.RequestRemoveTx) error { + return p.Client.RemoveTx(ctx, req.TxKey) +} + +func (p proxyService) Status(ctx context.Context) (*coretypes.ResultStatus, error) { + return p.Client.Status(ctx) +} + +func (p proxyService) Subscribe(ctx context.Context, req *coretypes.RequestSubscribe) (*coretypes.ResultSubscribe, error) { + return p.Client.SubscribeWS(ctx, req.Query) +} + +func (p proxyService) Tx(ctx context.Context, req *coretypes.RequestTx) (*coretypes.ResultTx, error) { + return p.Client.Tx(ctx, req.Hash, req.Prove) +} + +func (p proxyService) TxSearch(ctx context.Context, req *coretypes.RequestTxSearch) (*coretypes.ResultTxSearch, error) { + return p.Client.TxSearch(ctx, req.Query, req.Prove, req.Page.IntPtr(), req.PerPage.IntPtr(), req.OrderBy) +} + +func (p proxyService) UnconfirmedTxs(ctx context.Context, req *coretypes.RequestUnconfirmedTxs) (*coretypes.ResultUnconfirmedTxs, error) { + return p.Client.UnconfirmedTxs(ctx, req.Page.IntPtr(), req.PerPage.IntPtr()) +} + +func (p proxyService) Unsubscribe(ctx context.Context, req *coretypes.RequestUnsubscribe) (*coretypes.ResultUnsubscribe, error) { + return p.Client.UnsubscribeWS(ctx, req.Query) } func (p proxyService) UnsubscribeAll(ctx context.Context) (*coretypes.ResultUnsubscribe, error) { - return p.UnsubscribeAllWS(ctx) + return p.Client.UnsubscribeAllWS(ctx) } -func (p proxyService) BroadcastEvidence(ctx context.Context, ev coretypes.Evidence) (*coretypes.ResultBroadcastEvidence, error) { - return p.Client.BroadcastEvidence(ctx, ev.Value) +func (p proxyService) Validators(ctx context.Context, req *coretypes.RequestValidators) (*coretypes.ResultValidators, error) { + return p.Client.Validators(ctx, (*int64)(req.Height), req.Page.IntPtr(), req.PerPage.IntPtr()) } diff --git a/light/rpc/client.go b/light/rpc/client.go index 54b0a6258..aedf15050 100644 --- a/light/rpc/client.go +++ b/light/rpc/client.go @@ -51,7 +51,6 @@ type Client struct { keyPathFn KeyPathFunc closers []func() - quitCh chan struct{} } var _ rpcclient.Client = (*Client)(nil) @@ -92,10 +91,9 @@ func DefaultMerkleKeyPathFn() KeyPathFunc { // NewClient returns a new client. func NewClient(logger log.Logger, next rpcclient.Client, lc LightClient, opts ...Option) *Client { c := &Client{ - next: next, - lc: lc, - prt: merkle.DefaultProofRuntime(), - quitCh: make(chan struct{}), + next: next, + lc: lc, + prt: merkle.DefaultProofRuntime(), } c.BaseService = *service.NewBaseService(logger, "Client", c) for _, o := range opts { @@ -111,10 +109,6 @@ func (c *Client) OnStart(ctx context.Context) error { return err } c.closers = append(c.closers, ncancel) - go func() { - defer close(c.quitCh) - c.Wait() - }() return nil } @@ -458,16 +452,17 @@ func (c *Client) BlockResults(ctx context.Context, height *int64) (*coretypes.Re return nil, err } - // Build a Merkle tree of proto-encoded FinalizeBlock tx results and get a hash. - results := types.NewResults(res.TxsResults) - // Build a Merkle tree out of the slice. - rH := merkle.HashFromByteSlices([][]byte{bbeBytes, results.Hash()}) + rs, err := abci.MarshalTxResults(res.TxsResults) + if err != nil { + return nil, err + } + mh := merkle.HashFromByteSlices(append([][]byte{bbeBytes}, rs...)) // Verify block results. - if !bytes.Equal(rH, trustedBlock.LastResultsHash) { + if !bytes.Equal(mh, trustedBlock.LastResultsHash) { return nil, fmt.Errorf("last results %X does not match with trusted last results %X", - rH, trustedBlock.LastResultsHash) + mh, trustedBlock.LastResultsHash) } return res, nil diff --git a/light/rpc/mocks/light_client.go b/light/rpc/mocks/light_client.go index 347d14707..ea6d6a2d4 100644 --- a/light/rpc/mocks/light_client.go +++ b/light/rpc/mocks/light_client.go @@ -7,6 +7,8 @@ import ( mock "github.com/stretchr/testify/mock" + testing "testing" + time "time" types "github.com/tendermint/tendermint/types" @@ -115,3 +117,13 @@ func (_m *LightClient) VerifyLightBlockAtHeight(ctx context.Context, height int6 return r0, r1 } + +// NewLightClient creates a new instance of LightClient. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewLightClient(t testing.TB) *LightClient { + mock := &LightClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/light/store/db/db_test.go b/light/store/db/db_test.go index 7069eb11d..cae9bbfc5 100644 --- a/light/store/db/db_test.go +++ b/light/store/db/db_test.go @@ -11,7 +11,6 @@ import ( dbm "github.com/tendermint/tm-db" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/test/factory" tmrand "github.com/tendermint/tendermint/libs/rand" "github.com/tendermint/tendermint/types" @@ -205,14 +204,14 @@ func randLightBlock(ctx context.Context, t *testing.T, height int64) *types.Ligh Height: height, Time: time.Now(), LastBlockID: types.BlockID{}, - LastCommitHash: crypto.CRandBytes(tmhash.Size), - DataHash: crypto.CRandBytes(tmhash.Size), - ValidatorsHash: crypto.CRandBytes(tmhash.Size), - NextValidatorsHash: crypto.CRandBytes(tmhash.Size), - ConsensusHash: crypto.CRandBytes(tmhash.Size), - AppHash: crypto.CRandBytes(tmhash.Size), - LastResultsHash: crypto.CRandBytes(tmhash.Size), - EvidenceHash: crypto.CRandBytes(tmhash.Size), + LastCommitHash: crypto.CRandBytes(crypto.HashSize), + DataHash: crypto.CRandBytes(crypto.HashSize), + ValidatorsHash: crypto.CRandBytes(crypto.HashSize), + NextValidatorsHash: crypto.CRandBytes(crypto.HashSize), + ConsensusHash: crypto.CRandBytes(crypto.HashSize), + AppHash: crypto.CRandBytes(crypto.HashSize), + LastResultsHash: crypto.CRandBytes(crypto.HashSize), + EvidenceHash: crypto.CRandBytes(crypto.HashSize), ProposerAddress: crypto.CRandBytes(crypto.AddressSize), }, Commit: &types.Commit{}, diff --git a/light/trust_options.go b/light/trust_options.go index cbf3b1cd8..2bfad9857 100644 --- a/light/trust_options.go +++ b/light/trust_options.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" ) // TrustOptions are the trust parameters needed when a new light client @@ -43,9 +43,9 @@ func (opts TrustOptions) ValidateBasic() error { if opts.Height <= 0 { return errors.New("negative or zero height") } - if len(opts.Hash) != tmhash.Size { + if len(opts.Hash) != crypto.HashSize { return fmt.Errorf("expected hash size to be %d bytes, got %d bytes", - tmhash.Size, + crypto.HashSize, len(opts.Hash), ) } diff --git a/networks/local/README.md b/networks/local/README.md index dcb31ae71..10fc19932 100644 --- a/networks/local/README.md +++ b/networks/local/README.md @@ -1,3 +1,3 @@ # Local Cluster with Docker Compose -See the [docs](https://docs.tendermint.com/master/networks/docker-compose.html). +See the [docs](https://docs.tendermint.com/master/tools/docker-compose.html). diff --git a/node/node.go b/node/node.go index e57b7c4db..56379d2e2 100644 --- a/node/node.go +++ b/node/node.go @@ -29,6 +29,7 @@ import ( rpccore "github.com/tendermint/tendermint/internal/rpc/core" sm "github.com/tendermint/tendermint/internal/state" "github.com/tendermint/tendermint/internal/state/indexer" + "github.com/tendermint/tendermint/internal/state/indexer/sink" "github.com/tendermint/tendermint/internal/statesync" "github.com/tendermint/tendermint/internal/store" "github.com/tendermint/tendermint/libs/log" @@ -60,17 +61,17 @@ type nodeImpl struct { nodeKey types.NodeKey // our node privkey // services - eventSinks []indexer.EventSink - stateStore sm.Store - blockStore *store.BlockStore // store the blockchain to disk - stateSync bool // whether the node should state sync on startup - stateSyncReactor *statesync.Reactor // for hosting and restoring state sync snapshots - - services []service.Service - rpcListeners []net.Listener // rpc servers - shutdownOps closer - rpcEnv *rpccore.Environment - prometheusSrv *http.Server + eventSinks []indexer.EventSink + initialState sm.State + stateStore sm.Store + blockStore *store.BlockStore // store the blockchain to disk + evPool *evidence.Pool + indexerService *indexer.Service + services []service.Service + rpcListeners []net.Listener // rpc servers + shutdownOps closer + rpcEnv *rpccore.Environment + prometheusSrv *http.Server } // newDefaultNode returns a Tendermint node with default settings for the @@ -87,12 +88,11 @@ func newDefaultNode( } if cfg.Mode == config.ModeSeed { return makeSeedNode( - ctx, + logger, cfg, config.DefaultDBProvider, nodeKey, defaultGenesisDocProviderFunc(cfg), - logger, ) } pval, err := makeDefaultPrivval(cfg) @@ -157,20 +157,8 @@ func makeNode( nodeMetrics := defaultMetricsProvider(cfg.Instrumentation)(genDoc.ChainID) - // Create the proxyApp and establish connections to the ABCI app (consensus, mempool, query). proxyApp := proxy.New(client, logger.With("module", "proxy"), nodeMetrics.proxy) - if err := proxyApp.Start(ctx); err != nil { - return nil, fmt.Errorf("error starting proxy app connections: %w", err) - } - - // EventBus and IndexerService must be started before the handshake because - // we might need to index the txs of the replayed block as this might not have happened - // when the node stopped last time (i.e. the node stopped or crashed after it saved the block - // but before it indexed the txs) eventBus := eventbus.NewDefault(logger.With("module", "events")) - if err := eventBus.Start(ctx); err != nil { - return nil, combineCloseError(err, makeCloser(closers)) - } var eventLog *eventlog.Log if w := cfg.RPC.EventLogWindowSize; w > 0 { @@ -181,17 +169,19 @@ func makeNode( Metrics: nodeMetrics.eventlog, }) if err != nil { - return nil, fmt.Errorf("initializing event log: %w", err) + return nil, combineCloseError(fmt.Errorf("initializing event log: %w", err), makeCloser(closers)) } } - - indexerService, eventSinks, err := createAndStartIndexerService( - ctx, cfg, dbProvider, eventBus, - logger, genDoc.ChainID, nodeMetrics.indexer) + eventSinks, err := sink.EventSinksFromConfig(cfg, dbProvider, genDoc.ChainID) if err != nil { return nil, combineCloseError(err, makeCloser(closers)) } - closers = append(closers, func() error { indexerService.Stop(); return nil }) + indexerService := indexer.NewService(indexer.ServiceArgs{ + Sinks: eventSinks, + EventBus: eventBus, + Logger: logger.With("module", "txindex"), + Metrics: nodeMetrics.indexer, + }) privValidator, err := createPrivval(ctx, logger, cfg, genDoc, filePrivval) if err != nil { @@ -213,43 +203,6 @@ func makeNode( } } - // Determine whether we should attempt state sync. - stateSync := cfg.StateSync.Enable && !onlyValidatorIsUs(state, pubKey) - if stateSync && state.LastBlockHeight > 0 { - logger.Info("Found local state with non-zero height, skipping state sync") - stateSync = false - } - - // Create the handshaker, which calls RequestInfo, sets the AppVersion on the state, - // and replays any blocks as necessary to sync tendermint with the app. - if !stateSync { - if err := consensus.NewHandshaker( - logger.With("module", "handshaker"), - stateStore, state, blockStore, eventBus, genDoc, - ).Handshake(ctx, proxyApp); err != nil { - return nil, combineCloseError(err, makeCloser(closers)) - } - - // Reload the state. It will have the Version.Consensus.App set by the - // Handshake, and may have other modifications as well (ie. depending on - // what happened during block replay). - state, err = stateStore.Load() - if err != nil { - return nil, combineCloseError( - fmt.Errorf("cannot load state: %w", err), - makeCloser(closers)) - } - } - - logNodeStartupInfo(state, pubKey, logger, cfg.Mode) - - // TODO: Fetch and provide real options and do proper p2p bootstrapping. - // TODO: Use a persistent peer database. - nodeInfo, err := makeNodeInfo(cfg, nodeKey, eventSinks, genDoc, state) - if err != nil { - return nil, combineCloseError(err, makeCloser(closers)) - } - peerManager, peerCloser, err := createPeerManager(cfg, dbProvider, nodeKey.ID) closers = append(closers, peerCloser) if err != nil { @@ -258,27 +211,64 @@ func makeNode( makeCloser(closers)) } - router, err := createRouter(ctx, logger, nodeMetrics.p2p, nodeInfo, nodeKey, - peerManager, cfg, proxyApp) + // TODO construct node here: + node := &nodeImpl{ + config: cfg, + logger: logger, + genesisDoc: genDoc, + privValidator: privValidator, + + peerManager: peerManager, + nodeKey: nodeKey, + + eventSinks: eventSinks, + indexerService: indexerService, + services: []service.Service{eventBus}, + + initialState: state, + stateStore: stateStore, + blockStore: blockStore, + + shutdownOps: makeCloser(closers), + + rpcEnv: &rpccore.Environment{ + ProxyApp: proxyApp, + + StateStore: stateStore, + BlockStore: blockStore, + + PeerManager: peerManager, + + GenDoc: genDoc, + EventSinks: eventSinks, + EventBus: eventBus, + EventLog: eventLog, + Logger: logger.With("module", "rpc"), + Config: *cfg.RPC, + }, + } + + node.router, err = createRouter(logger, nodeMetrics.p2p, node.NodeInfo, nodeKey, peerManager, cfg, proxyApp) if err != nil { return nil, combineCloseError( fmt.Errorf("failed to create router: %w", err), makeCloser(closers)) } - mpReactor, mp, err := createMempoolReactor(ctx, - cfg, proxyApp, stateStore, nodeMetrics.mempool, peerManager, router, logger, - ) + evReactor, evPool, edbCloser, err := createEvidenceReactor(logger, cfg, dbProvider, + stateStore, blockStore, peerManager.Subscribe, node.router.OpenChannel, nodeMetrics.evidence, eventBus) + closers = append(closers, edbCloser) if err != nil { return nil, combineCloseError(err, makeCloser(closers)) } + node.services = append(node.services, evReactor) + node.rpcEnv.EvidencePool = evPool + node.evPool = evPool - evReactor, evPool, err := createEvidenceReactor(ctx, - cfg, dbProvider, stateStore, blockStore, peerManager, router, logger, nodeMetrics.evidence, eventBus, - ) - if err != nil { - return nil, combineCloseError(err, makeCloser(closers)) - } + mpReactor, mp := createMempoolReactor(logger, cfg, proxyApp, stateStore, nodeMetrics.mempool, + peerManager.Subscribe, node.router.OpenChannel, peerManager.GetHeight) + node.rpcEnv.Mempool = mp + node.services = append(node.services, mpReactor) // make block executor for consensus and blockchain reactors to execute blocks blockExec := sm.NewBlockExecutor( @@ -288,41 +278,66 @@ func makeNode( mp, evPool, blockStore, - sm.BlockExecutorWithMetrics(nodeMetrics.state), + eventBus, + nodeMetrics.state, ) + // Determine whether we should attempt state sync. + stateSync := cfg.StateSync.Enable && !onlyValidatorIsUs(state, pubKey) + if stateSync && state.LastBlockHeight > 0 { + logger.Info("Found local state with non-zero height, skipping state sync") + stateSync = false + } + // Determine whether we should do block sync. This must happen after the handshake, since the // app may modify the validator set, specifying ourself as the only validator. blockSync := !onlyValidatorIsUs(state, pubKey) + waitSync := stateSync || blockSync - csReactor, csState, err := createConsensusReactor(ctx, - cfg, stateStore, blockExec, blockStore, mp, evPool, - privValidator, nodeMetrics.consensus, stateSync || blockSync, eventBus, - peerManager, router, logger, + csState, err := consensus.NewState(logger.With("module", "consensus"), + cfg.Consensus, + stateStore, + blockExec, + blockStore, + mp, + evPool, + eventBus, + consensus.StateMetrics(nodeMetrics.consensus), + consensus.SkipStateStoreBootstrap, ) if err != nil { return nil, combineCloseError(err, makeCloser(closers)) } + node.rpcEnv.ConsensusState = csState + + csReactor := consensus.NewReactor( + logger, + csState, + node.router.OpenChannel, + peerManager.Subscribe, + eventBus, + waitSync, + nodeMetrics.consensus, + ) + node.services = append(node.services, csReactor) + node.rpcEnv.ConsensusReactor = csReactor // Create the blockchain reactor. Note, we do not start block sync if we're // doing a state sync first. - bcReactor, err := blocksync.NewReactor(ctx, + bcReactor := blocksync.NewReactor( logger.With("module", "blockchain"), stateStore, blockExec, blockStore, csReactor, - router.OpenChannel, - peerManager.Subscribe(ctx), + node.router.OpenChannel, + peerManager.Subscribe, blockSync && !stateSync, nodeMetrics.consensus, eventBus, ) - if err != nil { - return nil, combineCloseError( - fmt.Errorf("could not create blocksync reactor: %w", err), - makeCloser(closers)) - } + node.services = append(node.services, bcReactor) + node.rpcEnv.BlockSyncReactor = bcReactor // Make ConsensusReactor. Don't enable fully if doing a state sync and/or block sync first. // FIXME We need to update metrics here, since other reactors don't have access to them. @@ -332,89 +347,50 @@ func makeNode( nodeMetrics.consensus.BlockSyncing.Set(1) } + if cfg.P2P.PexReactor { + node.services = append(node.services, pex.NewReactor(logger, peerManager, node.router.OpenChannel, peerManager.Subscribe)) + } + // Set up state sync reactor, and schedule a sync if requested. // FIXME The way we do phased startups (e.g. replay -> block sync -> consensus) is very messy, // we should clean this whole thing up. See: // https://github.com/tendermint/tendermint/issues/4644 - stateSyncReactor, err := statesync.NewReactor( - ctx, + node.services = append(node.services, statesync.NewReactor( genDoc.ChainID, genDoc.InitialHeight, *cfg.StateSync, logger.With("module", "statesync"), proxyApp, - router.OpenChannel, - peerManager.Subscribe(ctx), + node.router.OpenChannel, + peerManager.Subscribe, stateStore, blockStore, cfg.StateSync.TempDir, nodeMetrics.statesync, eventBus, - ) - if err != nil { - return nil, combineCloseError(err, makeCloser(closers)) - } + // the post-sync operation + func(ctx context.Context, state sm.State) error { + csReactor.SetStateSyncingMetrics(0) - var pexReactor service.Service - if cfg.P2P.PexReactor { - pexReactor, err = pex.NewReactor(ctx, logger, peerManager, router.OpenChannel, peerManager.Subscribe(ctx)) - if err != nil { - return nil, combineCloseError(err, makeCloser(closers)) - } - } - node := &nodeImpl{ - config: cfg, - logger: logger, - genesisDoc: genDoc, - privValidator: privValidator, + // TODO: Some form of orchestrator is needed here between the state + // advancing reactors to be able to control which one of the three + // is running + // FIXME Very ugly to have these metrics bleed through here. + csReactor.SetBlockSyncingMetrics(1) + if err := bcReactor.SwitchToBlockSync(ctx, state); err != nil { + logger.Error("failed to switch to block sync", "err", err) + return err + } - peerManager: peerManager, - router: router, - nodeInfo: nodeInfo, - nodeKey: nodeKey, - - eventSinks: eventSinks, - - services: []service.Service{ - eventBus, - evReactor, - mpReactor, - csReactor, - bcReactor, - pexReactor, + return nil }, - - stateStore: stateStore, - blockStore: blockStore, - stateSyncReactor: stateSyncReactor, - stateSync: stateSync, - - shutdownOps: makeCloser(closers), - - rpcEnv: &rpccore.Environment{ - ProxyApp: proxyApp, - EvidencePool: evPool, - ConsensusState: csState, - - StateStore: stateStore, - BlockStore: blockStore, - - ConsensusReactor: csReactor, - BlockSyncReactor: bcReactor, - - PeerManager: peerManager, - - GenDoc: genDoc, - EventSinks: eventSinks, - EventBus: eventBus, - EventLog: eventLog, - Mempool: mp, - Logger: logger.With("module", "rpc"), - Config: *cfg.RPC, - }, - } + stateSync, + )) if cfg.Mode == config.ModeValidator { + if privValidator != nil { + csState.SetPrivValidator(ctx, privValidator) + } node.rpcEnv.PubKey = pubKey } @@ -425,8 +401,50 @@ func makeNode( // OnStart starts the Node. It implements service.Service. func (n *nodeImpl) OnStart(ctx context.Context) error { + if err := n.rpcEnv.ProxyApp.Start(ctx); err != nil { + return fmt.Errorf("error starting proxy app connections: %w", err) + } + + // EventBus and IndexerService must be started before the handshake because + // we might need to index the txs of the replayed block as this might not have happened + // when the node stopped last time (i.e. the node stopped or crashed after it saved the block + // but before it indexed the txs) + if err := n.rpcEnv.EventBus.Start(ctx); err != nil { + return err + } + + if err := n.indexerService.Start(ctx); err != nil { + return err + } + + // Create the handshaker, which calls RequestInfo, sets the AppVersion on the state, + // and replays any blocks as necessary to sync tendermint with the app. + if err := consensus.NewHandshaker(n.logger.With("module", "handshaker"), + n.stateStore, n.initialState, n.blockStore, n.rpcEnv.EventBus, n.genesisDoc, + ).Handshake(ctx, n.rpcEnv.ProxyApp); err != nil { + return err + } + + // Reload the state. It will have the Version.Consensus.App set by the + // Handshake, and may have other modifications as well (ie. depending on + // what happened during block replay). + state, err := n.stateStore.Load() + if err != nil { + return fmt.Errorf("cannot load state: %w", err) + } + + logNodeStartupInfo(state, n.rpcEnv.PubKey, n.logger, n.config.Mode) + + // TODO: Fetch and provide real options and do proper p2p bootstrapping. + // TODO: Use a persistent peer database. + n.nodeInfo, err = makeNodeInfo(n.config, n.nodeKey, n.eventSinks, n.genesisDoc, state.Version.Consensus) + if err != nil { + return err + } + // Start Internal Services + if n.config.RPC.PprofListenAddress != "" { - rpcCtx, rpcCancel := context.WithCancel(ctx) + signal := make(chan struct{}) srv := &http.Server{Addr: n.config.RPC.PprofListenAddress, Handler: nil} go func() { select { @@ -434,7 +452,7 @@ func (n *nodeImpl) OnStart(ctx context.Context) error { sctx, scancel := context.WithTimeout(context.Background(), time.Second) defer scancel() _ = srv.Shutdown(sctx) - case <-rpcCtx.Done(): + case <-signal: } }() @@ -443,7 +461,7 @@ func (n *nodeImpl) OnStart(ctx context.Context) error { if err := srv.ListenAndServe(); err != nil { n.logger.Error("pprof server error", "err", err) - rpcCancel() + close(signal) } }() } @@ -462,15 +480,12 @@ func (n *nodeImpl) OnStart(ctx context.Context) error { } } - n.rpcEnv.NodeInfo = n.nodeInfo - // Start the RPC server before the P2P server - // so we can eg. receive txs for the first block - if n.config.RPC.ListenAddress != "" { - var err error - n.rpcListeners, err = n.rpcEnv.StartService(ctx, n.config) - if err != nil { - return err - } + state, err = n.stateStore.Load() + if err != nil { + return err + } + if err := n.evPool.Start(state); err != nil { + return err } if n.config.Instrumentation.Prometheus && n.config.Instrumentation.PrometheusListenAddr != "" { @@ -489,71 +504,13 @@ func (n *nodeImpl) OnStart(ctx context.Context) error { } } - if err := n.stateSyncReactor.Start(ctx); err != nil { - return err - } - - // Run state sync - // TODO: We shouldn't run state sync if we already have state that has a - // LastBlockHeight that is not InitialHeight - if n.stateSync { - bcR := n.rpcEnv.BlockSyncReactor - - // we need to get the genesis state to get parameters such as - state, err := sm.MakeGenesisState(n.genesisDoc) + n.rpcEnv.NodeInfo = n.nodeInfo + // Start the RPC server before the P2P server + // so we can eg. receive txs for the first block + if n.config.RPC.ListenAddress != "" { + var err error + n.rpcListeners, err = n.rpcEnv.StartService(ctx, n.config) if err != nil { - return fmt.Errorf("unable to derive state: %w", err) - } - - // TODO: we may want to move these events within the respective - // reactors. - // At the beginning of the statesync start, we use the initialHeight as the event height - // because of the statesync doesn't have the concreate state height before fetched the snapshot. - d := types.EventDataStateSyncStatus{Complete: false, Height: state.InitialHeight} - if err := n.stateSyncReactor.PublishStatus(ctx, d); err != nil { - n.logger.Error("failed to emit the statesync start event", "err", err) - } - - // RUN STATE SYNC NOW: - // - // TODO: Eventually this should run as part of some - // separate orchestrator - n.logger.Info("starting state sync") - ssState, err := n.stateSyncReactor.Sync(ctx) - if err != nil { - n.logger.Error("state sync failed; shutting down this node", "err", err) - // stop the node - n.Stop() - return err - } - - n.rpcEnv.ConsensusReactor.SetStateSyncingMetrics(0) - - if err := n.stateSyncReactor.PublishStatus(ctx, - types.EventDataStateSyncStatus{ - Complete: true, - Height: ssState.LastBlockHeight, - }); err != nil { - n.logger.Error("failed to emit the statesync start event", "err", err) - return err - } - - // TODO: Some form of orchestrator is needed here between the state - // advancing reactors to be able to control which one of the three - // is running - // FIXME Very ugly to have these metrics bleed through here. - n.rpcEnv.ConsensusReactor.SetBlockSyncingMetrics(1) - if err := bcR.SwitchToBlockSync(ctx, ssState); err != nil { - n.logger.Error("failed to switch to block sync", "err", err) - return err - } - - if err := bcR.PublishStatus(ctx, - types.EventDataBlockSyncStatus{ - Complete: false, - Height: ssState.LastBlockHeight, - }); err != nil { - n.logger.Error("failed to emit the block sync starting event", "err", err) return err } } @@ -564,6 +521,14 @@ func (n *nodeImpl) OnStart(ctx context.Context) error { // OnStop stops the Node. It implements service.Service. func (n *nodeImpl) OnStop() { n.logger.Info("Stopping Node") + // stop the listeners / external services first + for _, l := range n.rpcListeners { + n.logger.Info("Closing rpc listener", "listener", l) + if err := l.Close(); err != nil { + n.logger.Error("error closing listener", "listener", l, "err", err) + } + } + for _, es := range n.eventSinks { if err := es.Stop(); err != nil { n.logger.Error("failed to stop event sink", "err", err) @@ -574,18 +539,9 @@ func (n *nodeImpl) OnStop() { reactor.Wait() } - n.stateSyncReactor.Wait() n.router.Wait() n.rpcEnv.IsListening = false - // finally stop the listeners / external services - for _, l := range n.rpcListeners { - n.logger.Info("Closing rpc listener", "listener", l) - if err := l.Close(); err != nil { - n.logger.Error("error closing listener", "listener", l, "err", err) - } - } - if pvsc, ok := n.privValidator.(service.Service); ok { pvsc.Wait() } @@ -627,27 +583,31 @@ func (n *nodeImpl) startPrometheusServer(ctx context.Context, addr string) *http ), } - promCtx, promCancel := context.WithCancel(ctx) + signal := make(chan struct{}) go func() { select { case <-ctx.Done(): sctx, scancel := context.WithTimeout(context.Background(), time.Second) defer scancel() _ = srv.Shutdown(sctx) - case <-promCtx.Done(): + case <-signal: } }() go func() { if err := srv.ListenAndServe(); err != nil { n.logger.Error("Prometheus HTTP server ListenAndServe", "err", err) - promCancel() + close(signal) } }() return srv } +func (n *nodeImpl) NodeInfo() *types.NodeInfo { + return &n.nodeInfo +} + // EventBus returns the Node's EventBus. func (n *nodeImpl) EventBus() *eventbus.EventBus { return n.rpcEnv.EventBus @@ -760,7 +720,7 @@ func getRouterConfig(conf *config.Config, appClient abciclient.Client) p2p.Route if conf.FilterPeers && appClient != nil { opts.FilterPeerByID = func(ctx context.Context, id types.NodeID) error { - res, err := appClient.Query(ctx, abci.RequestQuery{ + res, err := appClient.Query(ctx, &abci.RequestQuery{ Path: fmt.Sprintf("/p2p/filter/id/%s", id), }) if err != nil { @@ -774,7 +734,7 @@ func getRouterConfig(conf *config.Config, appClient abciclient.Client) p2p.Route } opts.FilterPeerByIP = func(ctx context.Context, ip net.IP, port uint16) error { - res, err := appClient.Query(ctx, abci.RequestQuery{ + res, err := appClient.Query(ctx, &abci.RequestQuery{ Path: fmt.Sprintf("/p2p/filter/addr/%s", net.JoinHostPort(ip.String(), strconv.Itoa(int(port)))), }) if err != nil { diff --git a/node/node_test.go b/node/node_test.go index 690da1a4d..c5ff1f014 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -20,7 +20,6 @@ import ( "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/eventbus" "github.com/tendermint/tendermint/internal/evidence" "github.com/tendermint/tendermint/internal/mempool" @@ -28,6 +27,7 @@ import ( "github.com/tendermint/tendermint/internal/pubsub" sm "github.com/tendermint/tendermint/internal/state" "github.com/tendermint/tendermint/internal/state/indexer" + "github.com/tendermint/tendermint/internal/state/indexer/sink" "github.com/tendermint/tendermint/internal/store" "github.com/tendermint/tendermint/internal/test/factory" "github.com/tendermint/tendermint/libs/log" @@ -62,12 +62,13 @@ func TestNodeStartStop(t *testing.T) { require.NoError(t, n.Start(ctx)) // wait for the node to produce a block - tctx, cancel := context.WithTimeout(ctx, time.Second) + tctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() blocksSub, err := n.EventBus().SubscribeWithArgs(tctx, pubsub.SubscribeArgs{ ClientID: "node_test", Query: types.EventQueryNewBlock, + Limit: 1000, }) require.NoError(t, err) _, err = blocksSub.Next(tctx) @@ -138,6 +139,8 @@ func TestNodeSetAppVersion(t *testing.T) { // create node n := getTestNode(ctx, t, cfg, logger) + require.NoError(t, n.Start(ctx)) + // default config uses the kvstore app appVersion := kvstore.ProtocolVersion @@ -297,8 +300,7 @@ func TestCreateProposalBlock(t *testing.T) { // Make EvidencePool evidenceDB := dbm.NewMemDB() blockStore := store.NewBlockStore(dbm.NewMemDB()) - evidencePool, err := evidence.NewPool(logger, evidenceDB, stateStore, blockStore, evidence.NopMetrics()) - require.NoError(t, err) + evidencePool := evidence.NewPool(logger, evidenceDB, stateStore, blockStore, evidence.NopMetrics(), nil) // fill the evidence pool with more evidence // than can fit in a block @@ -324,6 +326,8 @@ func TestCreateProposalBlock(t *testing.T) { assert.NoError(t, err) } + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) blockExec := sm.NewBlockExecutor( stateStore, logger, @@ -331,10 +335,12 @@ func TestCreateProposalBlock(t *testing.T) { mp, evidencePool, blockStore, + eventBus, + sm.NopMetrics(), ) commit := types.NewCommit(height-1, 0, types.BlockID{}, nil) - block, _, err := blockExec.CreateProposalBlock( + block, err := blockExec.CreateProposalBlock( ctx, height, state, commit, @@ -399,6 +405,9 @@ func TestMaxTxsProposalBlockSize(t *testing.T) { err = mp.CheckTx(ctx, tx, nil, mempool.TxInfo{}) assert.NoError(t, err) + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) + blockExec := sm.NewBlockExecutor( stateStore, logger, @@ -406,10 +415,12 @@ func TestMaxTxsProposalBlockSize(t *testing.T) { mp, sm.EmptyEvidencePool{}, blockStore, + eventBus, + sm.NopMetrics(), ) commit := types.NewCommit(height-1, 0, types.BlockID{}, nil) - block, _, err := blockExec.CreateProposalBlock( + block, err := blockExec.CreateProposalBlock( ctx, height, state, commit, @@ -443,7 +454,8 @@ func TestMaxProposalBlockSize(t *testing.T) { err = proxyApp.Start(ctx) require.NoError(t, err) - state, stateDB, _ := state(t, types.MaxVotesCount, int64(1)) + state, stateDB, privVals := state(t, types.MaxVotesCount, int64(1)) + stateStore := sm.NewStore(stateDB) blockStore := store.NewBlockStore(dbm.NewMemDB()) const maxBytes int64 = 1024 * 1024 * 2 @@ -470,6 +482,9 @@ func TestMaxProposalBlockSize(t *testing.T) { assert.NoError(t, err) } + eventBus := eventbus.NewDefault(logger) + require.NoError(t, eventBus.Start(ctx)) + blockExec := sm.NewBlockExecutor( stateStore, logger, @@ -477,23 +492,31 @@ func TestMaxProposalBlockSize(t *testing.T) { mp, sm.EmptyEvidencePool{}, blockStore, + eventBus, + sm.NopMetrics(), ) blockID := types.BlockID{ - Hash: tmhash.Sum([]byte("blockID_hash")), + Hash: crypto.Checksum([]byte("blockID_hash")), PartSetHeader: types.PartSetHeader{ Total: math.MaxInt32, - Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), + Hash: crypto.Checksum([]byte("blockID_part_set_header_hash")), }, } + // save the updated validator set for use by the block executor. + state.LastBlockHeight = math.MaxInt64 - 3 + state.LastHeightValidatorsChanged = math.MaxInt64 - 1 + state.NextValidators = state.Validators.Copy() + require.NoError(t, stateStore.Save(state)) + timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) // change state in order to produce the largest accepted header state.LastBlockID = blockID - state.LastBlockHeight = math.MaxInt64 - 1 + state.LastBlockHeight = math.MaxInt64 - 2 state.LastBlockTime = timestamp - state.LastResultsHash = tmhash.Sum([]byte("last_results_hash")) - state.AppHash = tmhash.Sum([]byte("app_hash")) + state.LastResultsHash = crypto.Checksum([]byte("last_results_hash")) + state.AppHash = crypto.Checksum([]byte("app_hash")) state.Version.Consensus.Block = math.MaxInt64 state.Version.Consensus.App = math.MaxInt64 maxChainID := "" @@ -515,19 +538,29 @@ func TestMaxProposalBlockSize(t *testing.T) { BlockID: blockID, } + votes := make([]*types.Vote, types.MaxVotesCount) + // add maximum amount of signatures to a single commit for i := 0; i < types.MaxVotesCount; i++ { + pubKey, err := privVals[i].GetPubKey(ctx) + require.NoError(t, err) + votes[i] = &types.Vote{ + ValidatorAddress: pubKey.Address(), + } commit.Signatures = append(commit.Signatures, cs) } - block, partSet, err := blockExec.CreateProposalBlock( + block, err := blockExec.CreateProposalBlock( ctx, math.MaxInt64, - state, commit, + state, + commit, proposerAddr, - nil, + votes, ) require.NoError(t, err) + partSet, err := block.MakePartSet(types.BlockPartSizeBytes) + require.NoError(t, err) // this ensures that the header is at max size block.Header.Time = timestamp @@ -560,12 +593,12 @@ func TestNodeNewSeedNode(t *testing.T) { logger := log.NewNopLogger() - ns, err := makeSeedNode(ctx, + ns, err := makeSeedNode( + logger, cfg, config.DefaultDBProvider, nodeKey, defaultGenesisDocProviderFunc(cfg), - logger, ) t.Cleanup(ns.Wait) t.Cleanup(leaktest.CheckTimeout(t, time.Second)) @@ -603,11 +636,9 @@ func TestNodeSetEventSink(t *testing.T) { genDoc, err := types.GenesisDocFromFile(cfg.GenesisFile()) require.NoError(t, err) - indexService, eventSinks, err := createAndStartIndexerService(ctx, cfg, - config.DefaultDBProvider, eventBus, logger, genDoc.ChainID, - indexer.NopMetrics()) + eventSinks, err := sink.EventSinksFromConfig(cfg, config.DefaultDBProvider, genDoc.ChainID) require.NoError(t, err) - t.Cleanup(indexService.Wait) + return eventSinks } cleanup := func(ns service.Service) func() { @@ -733,7 +764,7 @@ func loadStatefromGenesis(ctx context.Context, t *testing.T) sm.State { require.True(t, loadedState.IsEmpty()) valSet, _ := factory.ValidatorSet(ctx, t, 0, 10) - genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, nil) + genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, factory.ConsensusParams()) state, err := loadStateFromDBOrGenesisDocProvider( stateStore, diff --git a/node/public.go b/node/public.go index af3aece8e..db292833e 100644 --- a/node/public.go +++ b/node/public.go @@ -68,7 +68,7 @@ func New( config.DefaultDBProvider, logger) case config.ModeSeed: - return makeSeedNode(ctx, conf, config.DefaultDBProvider, nodeKey, genProvider, logger) + return makeSeedNode(logger, conf, config.DefaultDBProvider, nodeKey, genProvider) default: return nil, fmt.Errorf("%q is not a valid mode", conf.Mode) } diff --git a/node/seed.go b/node/seed.go index 970896cc6..a0b71e411 100644 --- a/node/seed.go +++ b/node/seed.go @@ -29,7 +29,6 @@ type seedNodeImpl struct { // network peerManager *p2p.PeerManager router *p2p.Router - nodeInfo types.NodeInfo nodeKey types.NodeKey // our node privkey isListening bool @@ -40,12 +39,11 @@ type seedNodeImpl struct { // makeSeedNode returns a new seed node, containing only p2p, pex reactor func makeSeedNode( - ctx context.Context, + logger log.Logger, cfg *config.Config, dbProvider config.DBProvider, nodeKey types.NodeKey, genesisDocProvider genesisDocProvider, - logger log.Logger, ) (service.Service, error) { if !cfg.P2P.PexReactor { return nil, errors.New("cannot run seed nodes with PEX disabled") @@ -76,32 +74,25 @@ func makeSeedNode( closer) } - router, err := createRouter(ctx, logger, p2pMetrics, nodeInfo, nodeKey, - peerManager, cfg, nil) + router, err := createRouter(logger, p2pMetrics, func() *types.NodeInfo { return &nodeInfo }, nodeKey, peerManager, cfg, nil) if err != nil { return nil, combineCloseError( fmt.Errorf("failed to create router: %w", err), closer) } - pexReactor, err := pex.NewReactor(ctx, logger, peerManager, router.OpenChannel, peerManager.Subscribe(ctx)) - if err != nil { - return nil, combineCloseError(err, closer) - } - node := &seedNodeImpl{ config: cfg, logger: logger, genesisDoc: genDoc, - nodeInfo: nodeInfo, nodeKey: nodeKey, peerManager: peerManager, router: router, shutdownOps: closer, - pexReactor: pexReactor, + pexReactor: pex.NewReactor(logger, peerManager, router.OpenChannel, peerManager.Subscribe), } node.BaseService = *service.NewBaseService(logger, "SeedNode", node) diff --git a/node/setup.go b/node/setup.go index 7a473fae9..d6966800a 100644 --- a/node/setup.go +++ b/node/setup.go @@ -23,7 +23,6 @@ import ( "github.com/tendermint/tendermint/internal/p2p/pex" sm "github.com/tendermint/tendermint/internal/state" "github.com/tendermint/tendermint/internal/state/indexer" - "github.com/tendermint/tendermint/internal/state/indexer/sink" "github.com/tendermint/tendermint/internal/statesync" "github.com/tendermint/tendermint/internal/store" "github.com/tendermint/tendermint/libs/log" @@ -95,34 +94,6 @@ func initDBs( return blockStore, stateDB, makeCloser(closers), nil } -func createAndStartIndexerService( - ctx context.Context, - cfg *config.Config, - dbProvider config.DBProvider, - eventBus *eventbus.EventBus, - logger log.Logger, - chainID string, - metrics *indexer.Metrics, -) (*indexer.Service, []indexer.EventSink, error) { - eventSinks, err := sink.EventSinksFromConfig(cfg, dbProvider, chainID) - if err != nil { - return nil, nil, err - } - - indexerService := indexer.NewService(indexer.ServiceArgs{ - Sinks: eventSinks, - EventBus: eventBus, - Logger: logger.With("module", "txindex"), - Metrics: metrics, - }) - - if err := indexerService.Start(ctx); err != nil { - return nil, nil, err - } - - return indexerService, eventSinks, nil -} - func logNodeStartupInfo(state sm.State, pubKey crypto.PubKey, logger log.Logger, mode string) { // Log the version info. logger.Info("Version info", @@ -169,15 +140,15 @@ func onlyValidatorIsUs(state sm.State, pubKey crypto.PubKey) bool { } func createMempoolReactor( - ctx context.Context, + logger log.Logger, cfg *config.Config, appClient abciclient.Client, store sm.Store, memplMetrics *mempool.Metrics, - peerManager *p2p.PeerManager, - router *p2p.Router, - logger log.Logger, -) (service.Service, mempool.Mempool, error) { + peerEvents p2p.PeerEventSubscriber, + chCreator p2p.ChannelCreator, + peerHeight func(types.NodeID) int64, +) (service.Service, mempool.Mempool) { logger = logger.With("module", "mempool") mp := mempool.NewTxMempool( @@ -189,119 +160,44 @@ func createMempoolReactor( mempool.WithPostCheck(sm.TxPostCheckFromStore(store)), ) - reactor, err := mempool.NewReactor( - ctx, + reactor := mempool.NewReactor( logger, cfg.Mempool, - peerManager, mp, - router.OpenChannel, - peerManager.Subscribe(ctx), + chCreator, + peerEvents, + peerHeight, ) - if err != nil { - return nil, nil, err - } if cfg.Consensus.WaitForTxs() { mp.EnableTxsAvailable() } - return reactor, mp, nil + return reactor, mp } func createEvidenceReactor( - ctx context.Context, + logger log.Logger, cfg *config.Config, dbProvider config.DBProvider, store sm.Store, blockStore *store.BlockStore, - peerManager *p2p.PeerManager, - router *p2p.Router, - logger log.Logger, + peerEvents p2p.PeerEventSubscriber, + chCreator p2p.ChannelCreator, metrics *evidence.Metrics, eventBus *eventbus.EventBus, -) (*evidence.Reactor, *evidence.Pool, error) { +) (*evidence.Reactor, *evidence.Pool, closer, error) { evidenceDB, err := dbProvider(&config.DBContext{ID: "evidence", Config: cfg}) if err != nil { - return nil, nil, fmt.Errorf("unable to initialize evidence db: %w", err) + return nil, nil, func() error { return nil }, fmt.Errorf("unable to initialize evidence db: %w", err) } logger = logger.With("module", "evidence") - evidencePool, err := evidence.NewPool(logger, evidenceDB, store, blockStore, metrics) - if err != nil { - return nil, nil, fmt.Errorf("creating evidence pool: %w", err) - } + evidencePool := evidence.NewPool(logger, evidenceDB, store, blockStore, metrics, eventBus) + evidenceReactor := evidence.NewReactor(logger, chCreator, peerEvents, evidencePool) - evidencePool.SetEventBus(eventBus) - - evidenceReactor, err := evidence.NewReactor( - ctx, - logger, - router.OpenChannel, - peerManager.Subscribe(ctx), - evidencePool, - ) - if err != nil { - return nil, nil, fmt.Errorf("creating evidence reactor: %w", err) - } - - return evidenceReactor, evidencePool, nil -} - -func createConsensusReactor( - ctx context.Context, - cfg *config.Config, - store sm.Store, - blockExec *sm.BlockExecutor, - blockStore sm.BlockStore, - mp mempool.Mempool, - evidencePool *evidence.Pool, - privValidator types.PrivValidator, - csMetrics *consensus.Metrics, - waitSync bool, - eventBus *eventbus.EventBus, - peerManager *p2p.PeerManager, - router *p2p.Router, - logger log.Logger, -) (*consensus.Reactor, *consensus.State, error) { - logger = logger.With("module", "consensus") - - consensusState, err := consensus.NewState(ctx, - logger, - cfg.Consensus, - store, - blockExec, - blockStore, - mp, - evidencePool, - consensus.StateMetrics(csMetrics), - ) - if err != nil { - return nil, nil, err - } - - if privValidator != nil && cfg.Mode == config.ModeValidator { - consensusState.SetPrivValidator(ctx, privValidator) - } - - reactor, err := consensus.NewReactor( - ctx, - logger, - consensusState, - router.OpenChannel, - peerManager.Subscribe(ctx), - waitSync, - csMetrics, - ) - if err != nil { - return nil, nil, err - } - - // Services which will be publishing and/or subscribing for messages (events) - // consensusReactor will set it on consensusState and blockExecutor. - reactor.SetEventBus(eventBus) - return reactor, consensusState, nil + return evidenceReactor, evidencePool, evidenceDB.Close, nil } func createPeerManager( @@ -380,10 +276,9 @@ func createPeerManager( } func createRouter( - ctx context.Context, logger log.Logger, p2pMetrics *p2p.Metrics, - nodeInfo types.NodeInfo, + nodeInfoProducer func() *types.NodeInfo, nodeKey types.NodeKey, peerManager *p2p.PeerManager, cfg *config.Config, @@ -410,14 +305,13 @@ func createRouter( } return p2p.NewRouter( - ctx, p2pLogger, p2pMetrics, - nodeInfo, nodeKey.PrivKey, peerManager, - []p2p.Transport{transport}, - []p2p.Endpoint{ep}, + nodeInfoProducer, + transport, + ep, getRouterConfig(cfg, appClient), ) } @@ -427,7 +321,7 @@ func makeNodeInfo( nodeKey types.NodeKey, eventSinks []indexer.EventSink, genDoc *types.GenesisDoc, - state sm.State, + versionInfo version.Consensus, ) (types.NodeInfo, error) { txIndexerStatus := "off" @@ -439,8 +333,8 @@ func makeNodeInfo( nodeInfo := types.NodeInfo{ ProtocolVersion: types.ProtocolVersion{ P2P: version.P2PProtocol, // global - Block: state.Version.Consensus.Block, - App: state.Version.Consensus.App, + Block: versionInfo.Block, + App: versionInfo.App, }, NodeID: nodeKey.ID, Network: genDoc.ChainID, diff --git a/privval/file.go b/privval/file.go index 728e0dc67..bf5803632 100644 --- a/privval/file.go +++ b/privval/file.go @@ -117,6 +117,14 @@ type FilePVLastSignState struct { filePath string } +func (lss *FilePVLastSignState) reset() { + lss.Height = 0 + lss.Round = 0 + lss.Step = 0 + lss.Signature = nil + lss.SignBytes = nil +} + // checkHRS checks the given height, round, step (HRS) against that of the // FilePVLastSignState. It returns an error if the arguments constitute a regression, // or if they match but the SignBytes are empty. @@ -328,12 +336,7 @@ func (pv *FilePV) Save() error { // Reset resets all fields in the FilePV. // NOTE: Unsafe! func (pv *FilePV) Reset() error { - var sig []byte - pv.LastSignState.Height = 0 - pv.LastSignState.Round = 0 - pv.LastSignState.Step = 0 - pv.LastSignState.Signature = sig - pv.LastSignState.SignBytes = nil + pv.LastSignState.reset() return pv.Save() } @@ -370,6 +373,21 @@ func (pv *FilePV) signVote(chainID string, vote *tmproto.Vote) error { signBytes := types.VoteSignBytes(chainID, vote) + // Vote extensions are non-deterministic, so it is possible that an + // application may have created a different extension. We therefore always + // re-sign the vote extensions of precommits. For prevotes, the extension + // signature will always be empty. + var extSig []byte + if vote.Type == tmproto.PrecommitType { + extSignBytes := types.VoteExtensionSignBytes(chainID, vote) + extSig, err = pv.Key.PrivKey.Sign(extSignBytes) + if err != nil { + return err + } + } else if len(vote.Extension) > 0 { + return errors.New("unexpected vote extension - extensions are only allowed in precommits") + } + // We might crash before writing to the wal, // causing us to try to re-sign for the same HRS. // If signbytes are the same, use the last signature. @@ -379,6 +397,8 @@ func (pv *FilePV) signVote(chainID string, vote *tmproto.Vote) error { if bytes.Equal(signBytes, lss.SignBytes) { vote.Signature = lss.Signature } else { + // Compares the canonicalized votes (i.e. without vote extensions + // or vote extension signatures). timestamp, ok, err := checkVotesOnlyDifferByTimestamp(lss.SignBytes, signBytes) if err != nil { return err @@ -390,6 +410,9 @@ func (pv *FilePV) signVote(chainID string, vote *tmproto.Vote) error { vote.Timestamp = timestamp vote.Signature = lss.Signature } + + vote.ExtensionSignature = extSig + return nil } @@ -402,6 +425,8 @@ func (pv *FilePV) signVote(chainID string, vote *tmproto.Vote) error { return err } vote.Signature = sig + vote.ExtensionSignature = extSig + return nil } @@ -453,8 +478,10 @@ func (pv *FilePV) saveSigned(height int64, round int32, step int8, signBytes []b //----------------------------------------------------------------------------------------- -// returns the timestamp from the lastSignBytes. -// returns true if the only difference in the votes is their timestamp. +// Returns the timestamp from the lastSignBytes. +// Returns true if the only difference in the votes is their timestamp. +// Performs these checks on the canonical votes (excluding the vote extension +// and vote extension signatures). func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool, error) { var lastVote, newVote tmproto.CanonicalVote if err := protoio.UnmarshalDelimited(lastSignBytes, &lastVote); err != nil { diff --git a/privval/file_test.go b/privval/file_test.go index 91c2e2a9b..9f1105441 100644 --- a/privval/file_test.go +++ b/privval/file_test.go @@ -12,8 +12,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/tmhash" tmrand "github.com/tendermint/tendermint/libs/rand" tmtime "github.com/tendermint/tendermint/libs/time" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -21,20 +21,14 @@ import ( ) func TestGenLoadValidator(t *testing.T) { - tempKeyFile, err := os.CreateTemp(t.TempDir(), "priv_validator_key_") - require.NoError(t, err) - tempStateFile, err := os.CreateTemp(t.TempDir(), "priv_validator_state_") - require.NoError(t, err) - - privVal, err := GenFilePV(tempKeyFile.Name(), tempStateFile.Name(), "") - require.NoError(t, err) + privVal, tempKeyFileName, tempStateFileName := newTestFilePV(t) height := int64(100) privVal.LastSignState.Height = height require.NoError(t, privVal.Save()) addr := privVal.GetAddress() - privVal, err = LoadFilePV(tempKeyFile.Name(), tempStateFile.Name()) + privVal, err := LoadFilePV(tempKeyFileName, tempStateFileName) assert.NoError(t, err) assert.Equal(t, addr, privVal.GetAddress(), "expected privval addr to be the same") assert.Equal(t, height, privVal.LastSignState.Height, "expected privval.LastHeight to have been saved") @@ -44,14 +38,8 @@ func TestResetValidator(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - tempKeyFile, err := os.CreateTemp(t.TempDir(), "priv_validator_key_") - require.NoError(t, err) - tempStateFile, err := os.CreateTemp(t.TempDir(), "priv_validator_state_") - require.NoError(t, err) - - privVal, err := GenFilePV(tempKeyFile.Name(), tempStateFile.Name(), "") - require.NoError(t, err) - emptyState := FilePVLastSignState{filePath: tempStateFile.Name()} + privVal, _, tempStateFileName := newTestFilePV(t) + emptyState := FilePVLastSignState{filePath: tempStateFileName} // new priv val has empty state assert.Equal(t, privVal.LastSignState, emptyState) @@ -59,10 +47,10 @@ func TestResetValidator(t *testing.T) { // test vote height, round := int64(10), int32(1) voteType := tmproto.PrevoteType - randBytes := tmrand.Bytes(tmhash.Size) + randBytes := tmrand.Bytes(crypto.HashSize) blockID := types.BlockID{Hash: randBytes, PartSetHeader: types.PartSetHeader{}} - vote := newVote(privVal.Key.Address, 0, height, round, voteType, blockID) - err = privVal.SignVote(ctx, "mychainid", vote.ToProto()) + vote := newVote(privVal.Key.Address, 0, height, round, voteType, blockID, nil) + err := privVal.SignVote(ctx, "mychainid", vote.ToProto()) assert.NoError(t, err, "expected no error signing vote") // priv val after signing is not same as empty @@ -160,16 +148,10 @@ func TestSignVote(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - tempKeyFile, err := os.CreateTemp(t.TempDir(), "priv_validator_key_") - require.NoError(t, err) - tempStateFile, err := os.CreateTemp(t.TempDir(), "priv_validator_state_") - require.NoError(t, err) + privVal, _, _ := newTestFilePV(t) - privVal, err := GenFilePV(tempKeyFile.Name(), tempStateFile.Name(), "") - require.NoError(t, err) - - randbytes := tmrand.Bytes(tmhash.Size) - randbytes2 := tmrand.Bytes(tmhash.Size) + randbytes := tmrand.Bytes(crypto.HashSize) + randbytes2 := tmrand.Bytes(crypto.HashSize) block1 := types.BlockID{Hash: randbytes, PartSetHeader: types.PartSetHeader{Total: 5, Hash: randbytes}} @@ -180,10 +162,10 @@ func TestSignVote(t *testing.T) { voteType := tmproto.PrevoteType // sign a vote for first time - vote := newVote(privVal.Key.Address, 0, height, round, voteType, block1) + vote := newVote(privVal.Key.Address, 0, height, round, voteType, block1, nil) v := vote.ToProto() - err = privVal.SignVote(ctx, "mychainid", v) + err := privVal.SignVote(ctx, "mychainid", v) assert.NoError(t, err, "expected no error signing vote") // try to sign the same vote again; should be fine @@ -192,10 +174,10 @@ func TestSignVote(t *testing.T) { // now try some bad votes cases := []*types.Vote{ - newVote(privVal.Key.Address, 0, height, round-1, voteType, block1), // round regression - newVote(privVal.Key.Address, 0, height-1, round, voteType, block1), // height regression - newVote(privVal.Key.Address, 0, height-2, round+4, voteType, block1), // height regression and different round - newVote(privVal.Key.Address, 0, height, round, voteType, block2), // different block + newVote(privVal.Key.Address, 0, height, round-1, voteType, block1, nil), // round regression + newVote(privVal.Key.Address, 0, height-1, round, voteType, block1, nil), // height regression + newVote(privVal.Key.Address, 0, height-2, round+4, voteType, block1, nil), // height regression and different round + newVote(privVal.Key.Address, 0, height, round, voteType, block2, nil), // different block } for _, c := range cases { @@ -215,16 +197,10 @@ func TestSignProposal(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - tempKeyFile, err := os.CreateTemp(t.TempDir(), "priv_validator_key_") - require.NoError(t, err) - tempStateFile, err := os.CreateTemp(t.TempDir(), "priv_validator_state_") - require.NoError(t, err) + privVal, _, _ := newTestFilePV(t) - privVal, err := GenFilePV(tempKeyFile.Name(), tempStateFile.Name(), "") - require.NoError(t, err) - - randbytes := tmrand.Bytes(tmhash.Size) - randbytes2 := tmrand.Bytes(tmhash.Size) + randbytes := tmrand.Bytes(crypto.HashSize) + randbytes2 := tmrand.Bytes(crypto.HashSize) block1 := types.BlockID{Hash: randbytes, PartSetHeader: types.PartSetHeader{Total: 5, Hash: randbytes}} @@ -237,7 +213,7 @@ func TestSignProposal(t *testing.T) { proposal := newProposal(height, round, block1, ts) pbp := proposal.ToProto() - err = privVal.SignProposal(ctx, "mychainid", pbp) + err := privVal.SignProposal(ctx, "mychainid", pbp) assert.NoError(t, err, "expected no error signing proposal") // try to sign the same proposal again; should be fine @@ -270,7 +246,7 @@ func TestDifferByTimestamp(t *testing.T) { privVal, err := GenFilePV(tempKeyFile.Name(), tempStateFile.Name(), "") require.NoError(t, err) - randbytes := tmrand.Bytes(tmhash.Size) + randbytes := tmrand.Bytes(crypto.HashSize) height, round := int64(10), int32(1) chainID := "mychainid" @@ -278,7 +254,7 @@ func TestDifferByTimestamp(t *testing.T) { { voteType := tmproto.PrevoteType blockID := types.BlockID{Hash: randbytes, PartSetHeader: types.PartSetHeader{}} - vote := newVote(privVal.Key.Address, 0, height, round, voteType, blockID) + vote := newVote(privVal.Key.Address, 0, height, round, voteType, blockID, nil) v := vote.ToProto() err := privVal.SignVote(ctx, "mychainid", v) require.NoError(t, err, "expected no error signing vote") @@ -300,8 +276,69 @@ func TestDifferByTimestamp(t *testing.T) { } } +func TestVoteExtensionsAreAlwaysSigned(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + privVal, _, _ := newTestFilePV(t) + pubKey, err := privVal.GetPubKey(ctx) + assert.NoError(t, err) + + block := types.BlockID{ + Hash: tmrand.Bytes(crypto.HashSize), + PartSetHeader: types.PartSetHeader{Total: 5, Hash: tmrand.Bytes(crypto.HashSize)}, + } + + height, round := int64(10), int32(1) + voteType := tmproto.PrecommitType + + // We initially sign this vote without an extension + vote1 := newVote(privVal.Key.Address, 0, height, round, voteType, block, nil) + vpb1 := vote1.ToProto() + + err = privVal.SignVote(ctx, "mychainid", vpb1) + assert.NoError(t, err, "expected no error signing vote") + assert.NotNil(t, vpb1.ExtensionSignature) + + vesb1 := types.VoteExtensionSignBytes("mychainid", vpb1) + assert.True(t, pubKey.VerifySignature(vesb1, vpb1.ExtensionSignature)) + + // We duplicate this vote precisely, including its timestamp, but change + // its extension + vote2 := vote1.Copy() + vote2.Extension = []byte("new extension") + vpb2 := vote2.ToProto() + + err = privVal.SignVote(ctx, "mychainid", vpb2) + assert.NoError(t, err, "expected no error signing same vote with manipulated vote extension") + + // We need to ensure that a valid new extension signature has been created + // that validates against the vote extension sign bytes with the new + // extension, and does not validate against the vote extension sign bytes + // with the old extension. + vesb2 := types.VoteExtensionSignBytes("mychainid", vpb2) + assert.True(t, pubKey.VerifySignature(vesb2, vpb2.ExtensionSignature)) + assert.False(t, pubKey.VerifySignature(vesb1, vpb2.ExtensionSignature)) + + // We now manipulate the timestamp of the vote with the extension, as per + // TestDifferByTimestamp + expectedTimestamp := vpb2.Timestamp + + vpb2.Timestamp = vpb2.Timestamp.Add(time.Millisecond) + vpb2.Signature = nil + vpb2.ExtensionSignature = nil + + err = privVal.SignVote(ctx, "mychainid", vpb2) + assert.NoError(t, err, "expected no error signing same vote with manipulated timestamp and vote extension") + assert.Equal(t, expectedTimestamp, vpb2.Timestamp) + + vesb3 := types.VoteExtensionSignBytes("mychainid", vpb2) + assert.True(t, pubKey.VerifySignature(vesb3, vpb2.ExtensionSignature)) + assert.False(t, pubKey.VerifySignature(vesb1, vpb2.ExtensionSignature)) +} + func newVote(addr types.Address, idx int32, height int64, round int32, - typ tmproto.SignedMsgType, blockID types.BlockID) *types.Vote { + typ tmproto.SignedMsgType, blockID types.BlockID, extension []byte) *types.Vote { return &types.Vote{ ValidatorAddress: addr, ValidatorIndex: idx, @@ -310,6 +347,7 @@ func newVote(addr types.Address, idx int32, height int64, round int32, Type: typ, Timestamp: tmtime.Now(), BlockID: blockID, + Extension: extension, } } @@ -321,3 +359,15 @@ func newProposal(height int64, round int32, blockID types.BlockID, t time.Time) Timestamp: t, } } + +func newTestFilePV(t *testing.T) (*FilePV, string, string) { + tempKeyFile, err := os.CreateTemp(t.TempDir(), "priv_validator_key_") + require.NoError(t, err) + tempStateFile, err := os.CreateTemp(t.TempDir(), "priv_validator_state_") + require.NoError(t, err) + + privVal, err := GenFilePV(tempKeyFile.Name(), tempStateFile.Name(), "") + require.NoError(t, err) + + return privVal, tempKeyFile.Name(), tempStateFile.Name() +} diff --git a/privval/grpc/client_test.go b/privval/grpc/client_test.go index ac7608274..a81f6ce07 100644 --- a/privval/grpc/client_test.go +++ b/privval/grpc/client_test.go @@ -14,7 +14,6 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/log" tmrand "github.com/tendermint/tendermint/libs/rand" tmgrpc "github.com/tendermint/tendermint/privval/grpc" @@ -30,7 +29,7 @@ func dialer(t *testing.T, pv types.PrivValidator, logger log.Logger) (*grpc.Serv server := grpc.NewServer() - s := tmgrpc.NewSignerServer(chainID, pv, logger) + s := tmgrpc.NewSignerServer(logger, chainID, pv) privvalproto.RegisterPrivValidatorAPIServer(server, s) @@ -86,7 +85,7 @@ func TestSignerClient_SignVote(t *testing.T) { require.NoError(t, err) ts := time.Now() - hash := tmrand.Bytes(tmhash.Size) + hash := tmrand.Bytes(crypto.HashSize) valAddr := tmrand.Bytes(crypto.AddressSize) want := &types.Vote{ @@ -141,7 +140,7 @@ func TestSignerClient_SignProposal(t *testing.T) { require.NoError(t, err) ts := time.Now() - hash := tmrand.Bytes(tmhash.Size) + hash := tmrand.Bytes(crypto.HashSize) have := &types.Proposal{ Type: tmproto.ProposalType, diff --git a/privval/grpc/server.go b/privval/grpc/server.go index 13e0c9073..40af82479 100644 --- a/privval/grpc/server.go +++ b/privval/grpc/server.go @@ -21,11 +21,9 @@ type SignerServer struct { privVal types.PrivValidator } -func NewSignerServer(chainID string, - privVal types.PrivValidator, log log.Logger) *SignerServer { - +func NewSignerServer(logger log.Logger, chainID string, privVal types.PrivValidator) *SignerServer { return &SignerServer{ - logger: log, + logger: logger, chainID: chainID, privVal: privVal, } @@ -56,8 +54,7 @@ func (ss *SignerServer) GetPubKey(ctx context.Context, req *privvalproto.PubKeyR // SignVote receives a vote sign requests, attempts to sign it // returns SignedVoteResponse on success and error on failure -func (ss *SignerServer) SignVote(ctx context.Context, req *privvalproto.SignVoteRequest) ( - *privvalproto.SignedVoteResponse, error) { +func (ss *SignerServer) SignVote(ctx context.Context, req *privvalproto.SignVoteRequest) (*privvalproto.SignedVoteResponse, error) { vote := req.Vote err := ss.privVal.SignVote(ctx, req.ChainId, vote) @@ -72,8 +69,7 @@ func (ss *SignerServer) SignVote(ctx context.Context, req *privvalproto.SignVote // SignProposal receives a proposal sign requests, attempts to sign it // returns SignedProposalResponse on success and error on failure -func (ss *SignerServer) SignProposal(ctx context.Context, req *privvalproto.SignProposalRequest) ( - *privvalproto.SignedProposalResponse, error) { +func (ss *SignerServer) SignProposal(ctx context.Context, req *privvalproto.SignProposalRequest) (*privvalproto.SignedProposalResponse, error) { proposal := req.Proposal err := ss.privVal.SignProposal(ctx, req.ChainId, proposal) diff --git a/privval/grpc/server_test.go b/privval/grpc/server_test.go index 78e9c79ed..ad63b87a6 100644 --- a/privval/grpc/server_test.go +++ b/privval/grpc/server_test.go @@ -9,7 +9,6 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/log" tmrand "github.com/tendermint/tendermint/libs/rand" tmgrpc "github.com/tendermint/tendermint/privval/grpc" @@ -38,7 +37,7 @@ func TestGetPubKey(t *testing.T) { defer cancel() logger := log.NewTestingLogger(t) - s := tmgrpc.NewSignerServer(ChainID, tc.pv, logger) + s := tmgrpc.NewSignerServer(logger, ChainID, tc.pv) req := &privvalproto.PubKeyRequest{ChainId: ChainID} resp, err := s.GetPubKey(ctx, req) @@ -57,7 +56,7 @@ func TestGetPubKey(t *testing.T) { func TestSignVote(t *testing.T) { ts := time.Now() - hash := tmrand.Bytes(tmhash.Size) + hash := tmrand.Bytes(crypto.HashSize) valAddr := tmrand.Bytes(crypto.AddressSize) testCases := []struct { @@ -113,7 +112,7 @@ func TestSignVote(t *testing.T) { defer cancel() logger := log.NewTestingLogger(t) - s := tmgrpc.NewSignerServer(ChainID, tc.pv, logger) + s := tmgrpc.NewSignerServer(logger, ChainID, tc.pv) req := &privvalproto.SignVoteRequest{ChainId: ChainID, Vote: tc.have.ToProto()} resp, err := s.SignVote(ctx, req) @@ -133,7 +132,7 @@ func TestSignVote(t *testing.T) { func TestSignProposal(t *testing.T) { ts := time.Now() - hash := tmrand.Bytes(tmhash.Size) + hash := tmrand.Bytes(crypto.HashSize) testCases := []struct { name string @@ -184,7 +183,7 @@ func TestSignProposal(t *testing.T) { defer cancel() logger := log.NewTestingLogger(t) - s := tmgrpc.NewSignerServer(ChainID, tc.pv, logger) + s := tmgrpc.NewSignerServer(logger, ChainID, tc.pv) req := &privvalproto.SignProposalRequest{ChainId: ChainID, Proposal: tc.have.ToProto()} resp, err := s.SignProposal(ctx, req) diff --git a/privval/msgs_test.go b/privval/msgs_test.go index bbd3f6319..01cef4641 100644 --- a/privval/msgs_test.go +++ b/privval/msgs_test.go @@ -11,7 +11,6 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/encoding" - "github.com/tendermint/tendermint/crypto/tmhash" cryptoproto "github.com/tendermint/tendermint/proto/tendermint/crypto" privproto "github.com/tendermint/tendermint/proto/tendermint/privval" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -22,23 +21,14 @@ var stamp = time.Date(2019, 10, 13, 16, 14, 44, 0, time.UTC) func exampleVote() *types.Vote { return &types.Vote{ - Type: tmproto.SignedMsgType(1), - Height: 3, - Round: 2, - Timestamp: stamp, - BlockID: types.BlockID{ - Hash: tmhash.Sum([]byte("blockID_hash")), - PartSetHeader: types.PartSetHeader{ - Total: 1000000, - Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), - }, - }, + Type: tmproto.PrecommitType, + Height: 3, + Round: 2, + BlockID: types.BlockID{Hash: crypto.Checksum([]byte("blockID_hash")), PartSetHeader: types.PartSetHeader{Total: 1000000, Hash: crypto.Checksum([]byte("blockID_part_set_header_hash"))}}, + Timestamp: stamp, ValidatorAddress: crypto.AddressHash([]byte("validator_address")), ValidatorIndex: 56789, - VoteExtension: types.VoteExtension{ - AppDataToSign: []byte("app_data_signed"), - AppDataSelfAuthenticating: []byte("app_data_self_authenticating"), - }, + Extension: []byte("extension"), } } @@ -52,10 +42,10 @@ func exampleProposal() *types.Proposal { POLRound: 2, Signature: []byte("it's a signature"), BlockID: types.BlockID{ - Hash: tmhash.Sum([]byte("blockID_hash")), + Hash: crypto.Checksum([]byte("blockID_hash")), PartSetHeader: types.PartSetHeader{ Total: 1000000, - Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), + Hash: crypto.Checksum([]byte("blockID_part_set_header_hash")), }, }, } @@ -87,8 +77,8 @@ func TestPrivvalVectors(t *testing.T) { {"pubKey request", &privproto.PubKeyRequest{}, "0a00"}, {"pubKey response", &privproto.PubKeyResponse{PubKey: ppk, Error: nil}, "12240a220a20556a436f1218d30942efe798420f51dc9b6a311b929c578257457d05c5fcf230"}, {"pubKey response with error", &privproto.PubKeyResponse{PubKey: cryptoproto.PublicKey{}, Error: remoteError}, "12140a0012100801120c697427732061206572726f72"}, - {"Vote Request", &privproto.SignVoteRequest{Vote: votepb}, "1aa8010aa501080110031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0608f49a8ded0532146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb034a2f0a0f6170705f646174615f7369676e6564121c6170705f646174615f73656c665f61757468656e7469636174696e67"}, - {"Vote Response", &privproto.SignedVoteResponse{Vote: *votepb, Error: nil}, "22a8010aa501080110031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0608f49a8ded0532146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb034a2f0a0f6170705f646174615f7369676e6564121c6170705f646174615f73656c665f61757468656e7469636174696e67"}, + {"Vote Request", &privproto.SignVoteRequest{Vote: votepb}, "1a81010a7f080210031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0608f49a8ded0532146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb034a09657874656e73696f6e"}, + {"Vote Response", &privproto.SignedVoteResponse{Vote: *votepb, Error: nil}, "2281010a7f080210031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0608f49a8ded0532146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb034a09657874656e73696f6e"}, {"Vote Response with error", &privproto.SignedVoteResponse{Vote: tmproto.Vote{}, Error: remoteError}, "22250a11220212002a0b088092b8c398feffffff0112100801120c697427732061206572726f72"}, {"Proposal Request", &privproto.SignProposalRequest{Proposal: proposalpb}, "2a700a6e08011003180220022a4a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a320608f49a8ded053a10697427732061207369676e6174757265"}, {"Proposal Response", &privproto.SignedProposalResponse{Proposal: *proposalpb, Error: nil}, "32700a6e08011003180220022a4a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a320608f49a8ded053a10697427732061207369676e6174757265"}, diff --git a/privval/signer_client_test.go b/privval/signer_client_test.go index 272902fc9..fef16f2d6 100644 --- a/privval/signer_client_test.go +++ b/privval/signer_client_test.go @@ -11,7 +11,6 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/log" tmrand "github.com/tendermint/tendermint/libs/rand" cryptoproto "github.com/tendermint/tendermint/proto/tendermint/crypto" @@ -143,7 +142,7 @@ func TestSignerProposal(t *testing.T) { defer tc.closer() ts := time.Now() - hash := tmrand.Bytes(tmhash.Size) + hash := tmrand.Bytes(crypto.HashSize) have := &types.Proposal{ Type: tmproto.ProposalType, Height: 1, @@ -183,7 +182,7 @@ func TestSignerVote(t *testing.T) { defer tc.closer() ts := time.Now() - hash := tmrand.Bytes(tmhash.Size) + hash := tmrand.Bytes(crypto.HashSize) valAddr := tmrand.Bytes(crypto.AddressSize) want := &types.Vote{ Type: tmproto.PrecommitType, @@ -224,7 +223,7 @@ func TestSignerVoteResetDeadline(t *testing.T) { for _, tc := range getSignerTestCases(ctx, t, logger) { t.Run(tc.name, func(t *testing.T) { ts := time.Now() - hash := tmrand.Bytes(tmhash.Size) + hash := tmrand.Bytes(crypto.HashSize) valAddr := tmrand.Bytes(crypto.AddressSize) want := &types.Vote{ Type: tmproto.PrecommitType, @@ -277,7 +276,7 @@ func TestSignerVoteKeepAlive(t *testing.T) { defer tc.closer() ts := time.Now() - hash := tmrand.Bytes(tmhash.Size) + hash := tmrand.Bytes(crypto.HashSize) valAddr := tmrand.Bytes(crypto.AddressSize) want := &types.Vote{ Type: tmproto.PrecommitType, @@ -330,7 +329,7 @@ func TestSignerSignProposalErrors(t *testing.T) { tc.mockPV = types.NewErroringMockPV() ts := time.Now() - hash := tmrand.Bytes(tmhash.Size) + hash := tmrand.Bytes(crypto.HashSize) proposal := &types.Proposal{ Type: tmproto.ProposalType, Height: 1, @@ -368,7 +367,7 @@ func TestSignerSignVoteErrors(t *testing.T) { defer tc.closer() ts := time.Now() - hash := tmrand.Bytes(tmhash.Size) + hash := tmrand.Bytes(crypto.HashSize) valAddr := tmrand.Bytes(crypto.AddressSize) vote := &types.Vote{ Type: tmproto.PrecommitType, diff --git a/proto/Dockerfile b/proto/Dockerfile deleted file mode 100644 index 92fff39e6..000000000 --- a/proto/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -# This Dockerfile defines an image containing tools for linting, formatting, -# and compiling the Tendermint protos. -FROM golang:1.17-alpine - -# Install a commonly used set of programs for use with our protos. -# clang-extra-tools is included here because it provides clang-format, -# used to format the .proto files. -RUN apk add --no-cache build-base clang-extra-tools curl git - -ENV GOLANG_PROTOBUF_VERSION=1.3.1 \ - GOGO_PROTOBUF_VERSION=1.3.2 - -# Retrieve the go protoc programs and copy them into the PATH -RUN go install github.com/golang/protobuf/protoc-gen-go@v${GOLANG_PROTOBUF_VERSION} && \ - go install github.com/gogo/protobuf/protoc-gen-gogo@v${GOGO_PROTOBUF_VERSION} && \ - go install github.com/gogo/protobuf/protoc-gen-gogofaster@v${GOGO_PROTOBUF_VERSION} && \ - mv "$(go env GOPATH)"/bin/* /usr/local/bin/ - -# Copy the 'buf' program out of the buildbuf/buf container. -COPY --from=bufbuild/buf:latest /usr/local/bin/* /usr/local/bin/ diff --git a/proto/README.md b/proto/README.md index 07d61d62c..a0701d3bc 100644 --- a/proto/README.md +++ b/proto/README.md @@ -1,6 +1,6 @@ # Protocol Buffers -This sections defines the protocol buffers used in Tendermint. This is split into two directories: `spec`, the types required for all implementations and `tendermint`, a set of types internal to the Go implementation. All generated go code is also stored in `tendermint`. +This sections defines the protocol buffers used in Tendermint. This is split into two directories: `spec`, the types required for all implementations and `tendermint`, a set of types internal to the Go implementation. All generated go code is also stored in `tendermint`. More descriptions of the data structures are located in the spec directory as follows: - [Block](../spec/core/data_structures.md) @@ -14,7 +14,7 @@ The `.proto` files within this section are core to the protocol and updates must ### Steps 1. Make an issue with the proposed change. - - Within the issue members, from the Tendermint team will leave comments. If there is not consensus on the change an [RFC](../rfc/README.md) may be requested. + - Within the issue members, from the Tendermint team will leave comments. If there is not consensus on the change an [RFC](../docs/rfc/README.md) may be requested. 1a. Submission of an RFC as a pull request should be made to facilitate further discussion. 1b. Merge the RFC. 2. Make the necessary changes to the `.proto` file(s), [core data structures](../spec/core/data_structures.md) and/or [ABCI protocol](../spec/abci/apps.md). diff --git a/proto/buf.lock b/proto/buf.lock new file mode 100644 index 000000000..8c415e1af --- /dev/null +++ b/proto/buf.lock @@ -0,0 +1,7 @@ +# Generated by buf. DO NOT EDIT. +version: v1 +deps: + - remote: buf.build + owner: gogo + repository: protobuf + commit: 4df00b267f944190a229ce3695781e99 diff --git a/buf.yaml b/proto/buf.yaml similarity index 50% rename from buf.yaml rename to proto/buf.yaml index cc4aced57..816db10f7 100644 --- a/buf.yaml +++ b/proto/buf.yaml @@ -1,16 +1,11 @@ -version: v1beta1 - -build: - roots: - - proto - - third_party/proto +version: v1 +deps: + - buf.build/gogo/protobuf +breaking: + use: + - FILE lint: use: - BASIC - FILE_LOWER_SNAKE_CASE - UNARY_RPC - ignore: - - gogoproto -breaking: - use: - - FILE diff --git a/proto/tendermint/abci/types.proto b/proto/tendermint/abci/types.proto index 26f264958..d8143feb3 100644 --- a/proto/tendermint/abci/types.proto +++ b/proto/tendermint/abci/types.proto @@ -3,8 +3,6 @@ package tendermint.abci; option go_package = "github.com/tendermint/tendermint/abci/types"; -// For more information on gogo.proto, see: -// https://github.com/gogo/protobuf/blob/master/extensions.md import "tendermint/crypto/proof.proto"; import "tendermint/types/types.proto"; import "tendermint/crypto/keys.proto"; @@ -21,25 +19,25 @@ import "gogoproto/gogo.proto"; message Request { oneof value { - RequestEcho echo = 1; - RequestFlush flush = 2; - RequestInfo info = 3; - RequestInitChain init_chain = 4; - RequestQuery query = 5; - RequestBeginBlock begin_block = 6 [deprecated = true]; - RequestCheckTx check_tx = 7; - RequestDeliverTx deliver_tx = 8 [deprecated = true]; - RequestEndBlock end_block = 9 [deprecated = true]; - RequestCommit commit = 10; - RequestListSnapshots list_snapshots = 11; - RequestOfferSnapshot offer_snapshot = 12; - RequestLoadSnapshotChunk load_snapshot_chunk = 13; - RequestApplySnapshotChunk apply_snapshot_chunk = 14; - RequestPrepareProposal prepare_proposal = 15; - RequestProcessProposal process_proposal = 16; + RequestEcho echo = 1; + RequestFlush flush = 2; + RequestInfo info = 3; + RequestInitChain init_chain = 4; + RequestQuery query = 5; + RequestBeginBlock begin_block = 6 [deprecated = true]; + RequestCheckTx check_tx = 7; + RequestDeliverTx deliver_tx = 8 [deprecated = true]; + RequestEndBlock end_block = 9 [deprecated = true]; + RequestCommit commit = 10; + RequestListSnapshots list_snapshots = 11; + RequestOfferSnapshot offer_snapshot = 12; + RequestLoadSnapshotChunk load_snapshot_chunk = 13; + RequestApplySnapshotChunk apply_snapshot_chunk = 14; + RequestPrepareProposal prepare_proposal = 15; + RequestProcessProposal process_proposal = 16; RequestExtendVote extend_vote = 17; RequestVerifyVoteExtension verify_vote_extension = 18; - RequestFinalizeBlock finalize_block = 19; + RequestFinalizeBlock finalize_block = 19; } } @@ -76,7 +74,7 @@ message RequestBeginBlock { bytes hash = 1; tendermint.types.Header header = 2 [(gogoproto.nullable) = false]; CommitInfo last_commit_info = 3 [(gogoproto.nullable) = false]; - repeated Evidence byzantine_validators = 4 [(gogoproto.nullable) = false]; + repeated Misbehavior byzantine_validators = 4 [(gogoproto.nullable) = false]; } enum CheckTxType { @@ -123,23 +121,31 @@ message RequestApplySnapshotChunk { } message RequestPrepareProposal { - bytes hash = 1; - tendermint.types.Header header = 2 [(gogoproto.nullable) = false]; + // the modified transactions cannot exceed this size. + int64 max_tx_bytes = 1; // txs is an array of transactions that will be included in a block, // sent to the app for possible modifications. - repeated bytes txs = 3; - ExtendedCommitInfo local_last_commit = 4 [(gogoproto.nullable) = false]; - repeated Evidence byzantine_validators = 5 [(gogoproto.nullable) = false]; - // the modified transactions cannot exceed this size. - int64 max_tx_bytes = 6; + repeated bytes txs = 2; + ExtendedCommitInfo local_last_commit = 3 [(gogoproto.nullable) = false]; + repeated Misbehavior byzantine_validators = 4 [(gogoproto.nullable) = false]; + int64 height = 5; + google.protobuf.Timestamp time = 6 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + bytes next_validators_hash = 7; + // address of the public key of the validator proposing the block. + bytes proposer_address = 8; } message RequestProcessProposal { - bytes hash = 1; - tendermint.types.Header header = 2 [(gogoproto.nullable) = false]; - repeated bytes txs = 3; - CommitInfo proposed_last_commit = 4 [(gogoproto.nullable) = false]; - repeated Evidence byzantine_validators = 5 [(gogoproto.nullable) = false]; + repeated bytes txs = 1; + CommitInfo proposed_last_commit = 2 [(gogoproto.nullable) = false]; + repeated Misbehavior byzantine_validators = 3 [(gogoproto.nullable) = false]; + // hash is the merkle root hash of the fields of the proposed block. + bytes hash = 4; + int64 height = 5; + google.protobuf.Timestamp time = 6 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + bytes next_validators_hash = 7; + // address of the public key of the original proposer of the block. + bytes proposer_address = 8; } // Extends a vote with application-side injection @@ -157,11 +163,16 @@ message RequestVerifyVoteExtension { } message RequestFinalizeBlock { - bytes hash = 1; - tendermint.types.Header header = 2 [(gogoproto.nullable) = false]; - repeated bytes txs = 3; - CommitInfo decided_last_commit = 4 [(gogoproto.nullable) = false]; - repeated Evidence byzantine_validators = 5 [(gogoproto.nullable) = false]; + repeated bytes txs = 1; + CommitInfo decided_last_commit = 2 [(gogoproto.nullable) = false]; + repeated Misbehavior byzantine_validators = 3 [(gogoproto.nullable) = false]; + // hash is the merkle root hash of the fields of the proposed block. + bytes hash = 4; + int64 height = 5; + google.protobuf.Timestamp time = 6 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + bytes next_validators_hash = 7; + // proposer_address is the address of the public key of the original proposer of the block. + bytes proposer_address = 8; } //---------------------------------------- @@ -169,26 +180,26 @@ message RequestFinalizeBlock { message Response { oneof value { - ResponseException exception = 1; - ResponseEcho echo = 2; - ResponseFlush flush = 3; - ResponseInfo info = 4; - ResponseInitChain init_chain = 5; - ResponseQuery query = 6; - ResponseBeginBlock begin_block = 7 [deprecated = true]; - ResponseCheckTx check_tx = 8; - ResponseDeliverTx deliver_tx = 9 [deprecated = true]; - ResponseEndBlock end_block = 10 [deprecated = true]; - ResponseCommit commit = 11; - ResponseListSnapshots list_snapshots = 12; - ResponseOfferSnapshot offer_snapshot = 13; - ResponseLoadSnapshotChunk load_snapshot_chunk = 14; - ResponseApplySnapshotChunk apply_snapshot_chunk = 15; - ResponsePrepareProposal prepare_proposal = 16; - ResponseProcessProposal process_proposal = 17; + ResponseException exception = 1; + ResponseEcho echo = 2; + ResponseFlush flush = 3; + ResponseInfo info = 4; + ResponseInitChain init_chain = 5; + ResponseQuery query = 6; + ResponseBeginBlock begin_block = 7 [deprecated = true]; + ResponseCheckTx check_tx = 8; + ResponseDeliverTx deliver_tx = 9 [deprecated = true]; + ResponseEndBlock end_block = 10 [deprecated = true]; + ResponseCommit commit = 11; + ResponseListSnapshots list_snapshots = 12; + ResponseOfferSnapshot offer_snapshot = 13; + ResponseLoadSnapshotChunk load_snapshot_chunk = 14; + ResponseApplySnapshotChunk apply_snapshot_chunk = 15; + ResponsePrepareProposal prepare_proposal = 16; + ResponseProcessProposal process_proposal = 17; ResponseExtendVote extend_vote = 18; ResponseVerifyVoteExtension verify_vote_extension = 19; - ResponseFinalizeBlock finalize_block = 20; + ResponseFinalizeBlock finalize_block = 20; } } @@ -316,36 +327,46 @@ message ResponseApplySnapshotChunk { } message ResponsePrepareProposal { - bool modified_tx = 1; - repeated TxRecord tx_records = 2; - bytes app_hash = 3; - repeated ExecTxResult tx_results = 4; - repeated ValidatorUpdate validator_updates = 5; - tendermint.types.ConsensusParams consensus_param_updates = 6; - repeated bytes app_signed_updates = 7; -} - -message ResponseProcessProposal { - bool accept = 1; + repeated TxRecord tx_records = 1; bytes app_hash = 2; repeated ExecTxResult tx_results = 3; repeated ValidatorUpdate validator_updates = 4; tendermint.types.ConsensusParams consensus_param_updates = 5; } +message ResponseProcessProposal { + ProposalStatus status = 1; + bytes app_hash = 2; + repeated ExecTxResult tx_results = 3; + repeated ValidatorUpdate validator_updates = 4; + tendermint.types.ConsensusParams consensus_param_updates = 5; + + enum ProposalStatus { + UNKNOWN = 0; + ACCEPT = 1; + REJECT = 2; + } +} + message ResponseExtendVote { bytes vote_extension = 1; } message ResponseVerifyVoteExtension { - bool accept = 1; + VerifyStatus status = 1; + + enum VerifyStatus { + UNKNOWN = 0; + ACCEPT = 1; + REJECT = 2; + } } message ResponseFinalizeBlock { repeated Event events = 1 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; repeated ExecTxResult tx_results = 2; - repeated ValidatorUpdate validator_updates = 3; + repeated ValidatorUpdate validator_updates = 3 [(gogoproto.nullable) = false]; tendermint.types.ConsensusParams consensus_param_updates = 4; bytes app_hash = 5; int64 retain_height = 6; @@ -359,8 +380,14 @@ message CommitInfo { repeated VoteInfo votes = 2 [(gogoproto.nullable) = false]; } +// ExtendedCommitInfo is similar to CommitInfo except that it is only used in +// the PrepareProposal request such that Tendermint can provide vote extensions +// to the application. message ExtendedCommitInfo { - int32 round = 1; + // The round at which the block proposer decided in the previous height. + int32 round = 1; + // List of validators' addresses in the last validator set with their voting + // information, including vote extensions. repeated ExtendedVoteInfo votes = 2 [(gogoproto.nullable) = false]; } @@ -389,7 +416,7 @@ message ExecTxResult { string info = 4; // nondeterministic int64 gas_wanted = 5; int64 gas_used = 6; - repeated Event events = 7 + repeated Event events = 7 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; // nondeterministic string codespace = 8; } @@ -398,15 +425,15 @@ message ExecTxResult { // // One usage is indexing transaction results. message TxResult { - int64 height = 1; - uint32 index = 2; - bytes tx = 3; - ResponseDeliverTx result = 4 [(gogoproto.nullable) = false]; + int64 height = 1; + uint32 index = 2; + bytes tx = 3; + ExecTxResult result = 4 [(gogoproto.nullable) = false]; } message TxRecord { - TxAction action = 1; - bytes tx = 2; + TxAction action = 1; + bytes tx = 2; // TxAction contains App-provided information on what to do with a transaction that is part of a raw proposal enum TxAction { @@ -441,29 +468,22 @@ message VoteInfo { // ExtendedVoteInfo message ExtendedVoteInfo { - Validator validator = 1 [(gogoproto.nullable) = false]; - bool signed_last_block = 2; - bytes vote_extension = 3; + // The validator that sent the vote. + Validator validator = 1 [(gogoproto.nullable) = false]; + // Indicates whether the validator signed the last block, allowing for rewards based on validator availability. + bool signed_last_block = 2; + // Non-deterministic extension provided by the sending validator's application. + bytes vote_extension = 3; } -// CanonicalVoteExtension -// TODO: move this to core Tendermint data structures -message CanonicalVoteExtension { - bytes extension = 1; - int64 height = 2; - int32 round = 3; - string chain_id = 4; - bytes address = 5; -} - -enum EvidenceType { +enum MisbehaviorType { UNKNOWN = 0; DUPLICATE_VOTE = 1; LIGHT_CLIENT_ATTACK = 2; } -message Evidence { - EvidenceType type = 1; +message Misbehavior { + MisbehaviorType type = 1; // The offending validator Validator validator = 2 [(gogoproto.nullable) = false]; // The height when the offense occurred diff --git a/proto/tendermint/abci/types.proto.intermediate b/proto/tendermint/abci/types.proto.intermediate deleted file mode 100644 index b84f4c910..000000000 --- a/proto/tendermint/abci/types.proto.intermediate +++ /dev/null @@ -1,477 +0,0 @@ -syntax = "proto3"; -package tendermint.abci; - -import "tendermint/crypto/proof.proto"; -import "tendermint/types/types.proto"; -import "tendermint/crypto/keys.proto"; -import "tendermint/types/params.proto"; -import "google/protobuf/timestamp.proto"; -import "gogoproto/gogo.proto"; - - -// This file is a temporary workaround to enable development during the ABCI++ -// project. This file should be deleted and any references to it removed when -// the ongoing work on ABCI++ is completed. -// -// For the duration of ABCI++, this file should be able to build the `abci/types/types.pb.go` -// file. Any changes that update that file must come as a result of a change in -// this .proto file. -// For more information, see https://github.com/tendermint/tendermint/issues/8066 - -//---------------------------------------- -// Request types - -message Request { - oneof value { - RequestEcho echo = 1; - RequestFlush flush = 2; - RequestInfo info = 3; - RequestInitChain init_chain = 4; - RequestQuery query = 5; - RequestBeginBlock begin_block = 6 [deprecated = true]; - RequestCheckTx check_tx = 7; - RequestDeliverTx deliver_tx = 8 [deprecated = true]; - RequestEndBlock end_block = 9 [deprecated = true]; - RequestCommit commit = 10; - RequestListSnapshots list_snapshots = 11; - RequestOfferSnapshot offer_snapshot = 12; - RequestLoadSnapshotChunk load_snapshot_chunk = 13; - RequestApplySnapshotChunk apply_snapshot_chunk = 14; - RequestPrepareProposal prepare_proposal = 15; - RequestProcessProposal process_proposal = 16; - RequestExtendVote extend_vote = 17; - RequestVerifyVoteExtension verify_vote_extension = 18; - RequestFinalizeBlock finalize_block = 19; - } -} - -message RequestEcho { - string message = 1; -} - -message RequestFlush {} - -message RequestInfo { - string version = 1; - uint64 block_version = 2; - uint64 p2p_version = 3; - string abci_version = 4; -} - -message RequestInitChain { - google.protobuf.Timestamp time = 1 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; - string chain_id = 2; - tendermint.types.ConsensusParams consensus_params = 3; - repeated ValidatorUpdate validators = 4 [(gogoproto.nullable) = false]; - bytes app_state_bytes = 5; - int64 initial_height = 6; -} - -message RequestQuery { - bytes data = 1; - string path = 2; - int64 height = 3; - bool prove = 4; -} - -message RequestBeginBlock { - bytes hash = 1; - tendermint.types.Header header = 2 [(gogoproto.nullable) = false]; - CommitInfo last_commit_info = 3 [(gogoproto.nullable) = false]; - repeated Evidence byzantine_validators = 4 [(gogoproto.nullable) = false]; -} - -enum CheckTxType { - NEW = 0 [(gogoproto.enumvalue_customname) = "New"]; - RECHECK = 1 [(gogoproto.enumvalue_customname) = "Recheck"]; -} - -message RequestCheckTx { - bytes tx = 1; - CheckTxType type = 2; -} - -message RequestDeliverTx { - bytes tx = 1; -} - -message RequestEndBlock { - int64 height = 1; -} - -message RequestCommit {} - -// lists available snapshots -message RequestListSnapshots {} - -// offers a snapshot to the application -message RequestOfferSnapshot { - Snapshot snapshot = 1; // snapshot offered by peers - bytes app_hash = 2; // light client-verified app hash for snapshot height -} - -// loads a snapshot chunk -message RequestLoadSnapshotChunk { - uint64 height = 1; - uint32 format = 2; - uint32 chunk = 3; -} - -// Applies a snapshot chunk -message RequestApplySnapshotChunk { - uint32 index = 1; - bytes chunk = 2; - string sender = 3; -} - -// Extends a vote with application-side injection -message RequestExtendVote { - types.Vote vote = 1; -} - -// Verify the vote extension -message RequestVerifyVoteExtension { - types.Vote vote = 1; -} - -message RequestPrepareProposal { - // block_data is an array of transactions that will be included in a block, - // sent to the app for possible modifications. - // applications can not exceed the size of the data passed to it. - repeated bytes block_data = 1; - // If an application decides to populate block_data with extra information, they can not exceed this value. - int64 block_data_size = 2; - // votes includes all votes from the previous block. This contains vote extension data that can be used in proposal - // preparation. The votes here will then form the last commit that gets sent in the proposed block. - repeated tendermint.types.Vote votes = 3; -} - - -message RequestProcessProposal { - bytes hash = 1; - tendermint.types.Header header = 2 [(gogoproto.nullable) = false]; - repeated bytes txs = 3; - CommitInfo proposed_last_commit = 4 [(gogoproto.nullable) = false]; - repeated Evidence byzantine_validators = 5 [(gogoproto.nullable) = false]; -} - -message RequestFinalizeBlock { - bytes hash = 1; - tendermint.types.Header header = 2 [(gogoproto.nullable) = false]; - repeated bytes txs = 3; - CommitInfo decided_last_commit = 4 [(gogoproto.nullable) = false]; - repeated Evidence byzantine_validators = 5 [(gogoproto.nullable) = false]; -} - -//---------------------------------------- -// Response types - -message Response { - oneof value { - ResponseException exception = 1; - ResponseEcho echo = 2; - ResponseFlush flush = 3; - ResponseInfo info = 4; - ResponseInitChain init_chain = 5; - ResponseQuery query = 6; - ResponseBeginBlock begin_block = 7 [deprecated = true]; - ResponseCheckTx check_tx = 8; - ResponseDeliverTx deliver_tx = 9 [deprecated = true]; - ResponseEndBlock end_block = 10 [deprecated = true]; - ResponseCommit commit = 11; - ResponseListSnapshots list_snapshots = 12; - ResponseOfferSnapshot offer_snapshot = 13; - ResponseLoadSnapshotChunk load_snapshot_chunk = 14; - ResponseApplySnapshotChunk apply_snapshot_chunk = 15; - ResponsePrepareProposal prepare_proposal = 16; - ResponseProcessProposal process_proposal = 17; - ResponseExtendVote extend_vote = 18; - ResponseVerifyVoteExtension verify_vote_extension = 19; - ResponseFinalizeBlock finalize_block = 20; - } -} - -// nondeterministic -message ResponseException { - string error = 1; -} - -message ResponseEcho { - string message = 1; -} - -message ResponseFlush {} - -message ResponseInfo { - string data = 1; - - // this is the software version of the application. TODO: remove? - string version = 2; - uint64 app_version = 3; - - int64 last_block_height = 4; - bytes last_block_app_hash = 5; -} - -message ResponseInitChain { - tendermint.types.ConsensusParams consensus_params = 1; - repeated ValidatorUpdate validators = 2 [(gogoproto.nullable) = false]; - bytes app_hash = 3; -} - -message ResponseQuery { - uint32 code = 1; - // bytes data = 2; // use "value" instead. - string log = 3; // nondeterministic - string info = 4; // nondeterministic - int64 index = 5; - bytes key = 6; - bytes value = 7; - tendermint.crypto.ProofOps proof_ops = 8; - int64 height = 9; - string codespace = 10; -} - -message ResponseBeginBlock { - repeated Event events = 1 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; -} - -message ResponseCheckTx { - uint32 code = 1; - bytes data = 2; - string log = 3; // nondeterministic - string info = 4; // nondeterministic - int64 gas_wanted = 5; - int64 gas_used = 6; - repeated Event events = 7 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; - string codespace = 8; - string sender = 9; - int64 priority = 10; - - // mempool_error is set by Tendermint. - - // ABCI applications creating a ResponseCheckTX should not set mempool_error. - string mempool_error = 11; -} - -message ResponseDeliverTx { - uint32 code = 1; - bytes data = 2; - string log = 3; // nondeterministic - string info = 4; // nondeterministic - int64 gas_wanted = 5 [json_name = "gas_wanted"]; - int64 gas_used = 6 [json_name = "gas_used"]; - repeated Event events = 7 - [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; // nondeterministic - string codespace = 8; -} - -message ResponseEndBlock { - repeated ValidatorUpdate validator_updates = 1 [(gogoproto.nullable) = false]; - tendermint.types.ConsensusParams consensus_param_updates = 2; - repeated Event events = 3 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; -} - -message ResponseCommit { - // reserve 1 - bytes data = 2; - int64 retain_height = 3; -} - -message ResponseListSnapshots { - repeated Snapshot snapshots = 1; -} - -message ResponseOfferSnapshot { - Result result = 1; - - enum Result { - UNKNOWN = 0; // Unknown result, abort all snapshot restoration - ACCEPT = 1; // Snapshot accepted, apply chunks - ABORT = 2; // Abort all snapshot restoration - REJECT = 3; // Reject this specific snapshot, try others - REJECT_FORMAT = 4; // Reject all snapshots of this format, try others - REJECT_SENDER = 5; // Reject all snapshots from the sender(s), try others - } -} - -message ResponseLoadSnapshotChunk { - bytes chunk = 1; -} - -message ResponseApplySnapshotChunk { - Result result = 1; - repeated uint32 refetch_chunks = 2; // Chunks to refetch and reapply - repeated string reject_senders = 3; // Chunk senders to reject and ban - - enum Result { - UNKNOWN = 0; // Unknown result, abort all snapshot restoration - ACCEPT = 1; // Chunk successfully accepted - ABORT = 2; // Abort all snapshot restoration - RETRY = 3; // Retry chunk (combine with refetch and reject) - RETRY_SNAPSHOT = 4; // Retry snapshot (combine with refetch and reject) - REJECT_SNAPSHOT = 5; // Reject this snapshot, try others - } -} - -message ResponseExtendVote { - tendermint.types.VoteExtension vote_extension = 1; -} - -message ResponseVerifyVoteExtension { - Result result = 1; - - enum Result { - UNKNOWN = 0; // Unknown result, reject vote extension - ACCEPT = 1; // Vote extension verified, include the vote - SLASH = 2; // Vote extension verification aborted, continue but slash validator - REJECT = 3; // Vote extension invalidated - } -} - -message ResponsePrepareProposal { - repeated bytes block_data = 1; -} - -message ResponseProcessProposal { - bool accept = 1; - bytes app_hash = 2; - repeated ExecTxResult tx_results = 3; - repeated ValidatorUpdate validator_updates = 4; - tendermint.types.ConsensusParams consensus_param_updates = 5; -} - -message ResponseFinalizeBlock { - repeated Event events = 1 - [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; - repeated ExecTxResult tx_results = 2; - repeated ValidatorUpdate validator_updates = 3 [(gogoproto.nullable) = false]; - tendermint.types.ConsensusParams consensus_param_updates = 4; - bytes app_hash = 5; - int64 retain_height = 6; -} - -//---------------------------------------- -// Misc. - -message CommitInfo { - int32 round = 1; - repeated VoteInfo votes = 2 [(gogoproto.nullable) = false]; -} - -// Event allows application developers to attach additional information to -// ResponseBeginBlock, ResponseEndBlock, ResponseCheckTx and ResponseDeliverTx. -// Later, transactions may be queried using these events. -message Event { - string type = 1; - repeated EventAttribute attributes = 2 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "attributes,omitempty"]; -} - -// EventAttribute is a single key-value pair, associated with an event. -message EventAttribute { - string key = 1; - string value = 2; - bool index = 3; // nondeterministic -} - -// ExecTxResult contains results of executing one individual transaction. -// -// * Its structure is equivalent to #ResponseDeliverTx which will be deprecated/deleted -message ExecTxResult { - uint32 code = 1; - bytes data = 2; - string log = 3; // nondeterministic - string info = 4; // nondeterministic - int64 gas_wanted = 5; - int64 gas_used = 6; - repeated Event events = 7 - [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; // nondeterministic - string codespace = 8; -} - -// TxResult contains results of executing the transaction. -// -// One usage is indexing transaction results. -message TxResult { - int64 height = 1; - uint32 index = 2; - bytes tx = 3; - ExecTxResult result = 4 [(gogoproto.nullable) = false]; -} - -//---------------------------------------- -// Blockchain Types - -// Validator -message Validator { - bytes address = 1; // The first 20 bytes of SHA256(public key) - // PubKey pub_key = 2 [(gogoproto.nullable)=false]; - int64 power = 3; // The voting power -} - -// ValidatorUpdate -message ValidatorUpdate { - tendermint.crypto.PublicKey pub_key = 1 [(gogoproto.nullable) = false]; - int64 power = 2; -} - -// VoteInfo -message VoteInfo { - Validator validator = 1 [(gogoproto.nullable) = false]; - bool signed_last_block = 2; - reserved 3; // Placeholder for tendermint_signed_extension in v0.37 - reserved 4; // Placeholder for app_signed_extension in v0.37 -} - -enum EvidenceType { - UNKNOWN = 0; - DUPLICATE_VOTE = 1; - LIGHT_CLIENT_ATTACK = 2; -} - -message Evidence { - EvidenceType type = 1; - // The offending validator - Validator validator = 2 [(gogoproto.nullable) = false]; - // The height when the offense occurred - int64 height = 3; - // The corresponding time where the offense occurred - google.protobuf.Timestamp time = 4 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; - // Total voting power of the validator set in case the ABCI application does - // not store historical validators. - // https://github.com/tendermint/tendermint/issues/4581 - int64 total_voting_power = 5; -} - -//---------------------------------------- -// State Sync Types - -message Snapshot { - uint64 height = 1; // The height at which the snapshot was taken - uint32 format = 2; // The application-specific snapshot format - uint32 chunks = 3; // Number of chunks in the snapshot - bytes hash = 4; // Arbitrary snapshot hash, equal only if identical - bytes metadata = 5; // Arbitrary application metadata -} - -//---------------------------------------- -// Service Definition - -service ABCIApplication { - rpc Echo(RequestEcho) returns (ResponseEcho); - rpc Flush(RequestFlush) returns (ResponseFlush); - rpc Info(RequestInfo) returns (ResponseInfo); - rpc CheckTx(RequestCheckTx) returns (ResponseCheckTx); - rpc Query(RequestQuery) returns (ResponseQuery); - rpc Commit(RequestCommit) returns (ResponseCommit); - rpc InitChain(RequestInitChain) returns (ResponseInitChain); - rpc ListSnapshots(RequestListSnapshots) returns (ResponseListSnapshots); - rpc OfferSnapshot(RequestOfferSnapshot) returns (ResponseOfferSnapshot); - rpc LoadSnapshotChunk(RequestLoadSnapshotChunk) returns (ResponseLoadSnapshotChunk); - rpc ApplySnapshotChunk(RequestApplySnapshotChunk) returns (ResponseApplySnapshotChunk); - rpc PrepareProposal(RequestPrepareProposal) returns (ResponsePrepareProposal); - rpc ProcessProposal(RequestProcessProposal) returns (ResponseProcessProposal); - rpc ExtendVote(RequestExtendVote) returns (ResponseExtendVote); - rpc VerifyVoteExtension(RequestVerifyVoteExtension) returns (ResponseVerifyVoteExtension); - rpc FinalizeBlock(RequestFinalizeBlock) returns (ResponseFinalizeBlock); -} diff --git a/proto/tendermint/blocksync/types.proto b/proto/tendermint/blocksync/types.proto index 999a6db7f..4febfd145 100644 --- a/proto/tendermint/blocksync/types.proto +++ b/proto/tendermint/blocksync/types.proto @@ -1,6 +1,8 @@ syntax = "proto3"; package tendermint.blocksync; +option go_package = "github.com/tendermint/tendermint/proto/tendermint/blocksync"; + import "tendermint/types/block.proto"; // BlockRequest requests a block for a specific height diff --git a/proto/tendermint/consensus/types.proto b/proto/tendermint/consensus/types.proto index fd0e427d0..7abe0d74b 100644 --- a/proto/tendermint/consensus/types.proto +++ b/proto/tendermint/consensus/types.proto @@ -1,6 +1,8 @@ syntax = "proto3"; package tendermint.consensus; +option go_package = "github.com/tendermint/tendermint/proto/tendermint/consensus"; + import "gogoproto/gogo.proto"; import "tendermint/types/types.proto"; import "tendermint/libs/bits/types.proto"; diff --git a/proto/tendermint/consensus/wal.proto b/proto/tendermint/consensus/wal.proto index 22531e0d0..44afa2c0c 100644 --- a/proto/tendermint/consensus/wal.proto +++ b/proto/tendermint/consensus/wal.proto @@ -1,6 +1,8 @@ syntax = "proto3"; package tendermint.consensus; +option go_package = "github.com/tendermint/tendermint/proto/tendermint/consensus"; + import "gogoproto/gogo.proto"; import "tendermint/consensus/types.proto"; import "tendermint/types/events.proto"; diff --git a/proto/tendermint/crypto/keys.proto b/proto/tendermint/crypto/keys.proto index faaaed6fc..d66f9fc0c 100644 --- a/proto/tendermint/crypto/keys.proto +++ b/proto/tendermint/crypto/keys.proto @@ -1,6 +1,8 @@ syntax = "proto3"; package tendermint.crypto; +option go_package = "github.com/tendermint/tendermint/proto/tendermint/crypto"; + import "gogoproto/gogo.proto"; // PublicKey defines the keys available for use with Tendermint Validators diff --git a/proto/tendermint/crypto/proof.proto b/proto/tendermint/crypto/proof.proto index bde5a4ff9..975df7685 100644 --- a/proto/tendermint/crypto/proof.proto +++ b/proto/tendermint/crypto/proof.proto @@ -1,6 +1,8 @@ syntax = "proto3"; package tendermint.crypto; +option go_package = "github.com/tendermint/tendermint/proto/tendermint/crypto"; + import "gogoproto/gogo.proto"; message Proof { diff --git a/proto/tendermint/libs/bits/types.proto b/proto/tendermint/libs/bits/types.proto index 1ea81d33f..3111d113a 100644 --- a/proto/tendermint/libs/bits/types.proto +++ b/proto/tendermint/libs/bits/types.proto @@ -1,6 +1,8 @@ syntax = "proto3"; package tendermint.libs.bits; +option go_package = "github.com/tendermint/tendermint/proto/tendermint/libs/bits"; + message BitArray { int64 bits = 1; repeated uint64 elems = 2; diff --git a/proto/tendermint/mempool/types.proto b/proto/tendermint/mempool/types.proto index 7fa53ef79..b55d9717b 100644 --- a/proto/tendermint/mempool/types.proto +++ b/proto/tendermint/mempool/types.proto @@ -1,6 +1,8 @@ syntax = "proto3"; package tendermint.mempool; +option go_package = "github.com/tendermint/tendermint/proto/tendermint/mempool"; + message Txs { repeated bytes txs = 1; } diff --git a/proto/tendermint/p2p/conn.proto b/proto/tendermint/p2p/conn.proto index 62abd4f5f..b12de6c82 100644 --- a/proto/tendermint/p2p/conn.proto +++ b/proto/tendermint/p2p/conn.proto @@ -1,6 +1,8 @@ syntax = "proto3"; package tendermint.p2p; +option go_package = "github.com/tendermint/tendermint/proto/tendermint/p2p"; + import "gogoproto/gogo.proto"; import "tendermint/crypto/keys.proto"; diff --git a/proto/tendermint/p2p/pex.proto b/proto/tendermint/p2p/pex.proto index 374047b0f..545743444 100644 --- a/proto/tendermint/p2p/pex.proto +++ b/proto/tendermint/p2p/pex.proto @@ -1,6 +1,8 @@ syntax = "proto3"; package tendermint.p2p; +option go_package = "github.com/tendermint/tendermint/proto/tendermint/p2p"; + import "gogoproto/gogo.proto"; message PexAddress { diff --git a/proto/tendermint/p2p/types.proto b/proto/tendermint/p2p/types.proto index e4e86434a..faccd59d2 100644 --- a/proto/tendermint/p2p/types.proto +++ b/proto/tendermint/p2p/types.proto @@ -1,6 +1,8 @@ syntax = "proto3"; package tendermint.p2p; +option go_package = "github.com/tendermint/tendermint/proto/tendermint/p2p"; + import "gogoproto/gogo.proto"; import "google/protobuf/timestamp.proto"; diff --git a/proto/tendermint/privval/service.proto b/proto/tendermint/privval/service.proto index 63e9afca7..2c699e1cd 100644 --- a/proto/tendermint/privval/service.proto +++ b/proto/tendermint/privval/service.proto @@ -1,6 +1,6 @@ syntax = "proto3"; package tendermint.privval; -option go_package = "github.com/tendermint/tendermint/proto/tendermint/privval"; +option go_package = "github.com/tendermint/tendermint/proto/tendermint/privval"; import "tendermint/privval/types.proto"; diff --git a/proto/tendermint/statesync/message_test.go b/proto/tendermint/statesync/message_test.go index a0b241615..1db421aca 100644 --- a/proto/tendermint/statesync/message_test.go +++ b/proto/tendermint/statesync/message_test.go @@ -3,6 +3,7 @@ package statesync_test import ( "encoding/hex" "testing" + "time" "github.com/gogo/protobuf/proto" "github.com/stretchr/testify/require" @@ -204,8 +205,8 @@ func TestStateSyncVectors(t *testing.T) { AppVersion: 11, }, Synchrony: &tmproto.SynchronyParams{ - MessageDelay: 550, - Precision: 90, + MessageDelay: durationPtr(550), + Precision: durationPtr(90), }, }, }, @@ -224,3 +225,7 @@ func TestStateSyncVectors(t *testing.T) { require.Equal(t, tc.expBytes, hex.EncodeToString(bz), tc.testName) } } + +func durationPtr(t time.Duration) *time.Duration { + return &t +} diff --git a/proto/tendermint/statesync/types.proto b/proto/tendermint/statesync/types.proto index 12f7a1d23..94e22e834 100644 --- a/proto/tendermint/statesync/types.proto +++ b/proto/tendermint/statesync/types.proto @@ -1,6 +1,8 @@ syntax = "proto3"; package tendermint.statesync; +option go_package = "github.com/tendermint/tendermint/proto/tendermint/statesync"; + import "gogoproto/gogo.proto"; import "tendermint/types/types.proto"; import "tendermint/types/params.proto"; diff --git a/proto/tendermint/types/block.proto b/proto/tendermint/types/block.proto index 8a713b7dc..84e9bb15d 100644 --- a/proto/tendermint/types/block.proto +++ b/proto/tendermint/types/block.proto @@ -1,6 +1,8 @@ syntax = "proto3"; package tendermint.types; +option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; + import "gogoproto/gogo.proto"; import "tendermint/types/types.proto"; import "tendermint/types/evidence.proto"; diff --git a/proto/tendermint/types/canonical.pb.go b/proto/tendermint/types/canonical.pb.go index 0cd7386f7..50c0c84fa 100644 --- a/proto/tendermint/types/canonical.pb.go +++ b/proto/tendermint/types/canonical.pb.go @@ -225,13 +225,12 @@ func (m *CanonicalProposal) GetChainID() string { } type CanonicalVote struct { - Type SignedMsgType `protobuf:"varint,1,opt,name=type,proto3,enum=tendermint.types.SignedMsgType" json:"type,omitempty"` - Height int64 `protobuf:"fixed64,2,opt,name=height,proto3" json:"height,omitempty"` - Round int64 `protobuf:"fixed64,3,opt,name=round,proto3" json:"round,omitempty"` - BlockID *CanonicalBlockID `protobuf:"bytes,4,opt,name=block_id,json=blockId,proto3" json:"block_id,omitempty"` - Timestamp time.Time `protobuf:"bytes,5,opt,name=timestamp,proto3,stdtime" json:"timestamp"` - ChainID string `protobuf:"bytes,6,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` - VoteExtension *VoteExtensionToSign `protobuf:"bytes,7,opt,name=vote_extension,json=voteExtension,proto3" json:"vote_extension,omitempty"` + Type SignedMsgType `protobuf:"varint,1,opt,name=type,proto3,enum=tendermint.types.SignedMsgType" json:"type,omitempty"` + Height int64 `protobuf:"fixed64,2,opt,name=height,proto3" json:"height,omitempty"` + Round int64 `protobuf:"fixed64,3,opt,name=round,proto3" json:"round,omitempty"` + BlockID *CanonicalBlockID `protobuf:"bytes,4,opt,name=block_id,json=blockId,proto3" json:"block_id,omitempty"` + Timestamp time.Time `protobuf:"bytes,5,opt,name=timestamp,proto3,stdtime" json:"timestamp"` + ChainID string `protobuf:"bytes,6,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` } func (m *CanonicalVote) Reset() { *m = CanonicalVote{} } @@ -309,57 +308,121 @@ func (m *CanonicalVote) GetChainID() string { return "" } -func (m *CanonicalVote) GetVoteExtension() *VoteExtensionToSign { +// CanonicalVoteExtension provides us a way to serialize a vote extension from +// a particular validator such that we can sign over those serialized bytes. +type CanonicalVoteExtension struct { + Extension []byte `protobuf:"bytes,1,opt,name=extension,proto3" json:"extension,omitempty"` + Height int64 `protobuf:"fixed64,2,opt,name=height,proto3" json:"height,omitempty"` + Round int64 `protobuf:"fixed64,3,opt,name=round,proto3" json:"round,omitempty"` + ChainId string `protobuf:"bytes,4,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` +} + +func (m *CanonicalVoteExtension) Reset() { *m = CanonicalVoteExtension{} } +func (m *CanonicalVoteExtension) String() string { return proto.CompactTextString(m) } +func (*CanonicalVoteExtension) ProtoMessage() {} +func (*CanonicalVoteExtension) Descriptor() ([]byte, []int) { + return fileDescriptor_8d1a1a84ff7267ed, []int{4} +} +func (m *CanonicalVoteExtension) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *CanonicalVoteExtension) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_CanonicalVoteExtension.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *CanonicalVoteExtension) XXX_Merge(src proto.Message) { + xxx_messageInfo_CanonicalVoteExtension.Merge(m, src) +} +func (m *CanonicalVoteExtension) XXX_Size() int { + return m.Size() +} +func (m *CanonicalVoteExtension) XXX_DiscardUnknown() { + xxx_messageInfo_CanonicalVoteExtension.DiscardUnknown(m) +} + +var xxx_messageInfo_CanonicalVoteExtension proto.InternalMessageInfo + +func (m *CanonicalVoteExtension) GetExtension() []byte { if m != nil { - return m.VoteExtension + return m.Extension } return nil } +func (m *CanonicalVoteExtension) GetHeight() int64 { + if m != nil { + return m.Height + } + return 0 +} + +func (m *CanonicalVoteExtension) GetRound() int64 { + if m != nil { + return m.Round + } + return 0 +} + +func (m *CanonicalVoteExtension) GetChainId() string { + if m != nil { + return m.ChainId + } + return "" +} + func init() { proto.RegisterType((*CanonicalBlockID)(nil), "tendermint.types.CanonicalBlockID") proto.RegisterType((*CanonicalPartSetHeader)(nil), "tendermint.types.CanonicalPartSetHeader") proto.RegisterType((*CanonicalProposal)(nil), "tendermint.types.CanonicalProposal") proto.RegisterType((*CanonicalVote)(nil), "tendermint.types.CanonicalVote") + proto.RegisterType((*CanonicalVoteExtension)(nil), "tendermint.types.CanonicalVoteExtension") } func init() { proto.RegisterFile("tendermint/types/canonical.proto", fileDescriptor_8d1a1a84ff7267ed) } var fileDescriptor_8d1a1a84ff7267ed = []byte{ - // 522 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x94, 0x3f, 0x6f, 0xd3, 0x40, - 0x18, 0xc6, 0xe3, 0xd4, 0x49, 0x9c, 0x4b, 0x53, 0xc2, 0xa9, 0xaa, 0xac, 0x08, 0xd9, 0x96, 0x25, - 0x90, 0x59, 0x6c, 0x29, 0x1d, 0xd8, 0x5d, 0x90, 0x08, 0x2a, 0xa2, 0x5c, 0xa3, 0x0e, 0x2c, 0xd6, - 0xc5, 0x3e, 0x6c, 0x0b, 0xc7, 0x67, 0xd9, 0x97, 0x8a, 0x2e, 0x7c, 0x86, 0x7e, 0xac, 0x8e, 0x1d, - 0x61, 0x09, 0xc8, 0xf9, 0x12, 0x8c, 0xe8, 0xce, 0x49, 0x1c, 0x25, 0xc0, 0x02, 0xea, 0x12, 0xbd, - 0x7f, 0x1e, 0xbf, 0xef, 0xa3, 0xdf, 0xab, 0x1c, 0x30, 0x18, 0x49, 0x03, 0x92, 0xcf, 0xe2, 0x94, - 0x39, 0xec, 0x26, 0x23, 0x85, 0xe3, 0xe3, 0x94, 0xa6, 0xb1, 0x8f, 0x13, 0x3b, 0xcb, 0x29, 0xa3, - 0x70, 0x50, 0x2b, 0x6c, 0xa1, 0x18, 0x1e, 0x87, 0x34, 0xa4, 0xa2, 0xe9, 0xf0, 0xa8, 0xd2, 0x0d, - 0x9f, 0xec, 0x4d, 0x12, 0xbf, 0xab, 0xae, 0x1e, 0x52, 0x1a, 0x26, 0xc4, 0x11, 0xd9, 0x74, 0xfe, - 0xd1, 0x61, 0xf1, 0x8c, 0x14, 0x0c, 0xcf, 0xb2, 0x4a, 0x60, 0x7e, 0x01, 0x83, 0xb3, 0xf5, 0x66, - 0x37, 0xa1, 0xfe, 0xa7, 0xf1, 0x4b, 0x08, 0x81, 0x1c, 0xe1, 0x22, 0x52, 0x25, 0x43, 0xb2, 0x0e, - 0x91, 0x88, 0xe1, 0x15, 0x78, 0x94, 0xe1, 0x9c, 0x79, 0x05, 0x61, 0x5e, 0x44, 0x70, 0x40, 0x72, - 0xb5, 0x69, 0x48, 0x56, 0x6f, 0x64, 0xd9, 0xbb, 0x46, 0xed, 0xcd, 0xc0, 0x0b, 0x9c, 0xb3, 0x4b, - 0xc2, 0x5e, 0x0b, 0xbd, 0x2b, 0xdf, 0x2d, 0xf4, 0x06, 0xea, 0x67, 0xdb, 0x45, 0xd3, 0x05, 0x27, - 0xbf, 0x97, 0xc3, 0x63, 0xd0, 0x62, 0x94, 0xe1, 0x44, 0xd8, 0xe8, 0xa3, 0x2a, 0xd9, 0x78, 0x6b, - 0xd6, 0xde, 0xcc, 0x6f, 0x4d, 0xf0, 0xb8, 0x1e, 0x92, 0xd3, 0x8c, 0x16, 0x38, 0x81, 0xa7, 0x40, - 0xe6, 0x76, 0xc4, 0xe7, 0x47, 0x23, 0x7d, 0xdf, 0xe6, 0x65, 0x1c, 0xa6, 0x24, 0x78, 0x5b, 0x84, - 0x93, 0x9b, 0x8c, 0x20, 0x21, 0x86, 0x27, 0xa0, 0x1d, 0x91, 0x38, 0x8c, 0x98, 0x58, 0x30, 0x40, - 0xab, 0x8c, 0x9b, 0xc9, 0xe9, 0x3c, 0x0d, 0xd4, 0x03, 0x51, 0xae, 0x12, 0xf8, 0x1c, 0x74, 0x33, - 0x9a, 0x78, 0x55, 0x47, 0x36, 0x24, 0xeb, 0xc0, 0x3d, 0x2c, 0x17, 0xba, 0x72, 0xf1, 0xee, 0x1c, - 0xf1, 0x1a, 0x52, 0x32, 0x9a, 0x88, 0x08, 0xbe, 0x01, 0xca, 0x94, 0xe3, 0xf5, 0xe2, 0x40, 0x6d, - 0x09, 0x70, 0xe6, 0x5f, 0xc0, 0xad, 0x2e, 0xe1, 0xf6, 0xca, 0x85, 0xde, 0x59, 0x25, 0xa8, 0x23, - 0x06, 0x8c, 0x03, 0xe8, 0x82, 0xee, 0xe6, 0x8c, 0x6a, 0x5b, 0x0c, 0x1b, 0xda, 0xd5, 0xa1, 0xed, - 0xf5, 0xa1, 0xed, 0xc9, 0x5a, 0xe1, 0x2a, 0x9c, 0xfb, 0xed, 0x77, 0x5d, 0x42, 0xf5, 0x67, 0xf0, - 0x19, 0x50, 0xfc, 0x08, 0xc7, 0x29, 0xf7, 0xd3, 0x31, 0x24, 0xab, 0x5b, 0xed, 0x3a, 0xe3, 0x35, - 0xbe, 0x4b, 0x34, 0xc7, 0x81, 0xf9, 0xb3, 0x09, 0xfa, 0x1b, 0x5b, 0x57, 0x94, 0x91, 0x87, 0xe0, - 0xba, 0x0d, 0x4b, 0xfe, 0x9f, 0xb0, 0x5a, 0xff, 0x0e, 0xab, 0xfd, 0x67, 0x58, 0xf0, 0x1c, 0x1c, - 0x5d, 0x53, 0x46, 0x3c, 0xf2, 0x99, 0x91, 0xb4, 0x88, 0x69, 0x2a, 0xd0, 0xf6, 0x46, 0x4f, 0xf7, - 0xdd, 0x73, 0x94, 0xaf, 0xd6, 0xb2, 0x09, 0xe5, 0xcc, 0x50, 0xff, 0x7a, 0xbb, 0xe8, 0xbe, 0xbf, - 0x2b, 0x35, 0xe9, 0xbe, 0xd4, 0xa4, 0x1f, 0xa5, 0x26, 0xdd, 0x2e, 0xb5, 0xc6, 0xfd, 0x52, 0x6b, - 0x7c, 0x5d, 0x6a, 0x8d, 0x0f, 0x2f, 0xc2, 0x98, 0x45, 0xf3, 0xa9, 0xed, 0xd3, 0x99, 0xb3, 0xfd, - 0xf7, 0xaf, 0xc3, 0xea, 0x99, 0xd8, 0x7d, 0x1a, 0xa6, 0x6d, 0x51, 0x3f, 0xfd, 0x15, 0x00, 0x00, - 0xff, 0xff, 0x4e, 0x61, 0xcb, 0xc4, 0x7f, 0x04, 0x00, 0x00, + // 523 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x54, 0xc1, 0x6e, 0xd3, 0x40, + 0x10, 0x8d, 0x53, 0x27, 0xb1, 0xb7, 0x0d, 0x84, 0x55, 0x55, 0x99, 0xa8, 0xb2, 0x2d, 0x1f, 0x90, + 0xb9, 0xd8, 0x52, 0x7b, 0xe0, 0xee, 0x82, 0x44, 0x10, 0x88, 0xe2, 0x56, 0x3d, 0x70, 0x89, 0x36, + 0xf6, 0x62, 0x5b, 0x38, 0xde, 0x95, 0xbd, 0x91, 0xe8, 0x05, 0x7e, 0xa1, 0xdf, 0xc1, 0x97, 0xf4, + 0xd8, 0x23, 0x5c, 0x02, 0x72, 0x7e, 0x04, 0xed, 0xda, 0xb1, 0xad, 0x16, 0x55, 0x42, 0x20, 0x2e, + 0xd1, 0xcc, 0x9b, 0xb7, 0x33, 0x4f, 0x6f, 0xe2, 0x01, 0x26, 0xc3, 0x59, 0x88, 0xf3, 0x65, 0x92, + 0x31, 0x97, 0x5d, 0x52, 0x5c, 0xb8, 0x01, 0xca, 0x48, 0x96, 0x04, 0x28, 0x75, 0x68, 0x4e, 0x18, + 0x81, 0x93, 0x96, 0xe1, 0x08, 0xc6, 0x74, 0x3f, 0x22, 0x11, 0x11, 0x45, 0x97, 0x47, 0x15, 0x6f, + 0x7a, 0x78, 0xa7, 0x93, 0xf8, 0xad, 0xab, 0x46, 0x44, 0x48, 0x94, 0x62, 0x57, 0x64, 0x8b, 0xd5, + 0x07, 0x97, 0x25, 0x4b, 0x5c, 0x30, 0xb4, 0xa4, 0x15, 0xc1, 0xfa, 0x0c, 0x26, 0x27, 0xdb, 0xc9, + 0x5e, 0x4a, 0x82, 0x8f, 0xb3, 0xe7, 0x10, 0x02, 0x39, 0x46, 0x45, 0xac, 0x49, 0xa6, 0x64, 0xef, + 0xf9, 0x22, 0x86, 0x17, 0xe0, 0x21, 0x45, 0x39, 0x9b, 0x17, 0x98, 0xcd, 0x63, 0x8c, 0x42, 0x9c, + 0x6b, 0x7d, 0x53, 0xb2, 0x77, 0x8f, 0x6c, 0xe7, 0xb6, 0x50, 0xa7, 0x69, 0x78, 0x8a, 0x72, 0x76, + 0x86, 0xd9, 0x4b, 0xc1, 0xf7, 0xe4, 0xeb, 0xb5, 0xd1, 0xf3, 0xc7, 0xb4, 0x0b, 0x5a, 0x1e, 0x38, + 0xf8, 0x3d, 0x1d, 0xee, 0x83, 0x01, 0x23, 0x0c, 0xa5, 0x42, 0xc6, 0xd8, 0xaf, 0x92, 0x46, 0x5b, + 0xbf, 0xd5, 0x66, 0x7d, 0xef, 0x83, 0x47, 0x6d, 0x93, 0x9c, 0x50, 0x52, 0xa0, 0x14, 0x1e, 0x03, + 0x99, 0xcb, 0x11, 0xcf, 0x1f, 0x1c, 0x19, 0x77, 0x65, 0x9e, 0x25, 0x51, 0x86, 0xc3, 0x37, 0x45, + 0x74, 0x7e, 0x49, 0xb1, 0x2f, 0xc8, 0xf0, 0x00, 0x0c, 0x63, 0x9c, 0x44, 0x31, 0x13, 0x03, 0x26, + 0x7e, 0x9d, 0x71, 0x31, 0x39, 0x59, 0x65, 0xa1, 0xb6, 0x23, 0xe0, 0x2a, 0x81, 0x4f, 0x81, 0x4a, + 0x49, 0x3a, 0xaf, 0x2a, 0xb2, 0x29, 0xd9, 0x3b, 0xde, 0x5e, 0xb9, 0x36, 0x94, 0xd3, 0xb7, 0xaf, + 0x7d, 0x8e, 0xf9, 0x0a, 0x25, 0xa9, 0x88, 0xe0, 0x2b, 0xa0, 0x2c, 0xb8, 0xbd, 0xf3, 0x24, 0xd4, + 0x06, 0xc2, 0x38, 0xeb, 0x1e, 0xe3, 0xea, 0x4d, 0x78, 0xbb, 0xe5, 0xda, 0x18, 0xd5, 0x89, 0x3f, + 0x12, 0x0d, 0x66, 0x21, 0xf4, 0x80, 0xda, 0xac, 0x51, 0x1b, 0x8a, 0x66, 0x53, 0xa7, 0x5a, 0xb4, + 0xb3, 0x5d, 0xb4, 0x73, 0xbe, 0x65, 0x78, 0x0a, 0xf7, 0xfd, 0xea, 0x87, 0x21, 0xf9, 0xed, 0x33, + 0xf8, 0x04, 0x28, 0x41, 0x8c, 0x92, 0x8c, 0xeb, 0x19, 0x99, 0x92, 0xad, 0x56, 0xb3, 0x4e, 0x38, + 0xc6, 0x67, 0x89, 0xe2, 0x2c, 0xb4, 0xbe, 0xf6, 0xc1, 0xb8, 0x91, 0x75, 0x41, 0x18, 0xfe, 0x1f, + 0xbe, 0x76, 0xcd, 0x92, 0xff, 0xa5, 0x59, 0x83, 0xbf, 0x37, 0x6b, 0x78, 0x8f, 0x59, 0x5f, 0x3a, + 0x7f, 0x66, 0xee, 0xd5, 0x8b, 0x4f, 0x0c, 0x67, 0x45, 0x42, 0x32, 0x78, 0x08, 0x54, 0xbc, 0x4d, + 0xea, 0xef, 0xaa, 0x05, 0xfe, 0xd0, 0x9d, 0xc7, 0x1d, 0x35, 0xdc, 0x1d, 0xb5, 0x11, 0xe0, 0xbd, + 0xbb, 0x2e, 0x75, 0xe9, 0xa6, 0xd4, 0xa5, 0x9f, 0xa5, 0x2e, 0x5d, 0x6d, 0xf4, 0xde, 0xcd, 0x46, + 0xef, 0x7d, 0xdb, 0xe8, 0xbd, 0xf7, 0xcf, 0xa2, 0x84, 0xc5, 0xab, 0x85, 0x13, 0x90, 0xa5, 0xdb, + 0xbd, 0x18, 0x6d, 0x58, 0x5d, 0x96, 0xdb, 0xd7, 0x64, 0x31, 0x14, 0xf8, 0xf1, 0xaf, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x2b, 0x89, 0x89, 0x5b, 0xb2, 0x04, 0x00, 0x00, } func (m *CanonicalBlockID) Marshal() (dAtA []byte, err error) { @@ -529,18 +592,6 @@ func (m *CanonicalVote) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.VoteExtension != nil { - { - size, err := m.VoteExtension.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintCanonical(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x3a - } if len(m.ChainID) > 0 { i -= len(m.ChainID) copy(dAtA[i:], m.ChainID) @@ -548,12 +599,12 @@ func (m *CanonicalVote) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x32 } - n5, err5 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) - if err5 != nil { - return 0, err5 + n4, err4 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) + if err4 != nil { + return 0, err4 } - i -= n5 - i = encodeVarintCanonical(dAtA, i, uint64(n5)) + i -= n4 + i = encodeVarintCanonical(dAtA, i, uint64(n4)) i-- dAtA[i] = 0x2a if m.BlockID != nil { @@ -588,6 +639,55 @@ func (m *CanonicalVote) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *CanonicalVoteExtension) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CanonicalVoteExtension) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *CanonicalVoteExtension) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ChainId) > 0 { + i -= len(m.ChainId) + copy(dAtA[i:], m.ChainId) + i = encodeVarintCanonical(dAtA, i, uint64(len(m.ChainId))) + i-- + dAtA[i] = 0x22 + } + if m.Round != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.Round)) + i-- + dAtA[i] = 0x19 + } + if m.Height != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.Height)) + i-- + dAtA[i] = 0x11 + } + if len(m.Extension) > 0 { + i -= len(m.Extension) + copy(dAtA[i:], m.Extension) + i = encodeVarintCanonical(dAtA, i, uint64(len(m.Extension))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintCanonical(dAtA []byte, offset int, v uint64) int { offset -= sovCanonical(v) base := offset @@ -686,8 +786,27 @@ func (m *CanonicalVote) Size() (n int) { if l > 0 { n += 1 + l + sovCanonical(uint64(l)) } - if m.VoteExtension != nil { - l = m.VoteExtension.Size() + return n +} + +func (m *CanonicalVoteExtension) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Extension) + if l > 0 { + n += 1 + l + sovCanonical(uint64(l)) + } + if m.Height != 0 { + n += 9 + } + if m.Round != 0 { + n += 9 + } + l = len(m.ChainId) + if l > 0 { n += 1 + l + sovCanonical(uint64(l)) } return n @@ -1297,11 +1416,61 @@ func (m *CanonicalVote) Unmarshal(dAtA []byte) error { } m.ChainID = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 7: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field VoteExtension", wireType) + default: + iNdEx = preIndex + skippy, err := skipCanonical(dAtA[iNdEx:]) + if err != nil { + return err } - var msglen int + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthCanonical + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CanonicalVoteExtension) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCanonical + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CanonicalVoteExtension: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CanonicalVoteExtension: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Extension", wireType) + } + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowCanonical @@ -1311,27 +1480,77 @@ func (m *CanonicalVote) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthCanonical } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthCanonical } if postIndex > l { return io.ErrUnexpectedEOF } - if m.VoteExtension == nil { - m.VoteExtension = &VoteExtensionToSign{} + m.Extension = append(m.Extension[:0], dAtA[iNdEx:postIndex]...) + if m.Extension == nil { + m.Extension = []byte{} } - if err := m.VoteExtension.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err + iNdEx = postIndex + case 2: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) } + m.Height = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.Height = int64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 3: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Round", wireType) + } + m.Round = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.Round = int64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChainId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCanonical + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCanonical + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCanonical + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChainId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex diff --git a/proto/tendermint/types/canonical.proto b/proto/tendermint/types/canonical.proto index 58d8c44e9..e9ed1e55d 100644 --- a/proto/tendermint/types/canonical.proto +++ b/proto/tendermint/types/canonical.proto @@ -1,6 +1,8 @@ syntax = "proto3"; package tendermint.types; +option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; + import "gogoproto/gogo.proto"; import "tendermint/types/types.proto"; import "google/protobuf/timestamp.proto"; @@ -33,3 +35,12 @@ message CanonicalVote { google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; string chain_id = 6 [(gogoproto.customname) = "ChainID"]; } + +// CanonicalVoteExtension provides us a way to serialize a vote extension from +// a particular validator such that we can sign over those serialized bytes. +message CanonicalVoteExtension { + bytes extension = 1; + sfixed64 height = 2; + sfixed64 round = 3; + string chain_id = 4; +} diff --git a/proto/tendermint/types/events.proto b/proto/tendermint/types/events.proto index 1ef715872..a1e5cc498 100644 --- a/proto/tendermint/types/events.proto +++ b/proto/tendermint/types/events.proto @@ -1,6 +1,8 @@ syntax = "proto3"; package tendermint.types; +option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; + message EventDataRoundState { int64 height = 1; int32 round = 2; diff --git a/proto/tendermint/types/evidence.proto b/proto/tendermint/types/evidence.proto index d42c84363..44ef70cf6 100644 --- a/proto/tendermint/types/evidence.proto +++ b/proto/tendermint/types/evidence.proto @@ -1,6 +1,8 @@ syntax = "proto3"; package tendermint.types; +option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; + import "gogoproto/gogo.proto"; import "google/protobuf/timestamp.proto"; import "tendermint/types/types.proto"; diff --git a/proto/tendermint/types/params.pb.go b/proto/tendermint/types/params.pb.go index 32689932c..41d417b91 100644 --- a/proto/tendermint/types/params.pb.go +++ b/proto/tendermint/types/params.pb.go @@ -35,6 +35,7 @@ type ConsensusParams struct { Validator *ValidatorParams `protobuf:"bytes,3,opt,name=validator,proto3" json:"validator,omitempty"` Version *VersionParams `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"` Synchrony *SynchronyParams `protobuf:"bytes,5,opt,name=synchrony,proto3" json:"synchrony,omitempty"` + Timeout *TimeoutParams `protobuf:"bytes,6,opt,name=timeout,proto3" json:"timeout,omitempty"` } func (m *ConsensusParams) Reset() { *m = ConsensusParams{} } @@ -105,6 +106,13 @@ func (m *ConsensusParams) GetSynchrony() *SynchronyParams { return nil } +func (m *ConsensusParams) GetTimeout() *TimeoutParams { + if m != nil { + return m.Timeout + } + return nil +} + // BlockParams contains limits on the block size. type BlockParams struct { // Max block size, in bytes. @@ -381,9 +389,17 @@ func (m *HashedParams) GetBlockMaxGas() int64 { return 0 } +// SynchronyParams configure the bounds under which a proposed block's timestamp is considered valid. +// These parameters are part of the proposer-based timestamps algorithm. For more information, +// see the specification of proposer-based timestamps: +// https://github.com/tendermint/tendermint/tree/master/spec/consensus/proposer-based-timestamp type SynchronyParams struct { - MessageDelay time.Duration `protobuf:"bytes,1,opt,name=message_delay,json=messageDelay,proto3,stdduration" json:"message_delay"` - Precision time.Duration `protobuf:"bytes,2,opt,name=precision,proto3,stdduration" json:"precision"` + // message_delay bounds how long a proposal message may take to reach all validators on a network + // and still be considered valid. + MessageDelay *time.Duration `protobuf:"bytes,1,opt,name=message_delay,json=messageDelay,proto3,stdduration" json:"message_delay,omitempty"` + // precision bounds how skewed a proposer's clock may be from any validator + // on the network while still producing valid proposals. + Precision *time.Duration `protobuf:"bytes,2,opt,name=precision,proto3,stdduration" json:"precision,omitempty"` } func (m *SynchronyParams) Reset() { *m = SynchronyParams{} } @@ -419,18 +435,135 @@ func (m *SynchronyParams) XXX_DiscardUnknown() { var xxx_messageInfo_SynchronyParams proto.InternalMessageInfo -func (m *SynchronyParams) GetMessageDelay() time.Duration { +func (m *SynchronyParams) GetMessageDelay() *time.Duration { if m != nil { return m.MessageDelay } - return 0 + return nil } -func (m *SynchronyParams) GetPrecision() time.Duration { +func (m *SynchronyParams) GetPrecision() *time.Duration { if m != nil { return m.Precision } - return 0 + return nil +} + +// TimeoutParams configure the timeouts for the steps of the Tendermint consensus algorithm. +type TimeoutParams struct { + // These fields configure the timeouts for the propose step of the Tendermint + // consensus algorithm: propose is the initial timeout and propose_delta + // determines how much the timeout grows in subsequent rounds. + // For the first round, this propose timeout is used and for every subsequent + // round, the timeout grows by propose_delta. + // + // For example: + // With propose = 10ms, propose_delta = 5ms, the first round's propose phase + // timeout would be 10ms, the second round's would be 15ms, the third 20ms and so on. + // + // If a node waiting for a proposal message does not receive one matching its + // current height and round before this timeout, the node will issue a + // nil prevote for the round and advance to the next step. + Propose *time.Duration `protobuf:"bytes,1,opt,name=propose,proto3,stdduration" json:"propose,omitempty"` + ProposeDelta *time.Duration `protobuf:"bytes,2,opt,name=propose_delta,json=proposeDelta,proto3,stdduration" json:"propose_delta,omitempty"` + // vote along with vote_delta configure the timeout for both of the prevote and + // precommit steps of the Tendermint consensus algorithm. + // + // These parameters influence the vote step timeouts in the the same way that + // the propose and propose_delta parameters do to the proposal step. + // + // The vote timeout does not begin until a quorum of votes has been received. Once + // a quorum of votes has been seen and this timeout elapses, Tendermint will + // procced to the next step of the consensus algorithm. If Tendermint receives + // all of the remaining votes before the end of the timeout, it will proceed + // to the next step immediately. + Vote *time.Duration `protobuf:"bytes,3,opt,name=vote,proto3,stdduration" json:"vote,omitempty"` + VoteDelta *time.Duration `protobuf:"bytes,4,opt,name=vote_delta,json=voteDelta,proto3,stdduration" json:"vote_delta,omitempty"` + // commit configures how long Tendermint will wait after receiving a quorum of + // precommits before beginning consensus for the next height. This can be + // used to allow slow precommits to arrive for inclusion in the next height before progressing. + Commit *time.Duration `protobuf:"bytes,5,opt,name=commit,proto3,stdduration" json:"commit,omitempty"` + // bypass_commit_timeout configures the node to proceed immediately to + // the next height once the node has received all precommits for a block, forgoing + // the remaining commit timeout. + // Setting bypass_commit_timeout false (the default) causes Tendermint to wait + // for the full commit timeout. + BypassCommitTimeout bool `protobuf:"varint,6,opt,name=bypass_commit_timeout,json=bypassCommitTimeout,proto3" json:"bypass_commit_timeout,omitempty"` +} + +func (m *TimeoutParams) Reset() { *m = TimeoutParams{} } +func (m *TimeoutParams) String() string { return proto.CompactTextString(m) } +func (*TimeoutParams) ProtoMessage() {} +func (*TimeoutParams) Descriptor() ([]byte, []int) { + return fileDescriptor_e12598271a686f57, []int{7} +} +func (m *TimeoutParams) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TimeoutParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TimeoutParams.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TimeoutParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_TimeoutParams.Merge(m, src) +} +func (m *TimeoutParams) XXX_Size() int { + return m.Size() +} +func (m *TimeoutParams) XXX_DiscardUnknown() { + xxx_messageInfo_TimeoutParams.DiscardUnknown(m) +} + +var xxx_messageInfo_TimeoutParams proto.InternalMessageInfo + +func (m *TimeoutParams) GetPropose() *time.Duration { + if m != nil { + return m.Propose + } + return nil +} + +func (m *TimeoutParams) GetProposeDelta() *time.Duration { + if m != nil { + return m.ProposeDelta + } + return nil +} + +func (m *TimeoutParams) GetVote() *time.Duration { + if m != nil { + return m.Vote + } + return nil +} + +func (m *TimeoutParams) GetVoteDelta() *time.Duration { + if m != nil { + return m.VoteDelta + } + return nil +} + +func (m *TimeoutParams) GetCommit() *time.Duration { + if m != nil { + return m.Commit + } + return nil +} + +func (m *TimeoutParams) GetBypassCommitTimeout() bool { + if m != nil { + return m.BypassCommitTimeout + } + return false } func init() { @@ -441,48 +574,56 @@ func init() { proto.RegisterType((*VersionParams)(nil), "tendermint.types.VersionParams") proto.RegisterType((*HashedParams)(nil), "tendermint.types.HashedParams") proto.RegisterType((*SynchronyParams)(nil), "tendermint.types.SynchronyParams") + proto.RegisterType((*TimeoutParams)(nil), "tendermint.types.TimeoutParams") } func init() { proto.RegisterFile("tendermint/types/params.proto", fileDescriptor_e12598271a686f57) } var fileDescriptor_e12598271a686f57 = []byte{ - // 561 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x94, 0x4d, 0x6b, 0xd4, 0x40, - 0x18, 0xc7, 0x37, 0xdd, 0xbe, 0xec, 0x3e, 0xdb, 0xed, 0x96, 0x41, 0x30, 0x56, 0x9a, 0x5d, 0x73, - 0x90, 0x82, 0x90, 0x88, 0x45, 0x44, 0x10, 0xa4, 0xdb, 0x8a, 0x05, 0xa9, 0x48, 0x7c, 0x39, 0xf4, - 0x12, 0x26, 0xbb, 0x63, 0x36, 0x74, 0x93, 0x19, 0x32, 0xc9, 0xb2, 0xf9, 0x16, 0x1e, 0x3d, 0x79, - 0xd6, 0x8f, 0xe1, 0xad, 0xc7, 0x1e, 0x3d, 0xa9, 0xec, 0x7e, 0x11, 0x99, 0xc9, 0x4c, 0xd3, 0xdd, - 0x2a, 0xd8, 0x5b, 0x32, 0xcf, 0xef, 0x97, 0x87, 0xf9, 0x3f, 0x93, 0x81, 0xdd, 0x8c, 0x24, 0x43, - 0x92, 0xc6, 0x51, 0x92, 0xb9, 0x59, 0xc1, 0x08, 0x77, 0x19, 0x4e, 0x71, 0xcc, 0x1d, 0x96, 0xd2, - 0x8c, 0xa2, 0xed, 0xaa, 0xec, 0xc8, 0xf2, 0xce, 0xad, 0x90, 0x86, 0x54, 0x16, 0x5d, 0xf1, 0x54, - 0x72, 0x3b, 0x56, 0x48, 0x69, 0x38, 0x26, 0xae, 0x7c, 0x0b, 0xf2, 0x8f, 0xee, 0x30, 0x4f, 0x71, - 0x16, 0xd1, 0xa4, 0xac, 0xdb, 0xdf, 0x57, 0xa0, 0x73, 0x48, 0x13, 0x4e, 0x12, 0x9e, 0xf3, 0x37, - 0xb2, 0x03, 0xda, 0x87, 0xb5, 0x60, 0x4c, 0x07, 0x67, 0xa6, 0xd1, 0x33, 0xf6, 0x5a, 0x8f, 0x76, - 0x9d, 0xe5, 0x5e, 0x4e, 0x5f, 0x94, 0x4b, 0xda, 0x2b, 0x59, 0xf4, 0x0c, 0x1a, 0x64, 0x12, 0x0d, - 0x49, 0x32, 0x20, 0xe6, 0x8a, 0xf4, 0x7a, 0xd7, 0xbd, 0x17, 0x8a, 0x50, 0xea, 0xa5, 0x81, 0x9e, - 0x43, 0x73, 0x82, 0xc7, 0xd1, 0x10, 0x67, 0x34, 0x35, 0xeb, 0x52, 0xbf, 0x77, 0x5d, 0xff, 0xa0, - 0x11, 0xe5, 0x57, 0x0e, 0x7a, 0x0a, 0x1b, 0x13, 0x92, 0xf2, 0x88, 0x26, 0xe6, 0xaa, 0xd4, 0xbb, - 0x7f, 0xd1, 0x4b, 0x40, 0xc9, 0x9a, 0x17, 0xbd, 0x79, 0x91, 0x0c, 0x46, 0x29, 0x4d, 0x0a, 0x73, - 0xed, 0x5f, 0xbd, 0xdf, 0x6a, 0x44, 0xf7, 0xbe, 0x74, 0xec, 0x43, 0x68, 0x5d, 0x09, 0x04, 0xdd, - 0x85, 0x66, 0x8c, 0xa7, 0x7e, 0x50, 0x64, 0x84, 0xcb, 0x08, 0xeb, 0x5e, 0x23, 0xc6, 0xd3, 0xbe, - 0x78, 0x47, 0xb7, 0x61, 0x43, 0x14, 0x43, 0xcc, 0x65, 0x4a, 0x75, 0x6f, 0x3d, 0xc6, 0xd3, 0x97, - 0x98, 0xdb, 0xdf, 0x0c, 0xd8, 0x5a, 0x8c, 0x07, 0x3d, 0x00, 0x24, 0x58, 0x1c, 0x12, 0x3f, 0xc9, - 0x63, 0x5f, 0xe6, 0xac, 0xbf, 0xd8, 0x89, 0xf1, 0xf4, 0x20, 0x24, 0xaf, 0xf3, 0x58, 0xb6, 0xe6, - 0xe8, 0x04, 0xb6, 0x35, 0xac, 0x47, 0xac, 0xe6, 0x70, 0xc7, 0x29, 0xcf, 0x80, 0xa3, 0xcf, 0x80, - 0x73, 0xa4, 0x80, 0x7e, 0xe3, 0xfc, 0x67, 0xb7, 0xf6, 0xf9, 0x57, 0xd7, 0xf0, 0xb6, 0xca, 0xef, - 0xe9, 0xca, 0xe2, 0x26, 0xea, 0x8b, 0x9b, 0xb0, 0x1f, 0x43, 0x67, 0x69, 0x14, 0xc8, 0x86, 0x36, - 0xcb, 0x03, 0xff, 0x8c, 0x14, 0xbe, 0xcc, 0xcb, 0x34, 0x7a, 0xf5, 0xbd, 0xa6, 0xd7, 0x62, 0x79, - 0xf0, 0x8a, 0x14, 0xef, 0xc4, 0x92, 0xfd, 0x10, 0xda, 0x0b, 0x23, 0x40, 0x5d, 0x68, 0x61, 0xc6, - 0x7c, 0x3d, 0x38, 0xb1, 0xb3, 0x55, 0x0f, 0x30, 0x63, 0x0a, 0xb3, 0x4f, 0x61, 0xf3, 0x18, 0xf3, - 0x11, 0x19, 0x2a, 0xe1, 0x3e, 0x74, 0x64, 0x0a, 0xfe, 0x72, 0xc0, 0x6d, 0xb9, 0x7c, 0xa2, 0x53, - 0xb6, 0xa1, 0x5d, 0x71, 0x55, 0xd6, 0x2d, 0x4d, 0x89, 0xc0, 0xbf, 0x18, 0xd0, 0x59, 0x1a, 0x2a, - 0x3a, 0x86, 0x76, 0x4c, 0x38, 0x97, 0x21, 0x92, 0x31, 0x2e, 0xd4, 0x1f, 0xf0, 0x5f, 0x09, 0x6e, - 0x2a, 0xf3, 0x48, 0x88, 0xe8, 0x00, 0x9a, 0x2c, 0x25, 0x83, 0x88, 0xdf, 0x70, 0x0e, 0x95, 0xd5, - 0x7f, 0xff, 0x75, 0x66, 0x19, 0xe7, 0x33, 0xcb, 0xb8, 0x98, 0x59, 0xc6, 0xef, 0x99, 0x65, 0x7c, - 0x9a, 0x5b, 0xb5, 0x8b, 0xb9, 0x55, 0xfb, 0x31, 0xb7, 0x6a, 0xa7, 0x4f, 0xc2, 0x28, 0x1b, 0xe5, - 0x81, 0x33, 0xa0, 0xb1, 0x7b, 0xf5, 0xaa, 0xa8, 0x1e, 0xcb, 0xbb, 0x60, 0xf9, 0x1a, 0x09, 0xd6, - 0xe5, 0xfa, 0xfe, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xcb, 0x26, 0x8a, 0x0b, 0x61, 0x04, 0x00, - 0x00, + // 680 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x94, 0xcf, 0x6e, 0xd3, 0x4a, + 0x14, 0xc6, 0xe3, 0x26, 0x4d, 0x93, 0x93, 0xa6, 0xa9, 0xe6, 0xde, 0xab, 0xeb, 0xdb, 0xab, 0x3a, + 0xc5, 0x0b, 0x54, 0x09, 0xc9, 0x41, 0xad, 0x50, 0x85, 0xc4, 0x1f, 0x91, 0x06, 0x81, 0x84, 0x8a, + 0x90, 0x29, 0x2c, 0xba, 0xb1, 0xc6, 0xc9, 0xe0, 0x5a, 0x8d, 0x3d, 0x96, 0xc7, 0x8e, 0xe2, 0xb7, + 0x60, 0x85, 0x78, 0x04, 0x78, 0x93, 0x2e, 0xbb, 0x64, 0x05, 0x28, 0x7d, 0x03, 0xd6, 0x2c, 0xd0, + 0xfc, 0x6b, 0x9a, 0x94, 0xd2, 0xac, 0xe2, 0xcc, 0xf9, 0x7e, 0xfe, 0x3c, 0xdf, 0x39, 0x33, 0xb0, + 0x99, 0x91, 0x78, 0x40, 0xd2, 0x28, 0x8c, 0xb3, 0x4e, 0x56, 0x24, 0x84, 0x75, 0x12, 0x9c, 0xe2, + 0x88, 0x39, 0x49, 0x4a, 0x33, 0x8a, 0xd6, 0xa7, 0x65, 0x47, 0x94, 0x37, 0xfe, 0x0e, 0x68, 0x40, + 0x45, 0xb1, 0xc3, 0x9f, 0xa4, 0x6e, 0xc3, 0x0a, 0x28, 0x0d, 0x86, 0xa4, 0x23, 0xfe, 0xf9, 0xf9, + 0xbb, 0xce, 0x20, 0x4f, 0x71, 0x16, 0xd2, 0x58, 0xd6, 0xed, 0x9f, 0x4b, 0xd0, 0xda, 0xa7, 0x31, + 0x23, 0x31, 0xcb, 0xd9, 0x2b, 0xe1, 0x80, 0x76, 0x61, 0xd9, 0x1f, 0xd2, 0xfe, 0x89, 0x69, 0x6c, + 0x19, 0xdb, 0x8d, 0x9d, 0x4d, 0x67, 0xde, 0xcb, 0xe9, 0xf2, 0xb2, 0x54, 0xbb, 0x52, 0x8b, 0x1e, + 0x40, 0x8d, 0x8c, 0xc2, 0x01, 0x89, 0xfb, 0xc4, 0x5c, 0x12, 0xdc, 0xd6, 0x55, 0xee, 0xa9, 0x52, + 0x28, 0xf4, 0x82, 0x40, 0x8f, 0xa1, 0x3e, 0xc2, 0xc3, 0x70, 0x80, 0x33, 0x9a, 0x9a, 0x65, 0x81, + 0xdf, 0xba, 0x8a, 0xbf, 0xd5, 0x12, 0xc5, 0x4f, 0x19, 0x74, 0x1f, 0x56, 0x46, 0x24, 0x65, 0x21, + 0x8d, 0xcd, 0x8a, 0xc0, 0xdb, 0xbf, 0xc1, 0xa5, 0x40, 0xc1, 0x5a, 0xcf, 0xbd, 0x59, 0x11, 0xf7, + 0x8f, 0x53, 0x1a, 0x17, 0xe6, 0xf2, 0x75, 0xde, 0xaf, 0xb5, 0x44, 0x7b, 0x5f, 0x30, 0xdc, 0x3b, + 0x0b, 0x23, 0x42, 0xf3, 0xcc, 0xac, 0x5e, 0xe7, 0x7d, 0x28, 0x05, 0xda, 0x5b, 0xe9, 0xed, 0x7d, + 0x68, 0x5c, 0xca, 0x12, 0xfd, 0x0f, 0xf5, 0x08, 0x8f, 0x3d, 0xbf, 0xc8, 0x08, 0x13, 0xe9, 0x97, + 0xdd, 0x5a, 0x84, 0xc7, 0x5d, 0xfe, 0x1f, 0xfd, 0x0b, 0x2b, 0xbc, 0x18, 0x60, 0x26, 0x02, 0x2e, + 0xbb, 0xd5, 0x08, 0x8f, 0x9f, 0x61, 0x66, 0x7f, 0x36, 0x60, 0x6d, 0x36, 0x59, 0x74, 0x07, 0x10, + 0xd7, 0xe2, 0x80, 0x78, 0x71, 0x1e, 0x79, 0xa2, 0x45, 0xfa, 0x8d, 0xad, 0x08, 0x8f, 0x9f, 0x04, + 0xe4, 0x65, 0x1e, 0x09, 0x6b, 0x86, 0x0e, 0x60, 0x5d, 0x8b, 0xf5, 0x74, 0xa8, 0x16, 0xfe, 0xe7, + 0xc8, 0xf1, 0x71, 0xf4, 0xf8, 0x38, 0x3d, 0x25, 0xe8, 0xd6, 0x4e, 0xbf, 0xb6, 0x4b, 0x1f, 0xbf, + 0xb5, 0x0d, 0x77, 0x4d, 0xbe, 0x4f, 0x57, 0x66, 0x37, 0x51, 0x9e, 0xdd, 0x84, 0x7d, 0x0f, 0x5a, + 0x73, 0x5d, 0x44, 0x36, 0x34, 0x93, 0xdc, 0xf7, 0x4e, 0x48, 0xe1, 0x89, 0xac, 0x4c, 0x63, 0xab, + 0xbc, 0x5d, 0x77, 0x1b, 0x49, 0xee, 0xbf, 0x20, 0xc5, 0x21, 0x5f, 0xb2, 0xef, 0x42, 0x73, 0xa6, + 0x7b, 0xa8, 0x0d, 0x0d, 0x9c, 0x24, 0x9e, 0xee, 0x39, 0xdf, 0x59, 0xc5, 0x05, 0x9c, 0x24, 0x4a, + 0x66, 0x1f, 0xc1, 0xea, 0x73, 0xcc, 0x8e, 0xc9, 0x40, 0x01, 0xb7, 0xa1, 0x25, 0x52, 0xf0, 0xe6, + 0x03, 0x6e, 0x8a, 0xe5, 0x03, 0x9d, 0xb2, 0x0d, 0xcd, 0xa9, 0x6e, 0x9a, 0x75, 0x43, 0xab, 0x78, + 0xe0, 0x1f, 0x0c, 0x68, 0xcd, 0xcd, 0x03, 0xea, 0x41, 0x33, 0x22, 0x8c, 0x89, 0x10, 0xc9, 0x10, + 0x17, 0xea, 0xf0, 0xfc, 0x21, 0xc1, 0x8a, 0x48, 0x6f, 0x55, 0x51, 0x3d, 0x0e, 0xa1, 0x87, 0x50, + 0x4f, 0x52, 0xd2, 0x0f, 0xd9, 0x42, 0x3d, 0x90, 0x6f, 0x98, 0x12, 0xf6, 0x8f, 0x25, 0x68, 0xce, + 0x4c, 0x1a, 0x9f, 0xcd, 0x24, 0xa5, 0x09, 0x65, 0x64, 0xd1, 0x0f, 0xd2, 0x7a, 0xbe, 0x23, 0xf5, + 0xc8, 0x77, 0x94, 0xe1, 0x45, 0xbf, 0x67, 0x55, 0x51, 0x3d, 0x0e, 0xa1, 0x5d, 0xa8, 0x8c, 0x68, + 0x46, 0xd4, 0xa1, 0xbe, 0x11, 0x16, 0x62, 0xf4, 0x08, 0x80, 0xff, 0x2a, 0xdf, 0xca, 0x82, 0x39, + 0x70, 0x44, 0x9a, 0xee, 0x41, 0xb5, 0x4f, 0xa3, 0x28, 0xcc, 0xd4, 0x79, 0xbe, 0x91, 0x55, 0x72, + 0xb4, 0x03, 0xff, 0xf8, 0x45, 0x82, 0x19, 0xf3, 0xe4, 0x82, 0x77, 0xf9, 0x60, 0xd7, 0xdc, 0xbf, + 0x64, 0x71, 0x5f, 0xd4, 0x54, 0xd0, 0xdd, 0x37, 0x9f, 0x26, 0x96, 0x71, 0x3a, 0xb1, 0x8c, 0xb3, + 0x89, 0x65, 0x7c, 0x9f, 0x58, 0xc6, 0xfb, 0x73, 0xab, 0x74, 0x76, 0x6e, 0x95, 0xbe, 0x9c, 0x5b, + 0xa5, 0xa3, 0xbd, 0x20, 0xcc, 0x8e, 0x73, 0xdf, 0xe9, 0xd3, 0xa8, 0x73, 0xf9, 0x4a, 0x9f, 0x3e, + 0xca, 0x3b, 0x7b, 0xfe, 0xba, 0xf7, 0xab, 0x62, 0x7d, 0xf7, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xfc, 0x06, 0xae, 0x9f, 0x09, 0x06, 0x00, 0x00, } func (this *ConsensusParams) Equal(that interface{}) bool { @@ -519,6 +660,9 @@ func (this *ConsensusParams) Equal(that interface{}) bool { if !this.Synchrony.Equal(that1.Synchrony) { return false } + if !this.Timeout.Equal(that1.Timeout) { + return false + } return true } func (this *BlockParams) Equal(that interface{}) bool { @@ -677,10 +821,91 @@ func (this *SynchronyParams) Equal(that interface{}) bool { } else if this == nil { return false } - if this.MessageDelay != that1.MessageDelay { + if this.MessageDelay != nil && that1.MessageDelay != nil { + if *this.MessageDelay != *that1.MessageDelay { + return false + } + } else if this.MessageDelay != nil { + return false + } else if that1.MessageDelay != nil { return false } - if this.Precision != that1.Precision { + if this.Precision != nil && that1.Precision != nil { + if *this.Precision != *that1.Precision { + return false + } + } else if this.Precision != nil { + return false + } else if that1.Precision != nil { + return false + } + return true +} +func (this *TimeoutParams) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*TimeoutParams) + if !ok { + that2, ok := that.(TimeoutParams) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.Propose != nil && that1.Propose != nil { + if *this.Propose != *that1.Propose { + return false + } + } else if this.Propose != nil { + return false + } else if that1.Propose != nil { + return false + } + if this.ProposeDelta != nil && that1.ProposeDelta != nil { + if *this.ProposeDelta != *that1.ProposeDelta { + return false + } + } else if this.ProposeDelta != nil { + return false + } else if that1.ProposeDelta != nil { + return false + } + if this.Vote != nil && that1.Vote != nil { + if *this.Vote != *that1.Vote { + return false + } + } else if this.Vote != nil { + return false + } else if that1.Vote != nil { + return false + } + if this.VoteDelta != nil && that1.VoteDelta != nil { + if *this.VoteDelta != *that1.VoteDelta { + return false + } + } else if this.VoteDelta != nil { + return false + } else if that1.VoteDelta != nil { + return false + } + if this.Commit != nil && that1.Commit != nil { + if *this.Commit != *that1.Commit { + return false + } + } else if this.Commit != nil { + return false + } else if that1.Commit != nil { + return false + } + if this.BypassCommitTimeout != that1.BypassCommitTimeout { return false } return true @@ -705,6 +930,18 @@ func (m *ConsensusParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.Timeout != nil { + { + size, err := m.Timeout.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } if m.Synchrony != nil { { size, err := m.Synchrony.MarshalToSizedBuffer(dAtA[:i]) @@ -826,12 +1063,12 @@ func (m *EvidenceParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x18 } - n6, err6 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.MaxAgeDuration, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.MaxAgeDuration):]) - if err6 != nil { - return 0, err6 + n7, err7 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.MaxAgeDuration, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.MaxAgeDuration):]) + if err7 != nil { + return 0, err7 } - i -= n6 - i = encodeVarintParams(dAtA, i, uint64(n6)) + i -= n7 + i = encodeVarintParams(dAtA, i, uint64(n7)) i-- dAtA[i] = 0x12 if m.MaxAgeNumBlocks != 0 { @@ -955,22 +1192,109 @@ func (m *SynchronyParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - n7, err7 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.Precision, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.Precision):]) - if err7 != nil { - return 0, err7 + if m.Precision != nil { + n8, err8 := github_com_gogo_protobuf_types.StdDurationMarshalTo(*m.Precision, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(*m.Precision):]) + if err8 != nil { + return 0, err8 + } + i -= n8 + i = encodeVarintParams(dAtA, i, uint64(n8)) + i-- + dAtA[i] = 0x12 } - i -= n7 - i = encodeVarintParams(dAtA, i, uint64(n7)) - i-- - dAtA[i] = 0x12 - n8, err8 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.MessageDelay, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.MessageDelay):]) - if err8 != nil { - return 0, err8 + if m.MessageDelay != nil { + n9, err9 := github_com_gogo_protobuf_types.StdDurationMarshalTo(*m.MessageDelay, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(*m.MessageDelay):]) + if err9 != nil { + return 0, err9 + } + i -= n9 + i = encodeVarintParams(dAtA, i, uint64(n9)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TimeoutParams) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TimeoutParams) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TimeoutParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.BypassCommitTimeout { + i-- + if m.BypassCommitTimeout { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x30 + } + if m.Commit != nil { + n10, err10 := github_com_gogo_protobuf_types.StdDurationMarshalTo(*m.Commit, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(*m.Commit):]) + if err10 != nil { + return 0, err10 + } + i -= n10 + i = encodeVarintParams(dAtA, i, uint64(n10)) + i-- + dAtA[i] = 0x2a + } + if m.VoteDelta != nil { + n11, err11 := github_com_gogo_protobuf_types.StdDurationMarshalTo(*m.VoteDelta, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(*m.VoteDelta):]) + if err11 != nil { + return 0, err11 + } + i -= n11 + i = encodeVarintParams(dAtA, i, uint64(n11)) + i-- + dAtA[i] = 0x22 + } + if m.Vote != nil { + n12, err12 := github_com_gogo_protobuf_types.StdDurationMarshalTo(*m.Vote, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(*m.Vote):]) + if err12 != nil { + return 0, err12 + } + i -= n12 + i = encodeVarintParams(dAtA, i, uint64(n12)) + i-- + dAtA[i] = 0x1a + } + if m.ProposeDelta != nil { + n13, err13 := github_com_gogo_protobuf_types.StdDurationMarshalTo(*m.ProposeDelta, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(*m.ProposeDelta):]) + if err13 != nil { + return 0, err13 + } + i -= n13 + i = encodeVarintParams(dAtA, i, uint64(n13)) + i-- + dAtA[i] = 0x12 + } + if m.Propose != nil { + n14, err14 := github_com_gogo_protobuf_types.StdDurationMarshalTo(*m.Propose, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(*m.Propose):]) + if err14 != nil { + return 0, err14 + } + i -= n14 + i = encodeVarintParams(dAtA, i, uint64(n14)) + i-- + dAtA[i] = 0xa } - i -= n8 - i = encodeVarintParams(dAtA, i, uint64(n8)) - i-- - dAtA[i] = 0xa return len(dAtA) - i, nil } @@ -1011,6 +1335,10 @@ func (m *ConsensusParams) Size() (n int) { l = m.Synchrony.Size() n += 1 + l + sovParams(uint64(l)) } + if m.Timeout != nil { + l = m.Timeout.Size() + n += 1 + l + sovParams(uint64(l)) + } return n } @@ -1094,10 +1422,46 @@ func (m *SynchronyParams) Size() (n int) { } var l int _ = l - l = github_com_gogo_protobuf_types.SizeOfStdDuration(m.MessageDelay) - n += 1 + l + sovParams(uint64(l)) - l = github_com_gogo_protobuf_types.SizeOfStdDuration(m.Precision) - n += 1 + l + sovParams(uint64(l)) + if m.MessageDelay != nil { + l = github_com_gogo_protobuf_types.SizeOfStdDuration(*m.MessageDelay) + n += 1 + l + sovParams(uint64(l)) + } + if m.Precision != nil { + l = github_com_gogo_protobuf_types.SizeOfStdDuration(*m.Precision) + n += 1 + l + sovParams(uint64(l)) + } + return n +} + +func (m *TimeoutParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Propose != nil { + l = github_com_gogo_protobuf_types.SizeOfStdDuration(*m.Propose) + n += 1 + l + sovParams(uint64(l)) + } + if m.ProposeDelta != nil { + l = github_com_gogo_protobuf_types.SizeOfStdDuration(*m.ProposeDelta) + n += 1 + l + sovParams(uint64(l)) + } + if m.Vote != nil { + l = github_com_gogo_protobuf_types.SizeOfStdDuration(*m.Vote) + n += 1 + l + sovParams(uint64(l)) + } + if m.VoteDelta != nil { + l = github_com_gogo_protobuf_types.SizeOfStdDuration(*m.VoteDelta) + n += 1 + l + sovParams(uint64(l)) + } + if m.Commit != nil { + l = github_com_gogo_protobuf_types.SizeOfStdDuration(*m.Commit) + n += 1 + l + sovParams(uint64(l)) + } + if m.BypassCommitTimeout { + n += 2 + } return n } @@ -1316,6 +1680,42 @@ func (m *ConsensusParams) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Timeout", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Timeout == nil { + m.Timeout = &TimeoutParams{} + } + if err := m.Timeout.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) @@ -1843,7 +2243,10 @@ func (m *SynchronyParams) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&m.MessageDelay, dAtA[iNdEx:postIndex]); err != nil { + if m.MessageDelay == nil { + m.MessageDelay = new(time.Duration) + } + if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(m.MessageDelay, dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1876,7 +2279,10 @@ func (m *SynchronyParams) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&m.Precision, dAtA[iNdEx:postIndex]); err != nil { + if m.Precision == nil { + m.Precision = new(time.Duration) + } + if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(m.Precision, dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1901,6 +2307,256 @@ func (m *SynchronyParams) Unmarshal(dAtA []byte) error { } return nil } +func (m *TimeoutParams) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TimeoutParams: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TimeoutParams: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Propose", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Propose == nil { + m.Propose = new(time.Duration) + } + if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(m.Propose, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProposeDelta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ProposeDelta == nil { + m.ProposeDelta = new(time.Duration) + } + if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(m.ProposeDelta, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Vote", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Vote == nil { + m.Vote = new(time.Duration) + } + if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(m.Vote, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VoteDelta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.VoteDelta == nil { + m.VoteDelta = new(time.Duration) + } + if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(m.VoteDelta, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Commit", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Commit == nil { + m.Commit = new(time.Duration) + } + if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(m.Commit, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BypassCommitTimeout", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.BypassCommitTimeout = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipParams(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthParams + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipParams(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/proto/tendermint/types/params.proto b/proto/tendermint/types/params.proto index a87670c9f..466ba464f 100644 --- a/proto/tendermint/types/params.proto +++ b/proto/tendermint/types/params.proto @@ -1,6 +1,8 @@ syntax = "proto3"; package tendermint.types; +option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; + import "gogoproto/gogo.proto"; import "google/protobuf/duration.proto"; @@ -73,7 +75,7 @@ message HashedParams { // see the specification of proposer-based timestamps: // https://github.com/tendermint/tendermint/tree/master/spec/consensus/proposer-based-timestamp message SynchronyParams { - // message_delay bounds how long a proposal message may take to reach all validators on a newtork + // message_delay bounds how long a proposal message may take to reach all validators on a network // and still be considered valid. google.protobuf.Duration message_delay = 1 [(gogoproto.stdduration) = true]; // precision bounds how skewed a proposer's clock may be from any validator @@ -118,10 +120,10 @@ message TimeoutParams { // used to allow slow precommits to arrive for inclusion in the next height before progressing. google.protobuf.Duration commit = 5 [(gogoproto.stdduration) = true]; - // enable_commit_timeout_bypass configures the node to proceed immediately to + // bypass_commit_timeout configures the node to proceed immediately to // the next height once the node has received all precommits for a block, forgoing // the remaining commit timeout. - // Setting enable_commit_timeout_bypass false (the default) causes Tendermint to wait - // for the full commit. - bool enable_commit_timeout_bypass = 6; + // Setting bypass_commit_timeout false (the default) causes Tendermint to wait + // for the full commit timeout. + bool bypass_commit_timeout = 6; } diff --git a/proto/tendermint/types/types.pb.go b/proto/tendermint/types/types.pb.go index 5c1f99d23..1904afcd1 100644 --- a/proto/tendermint/types/types.pb.go +++ b/proto/tendermint/types/types.pb.go @@ -29,7 +29,7 @@ var _ = time.Kitchen // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// BlockIdFlag indicates which BlcokID the signature is for +// BlockIdFlag indicates which BlockID the signature is for type BlockIDFlag int32 const ( @@ -466,15 +466,22 @@ func (m *Data) GetTxs() [][]byte { // Vote represents a prevote, precommit, or commit vote from validators for // consensus. type Vote struct { - Type SignedMsgType `protobuf:"varint,1,opt,name=type,proto3,enum=tendermint.types.SignedMsgType" json:"type,omitempty"` - Height int64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` - Round int32 `protobuf:"varint,3,opt,name=round,proto3" json:"round,omitempty"` - BlockID BlockID `protobuf:"bytes,4,opt,name=block_id,json=blockId,proto3" json:"block_id"` - Timestamp time.Time `protobuf:"bytes,5,opt,name=timestamp,proto3,stdtime" json:"timestamp"` - ValidatorAddress []byte `protobuf:"bytes,6,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty"` - ValidatorIndex int32 `protobuf:"varint,7,opt,name=validator_index,json=validatorIndex,proto3" json:"validator_index,omitempty"` - Signature []byte `protobuf:"bytes,8,opt,name=signature,proto3" json:"signature,omitempty"` - VoteExtension *VoteExtension `protobuf:"bytes,9,opt,name=vote_extension,json=voteExtension,proto3" json:"vote_extension,omitempty"` + Type SignedMsgType `protobuf:"varint,1,opt,name=type,proto3,enum=tendermint.types.SignedMsgType" json:"type,omitempty"` + Height int64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` + Round int32 `protobuf:"varint,3,opt,name=round,proto3" json:"round,omitempty"` + BlockID BlockID `protobuf:"bytes,4,opt,name=block_id,json=blockId,proto3" json:"block_id"` + Timestamp time.Time `protobuf:"bytes,5,opt,name=timestamp,proto3,stdtime" json:"timestamp"` + ValidatorAddress []byte `protobuf:"bytes,6,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty"` + ValidatorIndex int32 `protobuf:"varint,7,opt,name=validator_index,json=validatorIndex,proto3" json:"validator_index,omitempty"` + // Vote signature by the validator if they participated in consensus for the + // associated block. + Signature []byte `protobuf:"bytes,8,opt,name=signature,proto3" json:"signature,omitempty"` + // Vote extension provided by the application. Only valid for precommit + // messages. + Extension []byte `protobuf:"bytes,9,opt,name=extension,proto3" json:"extension,omitempty"` + // Vote extension signature by the validator if they participated in + // consensus for the associated block. Only valid for precommit messages. + ExtensionSignature []byte `protobuf:"bytes,10,opt,name=extension_signature,json=extensionSignature,proto3" json:"extension_signature,omitempty"` } func (m *Vote) Reset() { *m = Vote{} } @@ -566,108 +573,16 @@ func (m *Vote) GetSignature() []byte { return nil } -func (m *Vote) GetVoteExtension() *VoteExtension { +func (m *Vote) GetExtension() []byte { if m != nil { - return m.VoteExtension + return m.Extension } return nil } -// VoteExtension is app-defined additional information to the validator votes. -type VoteExtension struct { - AppDataToSign []byte `protobuf:"bytes,1,opt,name=app_data_to_sign,json=appDataToSign,proto3" json:"app_data_to_sign,omitempty"` - AppDataSelfAuthenticating []byte `protobuf:"bytes,2,opt,name=app_data_self_authenticating,json=appDataSelfAuthenticating,proto3" json:"app_data_self_authenticating,omitempty"` -} - -func (m *VoteExtension) Reset() { *m = VoteExtension{} } -func (m *VoteExtension) String() string { return proto.CompactTextString(m) } -func (*VoteExtension) ProtoMessage() {} -func (*VoteExtension) Descriptor() ([]byte, []int) { - return fileDescriptor_d3a6e55e2345de56, []int{6} -} -func (m *VoteExtension) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *VoteExtension) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_VoteExtension.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *VoteExtension) XXX_Merge(src proto.Message) { - xxx_messageInfo_VoteExtension.Merge(m, src) -} -func (m *VoteExtension) XXX_Size() int { - return m.Size() -} -func (m *VoteExtension) XXX_DiscardUnknown() { - xxx_messageInfo_VoteExtension.DiscardUnknown(m) -} - -var xxx_messageInfo_VoteExtension proto.InternalMessageInfo - -func (m *VoteExtension) GetAppDataToSign() []byte { +func (m *Vote) GetExtensionSignature() []byte { if m != nil { - return m.AppDataToSign - } - return nil -} - -func (m *VoteExtension) GetAppDataSelfAuthenticating() []byte { - if m != nil { - return m.AppDataSelfAuthenticating - } - return nil -} - -// VoteExtensionToSign is a subset of VoteExtension that is signed by the validators private key. -// VoteExtensionToSign is extracted from an existing VoteExtension. -type VoteExtensionToSign struct { - AppDataToSign []byte `protobuf:"bytes,1,opt,name=app_data_to_sign,json=appDataToSign,proto3" json:"app_data_to_sign,omitempty"` -} - -func (m *VoteExtensionToSign) Reset() { *m = VoteExtensionToSign{} } -func (m *VoteExtensionToSign) String() string { return proto.CompactTextString(m) } -func (*VoteExtensionToSign) ProtoMessage() {} -func (*VoteExtensionToSign) Descriptor() ([]byte, []int) { - return fileDescriptor_d3a6e55e2345de56, []int{7} -} -func (m *VoteExtensionToSign) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *VoteExtensionToSign) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_VoteExtensionToSign.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *VoteExtensionToSign) XXX_Merge(src proto.Message) { - xxx_messageInfo_VoteExtensionToSign.Merge(m, src) -} -func (m *VoteExtensionToSign) XXX_Size() int { - return m.Size() -} -func (m *VoteExtensionToSign) XXX_DiscardUnknown() { - xxx_messageInfo_VoteExtensionToSign.DiscardUnknown(m) -} - -var xxx_messageInfo_VoteExtensionToSign proto.InternalMessageInfo - -func (m *VoteExtensionToSign) GetAppDataToSign() []byte { - if m != nil { - return m.AppDataToSign + return m.ExtensionSignature } return nil } @@ -685,7 +600,7 @@ func (m *Commit) Reset() { *m = Commit{} } func (m *Commit) String() string { return proto.CompactTextString(m) } func (*Commit) ProtoMessage() {} func (*Commit) Descriptor() ([]byte, []int) { - return fileDescriptor_d3a6e55e2345de56, []int{8} + return fileDescriptor_d3a6e55e2345de56, []int{6} } func (m *Commit) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -744,18 +659,17 @@ func (m *Commit) GetSignatures() []CommitSig { // CommitSig is a part of the Vote included in a Commit. type CommitSig struct { - BlockIdFlag BlockIDFlag `protobuf:"varint,1,opt,name=block_id_flag,json=blockIdFlag,proto3,enum=tendermint.types.BlockIDFlag" json:"block_id_flag,omitempty"` - ValidatorAddress []byte `protobuf:"bytes,2,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty"` - Timestamp time.Time `protobuf:"bytes,3,opt,name=timestamp,proto3,stdtime" json:"timestamp"` - Signature []byte `protobuf:"bytes,4,opt,name=signature,proto3" json:"signature,omitempty"` - VoteExtension *VoteExtensionToSign `protobuf:"bytes,5,opt,name=vote_extension,json=voteExtension,proto3" json:"vote_extension,omitempty"` + BlockIdFlag BlockIDFlag `protobuf:"varint,1,opt,name=block_id_flag,json=blockIdFlag,proto3,enum=tendermint.types.BlockIDFlag" json:"block_id_flag,omitempty"` + ValidatorAddress []byte `protobuf:"bytes,2,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty"` + Timestamp time.Time `protobuf:"bytes,3,opt,name=timestamp,proto3,stdtime" json:"timestamp"` + Signature []byte `protobuf:"bytes,4,opt,name=signature,proto3" json:"signature,omitempty"` } func (m *CommitSig) Reset() { *m = CommitSig{} } func (m *CommitSig) String() string { return proto.CompactTextString(m) } func (*CommitSig) ProtoMessage() {} func (*CommitSig) Descriptor() ([]byte, []int) { - return fileDescriptor_d3a6e55e2345de56, []int{9} + return fileDescriptor_d3a6e55e2345de56, []int{7} } func (m *CommitSig) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -812,13 +726,6 @@ func (m *CommitSig) GetSignature() []byte { return nil } -func (m *CommitSig) GetVoteExtension() *VoteExtensionToSign { - if m != nil { - return m.VoteExtension - } - return nil -} - type Proposal struct { Type SignedMsgType `protobuf:"varint,1,opt,name=type,proto3,enum=tendermint.types.SignedMsgType" json:"type,omitempty"` Height int64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` @@ -833,7 +740,7 @@ func (m *Proposal) Reset() { *m = Proposal{} } func (m *Proposal) String() string { return proto.CompactTextString(m) } func (*Proposal) ProtoMessage() {} func (*Proposal) Descriptor() ([]byte, []int) { - return fileDescriptor_d3a6e55e2345de56, []int{10} + return fileDescriptor_d3a6e55e2345de56, []int{8} } func (m *Proposal) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -920,7 +827,7 @@ func (m *SignedHeader) Reset() { *m = SignedHeader{} } func (m *SignedHeader) String() string { return proto.CompactTextString(m) } func (*SignedHeader) ProtoMessage() {} func (*SignedHeader) Descriptor() ([]byte, []int) { - return fileDescriptor_d3a6e55e2345de56, []int{11} + return fileDescriptor_d3a6e55e2345de56, []int{9} } func (m *SignedHeader) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -972,7 +879,7 @@ func (m *LightBlock) Reset() { *m = LightBlock{} } func (m *LightBlock) String() string { return proto.CompactTextString(m) } func (*LightBlock) ProtoMessage() {} func (*LightBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_d3a6e55e2345de56, []int{12} + return fileDescriptor_d3a6e55e2345de56, []int{10} } func (m *LightBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1026,7 +933,7 @@ func (m *BlockMeta) Reset() { *m = BlockMeta{} } func (m *BlockMeta) String() string { return proto.CompactTextString(m) } func (*BlockMeta) ProtoMessage() {} func (*BlockMeta) Descriptor() ([]byte, []int) { - return fileDescriptor_d3a6e55e2345de56, []int{13} + return fileDescriptor_d3a6e55e2345de56, []int{11} } func (m *BlockMeta) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1095,7 +1002,7 @@ func (m *TxProof) Reset() { *m = TxProof{} } func (m *TxProof) String() string { return proto.CompactTextString(m) } func (*TxProof) ProtoMessage() {} func (*TxProof) Descriptor() ([]byte, []int) { - return fileDescriptor_d3a6e55e2345de56, []int{14} + return fileDescriptor_d3a6e55e2345de56, []int{12} } func (m *TxProof) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1154,8 +1061,6 @@ func init() { proto.RegisterType((*Header)(nil), "tendermint.types.Header") proto.RegisterType((*Data)(nil), "tendermint.types.Data") proto.RegisterType((*Vote)(nil), "tendermint.types.Vote") - proto.RegisterType((*VoteExtension)(nil), "tendermint.types.VoteExtension") - proto.RegisterType((*VoteExtensionToSign)(nil), "tendermint.types.VoteExtensionToSign") proto.RegisterType((*Commit)(nil), "tendermint.types.Commit") proto.RegisterType((*CommitSig)(nil), "tendermint.types.CommitSig") proto.RegisterType((*Proposal)(nil), "tendermint.types.Proposal") @@ -1168,96 +1073,91 @@ func init() { func init() { proto.RegisterFile("tendermint/types/types.proto", fileDescriptor_d3a6e55e2345de56) } var fileDescriptor_d3a6e55e2345de56 = []byte{ - // 1423 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x4f, 0x6f, 0xdb, 0x64, - 0x18, 0xaf, 0x13, 0xb7, 0x49, 0x9e, 0xc4, 0x6d, 0xfa, 0xd2, 0x6d, 0x69, 0xb6, 0xa6, 0x91, 0xd1, - 0x58, 0x37, 0x50, 0x3a, 0x3a, 0xc4, 0x9f, 0x03, 0xa0, 0x24, 0xcd, 0xb6, 0x68, 0x6d, 0x1a, 0x9c, - 0x6c, 0x08, 0x2e, 0x96, 0x93, 0xbc, 0x4d, 0xcc, 0x1c, 0xdb, 0xb2, 0xdf, 0x94, 0x76, 0x9f, 0x00, - 0xf5, 0xb4, 0x13, 0xb7, 0x9e, 0xe0, 0x80, 0xc4, 0x05, 0x89, 0x2f, 0x80, 0x38, 0xed, 0xb8, 0x1b, - 0x9c, 0x06, 0xea, 0x24, 0x3e, 0x07, 0x7a, 0xff, 0xc4, 0xb1, 0x9b, 0x86, 0x4d, 0xd5, 0xc4, 0x25, - 0xf2, 0xfb, 0x3c, 0xbf, 0xe7, 0xff, 0xcf, 0x8f, 0xdf, 0xc0, 0x35, 0x82, 0xed, 0x1e, 0xf6, 0x86, - 0xa6, 0x4d, 0x36, 0xc9, 0x91, 0x8b, 0x7d, 0xfe, 0x5b, 0x72, 0x3d, 0x87, 0x38, 0x28, 0x3b, 0xd1, - 0x96, 0x98, 0x3c, 0xbf, 0xd2, 0x77, 0xfa, 0x0e, 0x53, 0x6e, 0xd2, 0x27, 0x8e, 0xcb, 0xaf, 0xf7, - 0x1d, 0xa7, 0x6f, 0xe1, 0x4d, 0x76, 0xea, 0x8c, 0xf6, 0x37, 0x89, 0x39, 0xc4, 0x3e, 0x31, 0x86, - 0xae, 0x00, 0xac, 0x85, 0xc2, 0x74, 0xbd, 0x23, 0x97, 0x38, 0x14, 0xeb, 0xec, 0x0b, 0x75, 0x21, - 0xa4, 0x3e, 0xc0, 0x9e, 0x6f, 0x3a, 0x76, 0x38, 0x8f, 0x7c, 0x71, 0x2a, 0xcb, 0x03, 0xc3, 0x32, - 0x7b, 0x06, 0x71, 0x3c, 0x8e, 0x50, 0x3f, 0x01, 0xa5, 0x69, 0x78, 0xa4, 0x85, 0xc9, 0x7d, 0x6c, - 0xf4, 0xb0, 0x87, 0x56, 0x60, 0x9e, 0x38, 0xc4, 0xb0, 0x72, 0x52, 0x51, 0xda, 0x50, 0x34, 0x7e, - 0x40, 0x08, 0xe4, 0x81, 0xe1, 0x0f, 0x72, 0xb1, 0xa2, 0xb4, 0x91, 0xd1, 0xd8, 0xb3, 0x3a, 0x00, - 0x99, 0x9a, 0x52, 0x0b, 0xd3, 0xee, 0xe1, 0xc3, 0xb1, 0x05, 0x3b, 0x50, 0x69, 0xe7, 0x88, 0x60, - 0x5f, 0x98, 0xf0, 0x03, 0xfa, 0x00, 0xe6, 0x59, 0xfe, 0xb9, 0x78, 0x51, 0xda, 0x48, 0x6f, 0xe5, - 0x4a, 0xa1, 0x46, 0xf1, 0xfa, 0x4a, 0x4d, 0xaa, 0xaf, 0xc8, 0xcf, 0x5e, 0xac, 0xcf, 0x69, 0x1c, - 0xac, 0x5a, 0x90, 0xa8, 0x58, 0x4e, 0xf7, 0x71, 0x7d, 0x3b, 0x48, 0x44, 0x9a, 0x24, 0x82, 0x76, - 0x61, 0xc9, 0x35, 0x3c, 0xa2, 0xfb, 0x98, 0xe8, 0x03, 0x56, 0x05, 0x0b, 0x9a, 0xde, 0x5a, 0x2f, - 0x9d, 0x9d, 0x43, 0x29, 0x52, 0xac, 0x88, 0xa2, 0xb8, 0x61, 0xa1, 0xfa, 0x8f, 0x0c, 0x0b, 0xa2, - 0x19, 0x9f, 0x42, 0x42, 0xb4, 0x95, 0x05, 0x4c, 0x6f, 0xad, 0x85, 0x3d, 0x0a, 0x55, 0xa9, 0xea, - 0xd8, 0x3e, 0xb6, 0xfd, 0x91, 0x2f, 0xfc, 0x8d, 0x6d, 0xd0, 0x3b, 0x90, 0xec, 0x0e, 0x0c, 0xd3, - 0xd6, 0xcd, 0x1e, 0xcb, 0x28, 0x55, 0x49, 0x9f, 0xbe, 0x58, 0x4f, 0x54, 0xa9, 0xac, 0xbe, 0xad, - 0x25, 0x98, 0xb2, 0xde, 0x43, 0x97, 0x61, 0x61, 0x80, 0xcd, 0xfe, 0x80, 0xb0, 0xb6, 0xc4, 0x35, - 0x71, 0x42, 0x1f, 0x83, 0x4c, 0x09, 0x91, 0x93, 0x59, 0xec, 0x7c, 0x89, 0xb3, 0xa5, 0x34, 0x66, - 0x4b, 0xa9, 0x3d, 0x66, 0x4b, 0x25, 0x49, 0x03, 0x3f, 0xfd, 0x6b, 0x5d, 0xd2, 0x98, 0x05, 0xaa, - 0x82, 0x62, 0x19, 0x3e, 0xd1, 0x3b, 0xb4, 0x6d, 0x34, 0xfc, 0x3c, 0x73, 0xb1, 0x3a, 0xdd, 0x10, - 0xd1, 0x58, 0x91, 0x7a, 0x9a, 0x5a, 0x71, 0x51, 0x0f, 0x6d, 0x40, 0x96, 0x39, 0xe9, 0x3a, 0xc3, - 0xa1, 0x49, 0x74, 0xd6, 0xf7, 0x05, 0xd6, 0xf7, 0x45, 0x2a, 0xaf, 0x32, 0xf1, 0x7d, 0x3a, 0x81, - 0xab, 0x90, 0xea, 0x19, 0xc4, 0xe0, 0x90, 0x04, 0x83, 0x24, 0xa9, 0x80, 0x29, 0x6f, 0xc0, 0x52, - 0xc0, 0x3a, 0x9f, 0x43, 0x92, 0xdc, 0xcb, 0x44, 0xcc, 0x80, 0xb7, 0x61, 0xc5, 0xc6, 0x87, 0x44, - 0x3f, 0x8b, 0x4e, 0x31, 0x34, 0xa2, 0xba, 0x47, 0x51, 0x8b, 0xeb, 0xb0, 0xd8, 0x1d, 0x37, 0x9f, - 0x63, 0x81, 0x61, 0x95, 0x40, 0xca, 0x60, 0xab, 0x90, 0x34, 0x5c, 0x97, 0x03, 0xd2, 0x0c, 0x90, - 0x30, 0x5c, 0x97, 0xa9, 0x6e, 0xc1, 0x32, 0xab, 0xd1, 0xc3, 0xfe, 0xc8, 0x22, 0xc2, 0x49, 0x86, - 0x61, 0x96, 0xa8, 0x42, 0xe3, 0x72, 0x86, 0x7d, 0x1b, 0x14, 0x7c, 0x60, 0xf6, 0xb0, 0xdd, 0xc5, - 0x1c, 0xa7, 0x30, 0x5c, 0x66, 0x2c, 0x64, 0xa0, 0x9b, 0x90, 0x75, 0x3d, 0xc7, 0x75, 0x7c, 0xec, - 0xe9, 0x46, 0xaf, 0xe7, 0x61, 0xdf, 0xcf, 0x2d, 0x72, 0x7f, 0x63, 0x79, 0x99, 0x8b, 0xd5, 0x1c, - 0xc8, 0xdb, 0x06, 0x31, 0x50, 0x16, 0xe2, 0xe4, 0xd0, 0xcf, 0x49, 0xc5, 0xf8, 0x46, 0x46, 0xa3, - 0x8f, 0xea, 0x2f, 0x71, 0x90, 0x1f, 0x39, 0x04, 0xa3, 0x3b, 0x20, 0xd3, 0x31, 0x31, 0xf6, 0x2d, - 0x9e, 0xc7, 0xe7, 0x96, 0xd9, 0xb7, 0x71, 0x6f, 0xd7, 0xef, 0xb7, 0x8f, 0x5c, 0xac, 0x31, 0x70, - 0x88, 0x4e, 0xb1, 0x08, 0x9d, 0x56, 0x60, 0xde, 0x73, 0x46, 0x76, 0x8f, 0xb1, 0x6c, 0x5e, 0xe3, - 0x07, 0x54, 0x83, 0x64, 0xc0, 0x12, 0xf9, 0x55, 0x2c, 0x59, 0xa2, 0x2c, 0xa1, 0x1c, 0x16, 0x02, - 0x2d, 0xd1, 0x11, 0x64, 0xa9, 0x40, 0x2a, 0x58, 0x5e, 0x82, 0x6d, 0xaf, 0x47, 0xd8, 0x89, 0x19, - 0x7a, 0x17, 0x96, 0x83, 0xd9, 0x07, 0xcd, 0xe3, 0x8c, 0xcb, 0x06, 0x0a, 0xd1, 0xbd, 0x08, 0xad, - 0x74, 0xbe, 0x80, 0x12, 0xac, 0xae, 0x09, 0xad, 0xea, 0x6c, 0x13, 0x5d, 0x83, 0x94, 0x6f, 0xf6, - 0x6d, 0x83, 0x8c, 0x3c, 0x2c, 0x98, 0x37, 0x11, 0xa0, 0xbb, 0xb0, 0x78, 0xe0, 0x10, 0xac, 0xe3, - 0x43, 0x82, 0x6d, 0xf6, 0xa6, 0xa7, 0x66, 0xed, 0x0e, 0x3a, 0x91, 0xda, 0x18, 0xa6, 0x29, 0x07, - 0xe1, 0xa3, 0x7a, 0x04, 0x4a, 0x44, 0x8f, 0x6e, 0x40, 0x96, 0x92, 0x8e, 0xbd, 0x17, 0xc4, 0xd1, - 0x69, 0x44, 0xb1, 0xb5, 0x14, 0xc3, 0x75, 0xe9, 0xe0, 0xdb, 0x0e, 0x9d, 0x1e, 0xfa, 0x1c, 0xae, - 0x05, 0x40, 0x1f, 0x5b, 0xfb, 0xba, 0x31, 0x22, 0x03, 0x6c, 0x13, 0xb3, 0x6b, 0x10, 0xd3, 0xee, - 0x8b, 0x05, 0xba, 0x2a, 0x8c, 0x5a, 0xd8, 0xda, 0x2f, 0x47, 0x00, 0xea, 0x67, 0xf0, 0x56, 0x24, - 0xb4, 0xf0, 0xfb, 0xba, 0x09, 0xa8, 0xbf, 0x49, 0xb0, 0xc0, 0x5f, 0xe6, 0x10, 0x75, 0xa4, 0xf3, - 0xa9, 0x13, 0x9b, 0x45, 0x9d, 0xf8, 0xc5, 0xa9, 0x53, 0x06, 0x08, 0xe6, 0xe1, 0xe7, 0xe4, 0x62, - 0x7c, 0x23, 0xbd, 0x75, 0x75, 0xda, 0x11, 0x4f, 0xb1, 0x65, 0xf6, 0xc5, 0xae, 0x0a, 0x19, 0xa9, - 0x3f, 0xc7, 0x20, 0x15, 0xe8, 0x51, 0x19, 0x94, 0x71, 0x5e, 0xfa, 0xbe, 0x65, 0xf4, 0xc5, 0xeb, - 0xb3, 0x36, 0x33, 0xb9, 0xbb, 0x96, 0xd1, 0xd7, 0xd2, 0x22, 0x1f, 0x7a, 0x38, 0x9f, 0x8a, 0xb1, - 0x19, 0x54, 0x8c, 0x70, 0x3f, 0x7e, 0x31, 0xee, 0x47, 0x58, 0x2a, 0x9f, 0x65, 0xe9, 0xce, 0x14, - 0x4b, 0xf9, 0x2b, 0x76, 0xfd, 0x15, 0x2c, 0xe5, 0x13, 0x3e, 0xcb, 0xd5, 0x5f, 0x63, 0x90, 0x6c, - 0xb2, 0x65, 0x64, 0x58, 0xff, 0xc7, 0x8a, 0xb9, 0x0a, 0x29, 0xd7, 0xb1, 0x74, 0xae, 0x91, 0x99, - 0x26, 0xe9, 0x3a, 0x96, 0x36, 0x45, 0xa2, 0xf9, 0x37, 0xb4, 0x7f, 0x16, 0xde, 0xc0, 0x0c, 0x12, - 0x67, 0x66, 0xa0, 0x7a, 0x90, 0xe1, 0xad, 0x10, 0x97, 0x83, 0xdb, 0xb4, 0x07, 0xec, 0xb6, 0x21, - 0x4d, 0x5f, 0x66, 0x78, 0xda, 0x1c, 0xa9, 0x09, 0x1c, 0xb5, 0xe0, 0xdf, 0x52, 0x71, 0x3f, 0xc9, - 0xcd, 0x22, 0xb9, 0x26, 0x70, 0xea, 0xf7, 0x12, 0xc0, 0x0e, 0xed, 0x2c, 0xab, 0x97, 0x7e, 0xd6, - 0x7d, 0x96, 0x82, 0x1e, 0x89, 0x5c, 0x98, 0x35, 0x34, 0x11, 0x3f, 0xe3, 0x87, 0xf3, 0xae, 0x82, - 0x32, 0xa1, 0xb6, 0x8f, 0xc7, 0xc9, 0x9c, 0xe3, 0x24, 0xf8, 0xda, 0xb6, 0x30, 0xd1, 0x32, 0x07, - 0xa1, 0x93, 0xfa, 0xbb, 0x04, 0x29, 0x96, 0xd3, 0x2e, 0x26, 0x46, 0x64, 0x86, 0xd2, 0xc5, 0x67, - 0xb8, 0x06, 0xc0, 0xdd, 0xf8, 0xe6, 0x13, 0x2c, 0x98, 0x95, 0x62, 0x92, 0x96, 0xf9, 0x04, 0xa3, - 0x0f, 0x83, 0x86, 0xc7, 0xff, 0xbb, 0xe1, 0x62, 0x41, 0x8c, 0xdb, 0x7e, 0x05, 0x12, 0xf6, 0x68, - 0xa8, 0xd3, 0x6f, 0xac, 0xcc, 0xd9, 0x6a, 0x8f, 0x86, 0xed, 0x43, 0x5f, 0xfd, 0x06, 0x12, 0xed, - 0x43, 0x76, 0xdf, 0xa4, 0x14, 0xf5, 0x1c, 0x47, 0x5c, 0x72, 0xf8, 0x96, 0x4c, 0x52, 0x01, 0xfb, - 0xa6, 0x23, 0x90, 0xe9, 0x16, 0x1d, 0xdf, 0x7e, 0xe9, 0x33, 0x2a, 0xbd, 0xe6, 0x4d, 0x56, 0xdc, - 0x61, 0x6f, 0xfd, 0x21, 0x41, 0x3a, 0xb4, 0x6d, 0xd0, 0xfb, 0x70, 0xa9, 0xb2, 0xb3, 0x57, 0x7d, - 0xa0, 0xd7, 0xb7, 0xf5, 0xbb, 0x3b, 0xe5, 0x7b, 0xfa, 0xc3, 0xc6, 0x83, 0xc6, 0xde, 0x97, 0x8d, - 0xec, 0x5c, 0xfe, 0xf2, 0xf1, 0x49, 0x11, 0x85, 0xb0, 0x0f, 0xed, 0xc7, 0xb6, 0xf3, 0xad, 0x8d, - 0x36, 0x61, 0x25, 0x6a, 0x52, 0xae, 0xb4, 0x6a, 0x8d, 0x76, 0x56, 0xca, 0x5f, 0x3a, 0x3e, 0x29, - 0x2e, 0x87, 0x2c, 0xca, 0x1d, 0x1f, 0xdb, 0x64, 0xda, 0xa0, 0xba, 0xb7, 0xbb, 0x5b, 0x6f, 0x67, - 0x63, 0x53, 0x06, 0x62, 0xfd, 0xdf, 0x84, 0xe5, 0xa8, 0x41, 0xa3, 0xbe, 0x93, 0x8d, 0xe7, 0xd1, - 0xf1, 0x49, 0x71, 0x31, 0x84, 0x6e, 0x98, 0x56, 0x3e, 0xf9, 0xdd, 0x0f, 0x85, 0xb9, 0x9f, 0x7e, - 0x2c, 0x48, 0xb4, 0x32, 0x25, 0xb2, 0x23, 0xd0, 0x7b, 0x70, 0xa5, 0x55, 0xbf, 0xd7, 0xa8, 0x6d, - 0xeb, 0xbb, 0xad, 0x7b, 0x7a, 0xfb, 0xab, 0x66, 0x2d, 0x54, 0xdd, 0xd2, 0xf1, 0x49, 0x31, 0x2d, - 0x4a, 0x9a, 0x85, 0x6e, 0x6a, 0xb5, 0x47, 0x7b, 0xed, 0x5a, 0x56, 0xe2, 0xe8, 0xa6, 0x87, 0xe9, - 0x02, 0x63, 0xe8, 0xdb, 0xb0, 0x7a, 0x0e, 0x3a, 0x28, 0x6c, 0xf9, 0xf8, 0xa4, 0xa8, 0x34, 0x3d, - 0xcc, 0xdf, 0x1f, 0x66, 0x51, 0x82, 0xdc, 0xb4, 0xc5, 0x5e, 0x73, 0xaf, 0x55, 0xde, 0xc9, 0x16, - 0xf3, 0xd9, 0xe3, 0x93, 0x62, 0x66, 0xbc, 0x0c, 0x29, 0x7e, 0x52, 0x59, 0xe5, 0x8b, 0x67, 0xa7, - 0x05, 0xe9, 0xf9, 0x69, 0x41, 0xfa, 0xfb, 0xb4, 0x20, 0x3d, 0x7d, 0x59, 0x98, 0x7b, 0xfe, 0xb2, - 0x30, 0xf7, 0xe7, 0xcb, 0xc2, 0xdc, 0xd7, 0x1f, 0xf5, 0x4d, 0x32, 0x18, 0x75, 0x4a, 0x5d, 0x67, - 0xb8, 0x19, 0xfe, 0x8f, 0x35, 0x79, 0xe4, 0xff, 0xf5, 0xce, 0xfe, 0xff, 0xea, 0x2c, 0x30, 0xf9, - 0x9d, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x38, 0xfc, 0x14, 0xb1, 0x40, 0x0e, 0x00, 0x00, + // 1341 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0xcf, 0x73, 0xdb, 0xc4, + 0x17, 0x8f, 0x62, 0x25, 0xb6, 0x9f, 0xed, 0xc4, 0xd9, 0x6f, 0xda, 0xba, 0x6e, 0xe3, 0x68, 0xfc, + 0x1d, 0x20, 0x2d, 0x8c, 0x52, 0x52, 0x86, 0x1f, 0x07, 0x0e, 0xb6, 0x93, 0xb6, 0x9e, 0x26, 0x8e, + 0x91, 0xdd, 0x32, 0x70, 0xd1, 0xc8, 0xd6, 0xd6, 0x16, 0x95, 0x25, 0x8d, 0x76, 0x1d, 0x92, 0xfe, + 0x05, 0x4c, 0x4e, 0x3d, 0x71, 0xcb, 0x09, 0x0e, 0xdc, 0x39, 0x70, 0x65, 0x38, 0xf5, 0xd8, 0x1b, + 0x5c, 0x28, 0x4c, 0x3a, 0xc3, 0xdf, 0xc1, 0xec, 0x0f, 0xc9, 0x72, 0x9c, 0x40, 0xa7, 0xd3, 0xe1, + 0xe2, 0xd1, 0xbe, 0xf7, 0x79, 0x6f, 0xdf, 0x8f, 0xcf, 0xee, 0x5b, 0xc3, 0x75, 0x8a, 0x3d, 0x1b, + 0x87, 0x23, 0xc7, 0xa3, 0x9b, 0xf4, 0x28, 0xc0, 0x44, 0xfc, 0xea, 0x41, 0xe8, 0x53, 0x1f, 0x15, + 0x27, 0x5a, 0x9d, 0xcb, 0xcb, 0xab, 0x03, 0x7f, 0xe0, 0x73, 0xe5, 0x26, 0xfb, 0x12, 0xb8, 0xf2, + 0xfa, 0xc0, 0xf7, 0x07, 0x2e, 0xde, 0xe4, 0xab, 0xde, 0xf8, 0xd1, 0x26, 0x75, 0x46, 0x98, 0x50, + 0x6b, 0x14, 0x48, 0xc0, 0x5a, 0x62, 0x9b, 0x7e, 0x78, 0x14, 0x50, 0x9f, 0x61, 0xfd, 0x47, 0x52, + 0x5d, 0x49, 0xa8, 0x0f, 0x70, 0x48, 0x1c, 0xdf, 0x4b, 0xc6, 0x51, 0xd6, 0x66, 0xa2, 0x3c, 0xb0, + 0x5c, 0xc7, 0xb6, 0xa8, 0x1f, 0x0a, 0x44, 0xf5, 0x13, 0x28, 0xb4, 0xad, 0x90, 0x76, 0x30, 0xbd, + 0x87, 0x2d, 0x1b, 0x87, 0x68, 0x15, 0x16, 0xa8, 0x4f, 0x2d, 0xb7, 0xa4, 0x68, 0xca, 0x46, 0xc1, + 0x10, 0x0b, 0x84, 0x40, 0x1d, 0x5a, 0x64, 0x58, 0x9a, 0xd7, 0x94, 0x8d, 0xbc, 0xc1, 0xbf, 0xab, + 0x43, 0x50, 0x99, 0x29, 0xb3, 0x70, 0x3c, 0x1b, 0x1f, 0x46, 0x16, 0x7c, 0xc1, 0xa4, 0xbd, 0x23, + 0x8a, 0x89, 0x34, 0x11, 0x0b, 0xf4, 0x01, 0x2c, 0xf0, 0xf8, 0x4b, 0x29, 0x4d, 0xd9, 0xc8, 0x6d, + 0x95, 0xf4, 0x44, 0xa1, 0x44, 0x7e, 0x7a, 0x9b, 0xe9, 0xeb, 0xea, 0xb3, 0x17, 0xeb, 0x73, 0x86, + 0x00, 0x57, 0x5d, 0x48, 0xd7, 0x5d, 0xbf, 0xff, 0xb8, 0xb9, 0x1d, 0x07, 0xa2, 0x4c, 0x02, 0x41, + 0x7b, 0xb0, 0x1c, 0x58, 0x21, 0x35, 0x09, 0xa6, 0xe6, 0x90, 0x67, 0xc1, 0x37, 0xcd, 0x6d, 0xad, + 0xeb, 0x67, 0xfb, 0xa0, 0x4f, 0x25, 0x2b, 0x77, 0x29, 0x04, 0x49, 0x61, 0xf5, 0x2f, 0x15, 0x16, + 0x65, 0x31, 0x3e, 0x85, 0xb4, 0x2c, 0x2b, 0xdf, 0x30, 0xb7, 0xb5, 0x96, 0xf4, 0x28, 0x55, 0x7a, + 0xc3, 0xf7, 0x08, 0xf6, 0xc8, 0x98, 0x48, 0x7f, 0x91, 0x0d, 0x7a, 0x1b, 0x32, 0xfd, 0xa1, 0xe5, + 0x78, 0xa6, 0x63, 0xf3, 0x88, 0xb2, 0xf5, 0xdc, 0xe9, 0x8b, 0xf5, 0x74, 0x83, 0xc9, 0x9a, 0xdb, + 0x46, 0x9a, 0x2b, 0x9b, 0x36, 0xba, 0x0c, 0x8b, 0x43, 0xec, 0x0c, 0x86, 0x94, 0x97, 0x25, 0x65, + 0xc8, 0x15, 0xfa, 0x18, 0x54, 0x46, 0x88, 0x92, 0xca, 0xf7, 0x2e, 0xeb, 0x82, 0x2d, 0x7a, 0xc4, + 0x16, 0xbd, 0x1b, 0xb1, 0xa5, 0x9e, 0x61, 0x1b, 0x3f, 0xfd, 0x63, 0x5d, 0x31, 0xb8, 0x05, 0x6a, + 0x40, 0xc1, 0xb5, 0x08, 0x35, 0x7b, 0xac, 0x6c, 0x6c, 0xfb, 0x05, 0xee, 0xe2, 0xea, 0x6c, 0x41, + 0x64, 0x61, 0x65, 0xe8, 0x39, 0x66, 0x25, 0x44, 0x36, 0xda, 0x80, 0x22, 0x77, 0xd2, 0xf7, 0x47, + 0x23, 0x87, 0x9a, 0xbc, 0xee, 0x8b, 0xbc, 0xee, 0x4b, 0x4c, 0xde, 0xe0, 0xe2, 0x7b, 0xac, 0x03, + 0xd7, 0x20, 0x6b, 0x5b, 0xd4, 0x12, 0x90, 0x34, 0x87, 0x64, 0x98, 0x80, 0x2b, 0xdf, 0x81, 0xe5, + 0x98, 0x75, 0x44, 0x40, 0x32, 0xc2, 0xcb, 0x44, 0xcc, 0x81, 0xb7, 0x60, 0xd5, 0xc3, 0x87, 0xd4, + 0x3c, 0x8b, 0xce, 0x72, 0x34, 0x62, 0xba, 0x87, 0xd3, 0x16, 0x6f, 0xc1, 0x52, 0x3f, 0x2a, 0xbe, + 0xc0, 0x02, 0xc7, 0x16, 0x62, 0x29, 0x87, 0x5d, 0x85, 0x8c, 0x15, 0x04, 0x02, 0x90, 0xe3, 0x80, + 0xb4, 0x15, 0x04, 0x5c, 0x75, 0x13, 0x56, 0x78, 0x8e, 0x21, 0x26, 0x63, 0x97, 0x4a, 0x27, 0x79, + 0x8e, 0x59, 0x66, 0x0a, 0x43, 0xc8, 0x39, 0xf6, 0xff, 0x50, 0xc0, 0x07, 0x8e, 0x8d, 0xbd, 0x3e, + 0x16, 0xb8, 0x02, 0xc7, 0xe5, 0x23, 0x21, 0x07, 0xdd, 0x80, 0x62, 0x10, 0xfa, 0x81, 0x4f, 0x70, + 0x68, 0x5a, 0xb6, 0x1d, 0x62, 0x42, 0x4a, 0x4b, 0xc2, 0x5f, 0x24, 0xaf, 0x09, 0x71, 0xb5, 0x04, + 0xea, 0xb6, 0x45, 0x2d, 0x54, 0x84, 0x14, 0x3d, 0x24, 0x25, 0x45, 0x4b, 0x6d, 0xe4, 0x0d, 0xf6, + 0x59, 0xfd, 0x29, 0x05, 0xea, 0x43, 0x9f, 0x62, 0x74, 0x1b, 0x54, 0xd6, 0x26, 0xce, 0xbe, 0xa5, + 0xf3, 0xf8, 0xdc, 0x71, 0x06, 0x1e, 0xb6, 0xf7, 0xc8, 0xa0, 0x7b, 0x14, 0x60, 0x83, 0x83, 0x13, + 0x74, 0x9a, 0x9f, 0xa2, 0xd3, 0x2a, 0x2c, 0x84, 0xfe, 0xd8, 0xb3, 0x39, 0xcb, 0x16, 0x0c, 0xb1, + 0x40, 0x3b, 0x90, 0x89, 0x59, 0xa2, 0xfe, 0x1b, 0x4b, 0x96, 0x19, 0x4b, 0x18, 0x87, 0xa5, 0xc0, + 0x48, 0xf7, 0x24, 0x59, 0xea, 0x90, 0x8d, 0x2f, 0x2f, 0xc9, 0xb6, 0x57, 0x23, 0xec, 0xc4, 0x0c, + 0xbd, 0x0b, 0x2b, 0x71, 0xef, 0xe3, 0xe2, 0x09, 0xc6, 0x15, 0x63, 0x85, 0xac, 0xde, 0x14, 0xad, + 0x4c, 0x71, 0x01, 0xa5, 0x79, 0x5e, 0x13, 0x5a, 0x35, 0xf9, 0x4d, 0x74, 0x1d, 0xb2, 0xc4, 0x19, + 0x78, 0x16, 0x1d, 0x87, 0x58, 0x32, 0x6f, 0x22, 0x60, 0x5a, 0x7c, 0x48, 0xb1, 0xc7, 0x0f, 0xb9, + 0x60, 0xda, 0x44, 0x80, 0x36, 0xe1, 0x7f, 0xf1, 0xc2, 0x9c, 0x78, 0x11, 0x2c, 0x43, 0xb1, 0xaa, + 0x13, 0x69, 0xaa, 0x3f, 0x2b, 0xb0, 0x28, 0x0e, 0x46, 0xa2, 0x0d, 0xca, 0xf9, 0x6d, 0x98, 0xbf, + 0xa8, 0x0d, 0xa9, 0xd7, 0x6f, 0x43, 0x0d, 0x20, 0x0e, 0x93, 0x94, 0x54, 0x2d, 0xb5, 0x91, 0xdb, + 0xba, 0x36, 0xeb, 0x48, 0x84, 0xd8, 0x71, 0x06, 0xf2, 0xdc, 0x27, 0x8c, 0xaa, 0xbf, 0x2b, 0x90, + 0x8d, 0xf5, 0xa8, 0x06, 0x85, 0x28, 0x2e, 0xf3, 0x91, 0x6b, 0x0d, 0x24, 0x15, 0xd7, 0x2e, 0x0c, + 0xee, 0x8e, 0x6b, 0x0d, 0x8c, 0x9c, 0x8c, 0x87, 0x2d, 0xce, 0x6f, 0xeb, 0xfc, 0x05, 0x6d, 0x9d, + 0xe2, 0x51, 0xea, 0xf5, 0x78, 0x34, 0xd5, 0x71, 0xf5, 0x4c, 0xc7, 0xab, 0x3f, 0xce, 0x43, 0xa6, + 0xcd, 0x8f, 0xa2, 0xe5, 0xfe, 0x17, 0x07, 0xec, 0x1a, 0x64, 0x03, 0xdf, 0x35, 0x85, 0x46, 0xe5, + 0x9a, 0x4c, 0xe0, 0xbb, 0xc6, 0x4c, 0xdb, 0x17, 0xde, 0xd0, 0xe9, 0x5b, 0x7c, 0x03, 0x55, 0x4b, + 0x9f, 0xad, 0x5a, 0x08, 0x79, 0x51, 0x0a, 0x39, 0x1a, 0x6f, 0xb1, 0x1a, 0xf0, 0x59, 0xab, 0xcc, + 0x8e, 0x72, 0x11, 0xb6, 0x40, 0x1a, 0x12, 0xc7, 0x2c, 0xc4, 0x24, 0x91, 0xd3, 0xb9, 0x74, 0x11, + 0x2d, 0x0d, 0x89, 0xab, 0x7e, 0xab, 0x00, 0xec, 0xb2, 0xca, 0xf2, 0x7c, 0xd9, 0x50, 0x23, 0x3c, + 0x04, 0x73, 0x6a, 0xe7, 0xca, 0x45, 0x4d, 0x93, 0xfb, 0xe7, 0x49, 0x32, 0xee, 0x06, 0x14, 0x26, + 0x64, 0x24, 0x38, 0x0a, 0xe6, 0x1c, 0x27, 0xf1, 0xac, 0xe9, 0x60, 0x6a, 0xe4, 0x0f, 0x12, 0xab, + 0xea, 0x2f, 0x0a, 0x64, 0x79, 0x4c, 0x7b, 0x98, 0x5a, 0x53, 0x3d, 0x54, 0x5e, 0xbf, 0x87, 0x6b, + 0x00, 0xc2, 0x0d, 0x71, 0x9e, 0x60, 0xc9, 0xac, 0x2c, 0x97, 0x74, 0x9c, 0x27, 0x18, 0x7d, 0x18, + 0x17, 0x3c, 0xf5, 0xcf, 0x05, 0x97, 0x47, 0x3a, 0x2a, 0xfb, 0x15, 0x48, 0x7b, 0xe3, 0x91, 0xc9, + 0x26, 0x8c, 0x2a, 0xd8, 0xea, 0x8d, 0x47, 0xdd, 0x43, 0x52, 0xfd, 0x0a, 0xd2, 0xdd, 0x43, 0xfe, + 0xda, 0x62, 0x14, 0x0d, 0x7d, 0x5f, 0x8e, 0x78, 0xf1, 0xb4, 0xca, 0x30, 0x01, 0x9f, 0x68, 0x08, + 0x54, 0x36, 0xcb, 0xa3, 0xb7, 0x1f, 0xfb, 0x46, 0xfa, 0x2b, 0xbe, 0xe3, 0xe4, 0x0b, 0xee, 0xe6, + 0xaf, 0x0a, 0xe4, 0x12, 0xf7, 0x03, 0x7a, 0x1f, 0x2e, 0xd5, 0x77, 0xf7, 0x1b, 0xf7, 0xcd, 0xe6, + 0xb6, 0x79, 0x67, 0xb7, 0x76, 0xd7, 0x7c, 0xd0, 0xba, 0xdf, 0xda, 0xff, 0xbc, 0x55, 0x9c, 0x2b, + 0x5f, 0x3e, 0x3e, 0xd1, 0x50, 0x02, 0xfb, 0xc0, 0x7b, 0xec, 0xf9, 0x5f, 0xb3, 0xab, 0x78, 0x75, + 0xda, 0xa4, 0x56, 0xef, 0xec, 0xb4, 0xba, 0x45, 0xa5, 0x7c, 0xe9, 0xf8, 0x44, 0x5b, 0x49, 0x58, + 0xd4, 0x7a, 0x04, 0x7b, 0x74, 0xd6, 0xa0, 0xb1, 0xbf, 0xb7, 0xd7, 0xec, 0x16, 0xe7, 0x67, 0x0c, + 0xe4, 0x85, 0x7d, 0x03, 0x56, 0xa6, 0x0d, 0x5a, 0xcd, 0xdd, 0x62, 0xaa, 0x8c, 0x8e, 0x4f, 0xb4, + 0xa5, 0x04, 0xba, 0xe5, 0xb8, 0xe5, 0xcc, 0x37, 0xdf, 0x55, 0xe6, 0x7e, 0xf8, 0xbe, 0xa2, 0xb0, + 0xcc, 0x0a, 0x53, 0x77, 0x04, 0x7a, 0x0f, 0xae, 0x74, 0x9a, 0x77, 0x5b, 0x3b, 0xdb, 0xe6, 0x5e, + 0xe7, 0xae, 0xd9, 0xfd, 0xa2, 0xbd, 0x93, 0xc8, 0x6e, 0xf9, 0xf8, 0x44, 0xcb, 0xc9, 0x94, 0x2e, + 0x42, 0xb7, 0x8d, 0x9d, 0x87, 0xfb, 0xdd, 0x9d, 0xa2, 0x22, 0xd0, 0xed, 0x10, 0x1f, 0xf8, 0x14, + 0x73, 0xf4, 0x2d, 0xb8, 0x7a, 0x0e, 0x3a, 0x4e, 0x6c, 0xe5, 0xf8, 0x44, 0x2b, 0xb4, 0x43, 0x2c, + 0xce, 0x0f, 0xb7, 0xd0, 0xa1, 0x34, 0x6b, 0xb1, 0xdf, 0xde, 0xef, 0xd4, 0x76, 0x8b, 0x5a, 0xb9, + 0x78, 0x7c, 0xa2, 0xe5, 0xa3, 0xcb, 0x90, 0xe1, 0x27, 0x99, 0xd5, 0x3f, 0x7b, 0x76, 0x5a, 0x51, + 0x9e, 0x9f, 0x56, 0x94, 0x3f, 0x4f, 0x2b, 0xca, 0xd3, 0x97, 0x95, 0xb9, 0xe7, 0x2f, 0x2b, 0x73, + 0xbf, 0xbd, 0xac, 0xcc, 0x7d, 0xf9, 0xd1, 0xc0, 0xa1, 0xc3, 0x71, 0x4f, 0xef, 0xfb, 0xa3, 0xcd, + 0xe4, 0x3f, 0x8c, 0xc9, 0xa7, 0xf8, 0xa7, 0x73, 0xf6, 0xdf, 0x47, 0x6f, 0x91, 0xcb, 0x6f, 0xff, + 0x1d, 0x00, 0x00, 0xff, 0xff, 0xbb, 0xc0, 0x81, 0x37, 0x3e, 0x0d, 0x00, 0x00, } func (m *PartSetHeader) Marshal() (dAtA []byte, err error) { @@ -1558,15 +1458,17 @@ func (m *Vote) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.VoteExtension != nil { - { - size, err := m.VoteExtension.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) - } + if len(m.ExtensionSignature) > 0 { + i -= len(m.ExtensionSignature) + copy(dAtA[i:], m.ExtensionSignature) + i = encodeVarintTypes(dAtA, i, uint64(len(m.ExtensionSignature))) + i-- + dAtA[i] = 0x52 + } + if len(m.Extension) > 0 { + i -= len(m.Extension) + copy(dAtA[i:], m.Extension) + i = encodeVarintTypes(dAtA, i, uint64(len(m.Extension))) i-- dAtA[i] = 0x4a } @@ -1589,12 +1491,12 @@ func (m *Vote) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x32 } - n7, err7 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) - if err7 != nil { - return 0, err7 + n6, err6 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) + if err6 != nil { + return 0, err6 } - i -= n7 - i = encodeVarintTypes(dAtA, i, uint64(n7)) + i -= n6 + i = encodeVarintTypes(dAtA, i, uint64(n6)) i-- dAtA[i] = 0x2a { @@ -1625,73 +1527,6 @@ func (m *Vote) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *VoteExtension) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *VoteExtension) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *VoteExtension) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.AppDataSelfAuthenticating) > 0 { - i -= len(m.AppDataSelfAuthenticating) - copy(dAtA[i:], m.AppDataSelfAuthenticating) - i = encodeVarintTypes(dAtA, i, uint64(len(m.AppDataSelfAuthenticating))) - i-- - dAtA[i] = 0x12 - } - if len(m.AppDataToSign) > 0 { - i -= len(m.AppDataToSign) - copy(dAtA[i:], m.AppDataToSign) - i = encodeVarintTypes(dAtA, i, uint64(len(m.AppDataToSign))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *VoteExtensionToSign) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *VoteExtensionToSign) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *VoteExtensionToSign) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.AppDataToSign) > 0 { - i -= len(m.AppDataToSign) - copy(dAtA[i:], m.AppDataToSign) - i = encodeVarintTypes(dAtA, i, uint64(len(m.AppDataToSign))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - func (m *Commit) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1769,18 +1604,6 @@ func (m *CommitSig) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.VoteExtension != nil { - { - size, err := m.VoteExtension.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x2a - } if len(m.Signature) > 0 { i -= len(m.Signature) copy(dAtA[i:], m.Signature) @@ -1788,12 +1611,12 @@ func (m *CommitSig) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x22 } - n11, err11 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) - if err11 != nil { - return 0, err11 + n9, err9 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) + if err9 != nil { + return 0, err9 } - i -= n11 - i = encodeVarintTypes(dAtA, i, uint64(n11)) + i -= n9 + i = encodeVarintTypes(dAtA, i, uint64(n9)) i-- dAtA[i] = 0x1a if len(m.ValidatorAddress) > 0 { @@ -1838,12 +1661,12 @@ func (m *Proposal) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x3a } - n12, err12 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) - if err12 != nil { - return 0, err12 + n10, err10 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) + if err10 != nil { + return 0, err10 } - i -= n12 - i = encodeVarintTypes(dAtA, i, uint64(n12)) + i -= n10 + i = encodeVarintTypes(dAtA, i, uint64(n10)) i-- dAtA[i] = 0x32 { @@ -2238,37 +2061,11 @@ func (m *Vote) Size() (n int) { if l > 0 { n += 1 + l + sovTypes(uint64(l)) } - if m.VoteExtension != nil { - l = m.VoteExtension.Size() - n += 1 + l + sovTypes(uint64(l)) - } - return n -} - -func (m *VoteExtension) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.AppDataToSign) + l = len(m.Extension) if l > 0 { n += 1 + l + sovTypes(uint64(l)) } - l = len(m.AppDataSelfAuthenticating) - if l > 0 { - n += 1 + l + sovTypes(uint64(l)) - } - return n -} - -func (m *VoteExtensionToSign) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.AppDataToSign) + l = len(m.ExtensionSignature) if l > 0 { n += 1 + l + sovTypes(uint64(l)) } @@ -2317,10 +2114,6 @@ func (m *CommitSig) Size() (n int) { if l > 0 { n += 1 + l + sovTypes(uint64(l)) } - if m.VoteExtension != nil { - l = m.VoteExtension.Size() - n += 1 + l + sovTypes(uint64(l)) - } return n } @@ -3618,93 +3411,7 @@ func (m *Vote) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 9: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field VoteExtension", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.VoteExtension == nil { - m.VoteExtension = &VoteExtension{} - } - if err := m.VoteExtension.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTypes(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTypes - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *VoteExtension) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: VoteExtension: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: VoteExtension: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppDataToSign", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Extension", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -3731,14 +3438,14 @@ func (m *VoteExtension) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppDataToSign = append(m.AppDataToSign[:0], dAtA[iNdEx:postIndex]...) - if m.AppDataToSign == nil { - m.AppDataToSign = []byte{} + m.Extension = append(m.Extension[:0], dAtA[iNdEx:postIndex]...) + if m.Extension == nil { + m.Extension = []byte{} } iNdEx = postIndex - case 2: + case 10: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppDataSelfAuthenticating", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ExtensionSignature", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -3765,93 +3472,9 @@ func (m *VoteExtension) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppDataSelfAuthenticating = append(m.AppDataSelfAuthenticating[:0], dAtA[iNdEx:postIndex]...) - if m.AppDataSelfAuthenticating == nil { - m.AppDataSelfAuthenticating = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTypes(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTypes - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *VoteExtensionToSign) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: VoteExtensionToSign: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: VoteExtensionToSign: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppDataToSign", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppDataToSign = append(m.AppDataToSign[:0], dAtA[iNdEx:postIndex]...) - if m.AppDataToSign == nil { - m.AppDataToSign = []byte{} + m.ExtensionSignature = append(m.ExtensionSignature[:0], dAtA[iNdEx:postIndex]...) + if m.ExtensionSignature == nil { + m.ExtensionSignature = []byte{} } iNdEx = postIndex default: @@ -4179,42 +3802,6 @@ func (m *CommitSig) Unmarshal(dAtA []byte) error { m.Signature = []byte{} } iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field VoteExtension", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.VoteExtension == nil { - m.VoteExtension = &VoteExtensionToSign{} - } - if err := m.VoteExtension.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) diff --git a/proto/tendermint/types/types.proto b/proto/tendermint/types/types.proto index ef448d9ca..e2b8a46c8 100644 --- a/proto/tendermint/types/types.proto +++ b/proto/tendermint/types/types.proto @@ -1,7 +1,7 @@ syntax = "proto3"; package tendermint.types; -option go_package = "github.com/tendermint/tendermint/types"; +option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; import "gogoproto/gogo.proto"; import "google/protobuf/timestamp.proto"; @@ -9,7 +9,7 @@ import "tendermint/crypto/proof.proto"; import "tendermint/version/types.proto"; import "tendermint/types/validator.proto"; -// BlockIdFlag indicates which BlcokID the signature is for +// BlockIdFlag indicates which BlockID the signature is for enum BlockIDFlag { option (gogoproto.goproto_enum_stringer) = true; option (gogoproto.goproto_enum_prefix) = false; @@ -112,7 +112,15 @@ message Vote { [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; bytes validator_address = 6; int32 validator_index = 7; - bytes signature = 8; + // Vote signature by the validator if they participated in consensus for the + // associated block. + bytes signature = 8; + // Vote extension provided by the application. Only valid for precommit + // messages. + bytes extension = 9; + // Vote extension signature by the validator if they participated in + // consensus for the associated block. Only valid for precommit messages. + bytes extension_signature = 10; } // Commit contains the evidence that a block was committed by a set of diff --git a/proto/tendermint/types/types.proto.intermediate b/proto/tendermint/types/types.proto.intermediate deleted file mode 100644 index 280cd7133..000000000 --- a/proto/tendermint/types/types.proto.intermediate +++ /dev/null @@ -1,192 +0,0 @@ -syntax = "proto3"; -package tendermint.types; - -import "gogoproto/gogo.proto"; -import "google/protobuf/timestamp.proto"; -import "tendermint/crypto/proof.proto"; -import "tendermint/version/types.proto"; -import "tendermint/types/validator.proto"; - -// This file is a temporary workaround to enable development during the ABCI++ -// project. This file should be deleted and any references to it removed when -// the ongoing work on ABCI++ is completed. -// -// This file supports building of the `tendermint.abci` proto package. -// For more information, see https://github.com/tendermint/tendermint/issues/8066 - -// BlockIdFlag indicates which BlockID the signature is for -enum BlockIDFlag { - option (gogoproto.goproto_enum_stringer) = true; - option (gogoproto.goproto_enum_prefix) = false; - - BLOCK_ID_FLAG_UNKNOWN = 0 - [(gogoproto.enumvalue_customname) = "BlockIDFlagUnknown"]; - BLOCK_ID_FLAG_ABSENT = 1 - [(gogoproto.enumvalue_customname) = "BlockIDFlagAbsent"]; - BLOCK_ID_FLAG_COMMIT = 2 - [(gogoproto.enumvalue_customname) = "BlockIDFlagCommit"]; - BLOCK_ID_FLAG_NIL = 3 [(gogoproto.enumvalue_customname) = "BlockIDFlagNil"]; -} - -// SignedMsgType is a type of signed message in the consensus. -enum SignedMsgType { - option (gogoproto.goproto_enum_stringer) = true; - option (gogoproto.goproto_enum_prefix) = false; - - SIGNED_MSG_TYPE_UNKNOWN = 0 - [(gogoproto.enumvalue_customname) = "UnknownType"]; - // Votes - SIGNED_MSG_TYPE_PREVOTE = 1 - [(gogoproto.enumvalue_customname) = "PrevoteType"]; - SIGNED_MSG_TYPE_PRECOMMIT = 2 - [(gogoproto.enumvalue_customname) = "PrecommitType"]; - - // Proposals - SIGNED_MSG_TYPE_PROPOSAL = 32 - [(gogoproto.enumvalue_customname) = "ProposalType"]; -} - -// PartsetHeader -message PartSetHeader { - uint32 total = 1; - bytes hash = 2; -} - -message Part { - uint32 index = 1; - bytes bytes = 2; - tendermint.crypto.Proof proof = 3 [(gogoproto.nullable) = false]; -} - -// BlockID -message BlockID { - bytes hash = 1; - PartSetHeader part_set_header = 2 [(gogoproto.nullable) = false]; -} - -// -------------------------------- - -// Header defines the structure of a Tendermint block header. -message Header { - // basic block info - tendermint.version.Consensus version = 1 [(gogoproto.nullable) = false]; - string chain_id = 2 [(gogoproto.customname) = "ChainID"]; - int64 height = 3; - google.protobuf.Timestamp time = 4 - [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; - - // prev block info - BlockID last_block_id = 5 [(gogoproto.nullable) = false]; - - // hashes of block data - bytes last_commit_hash = 6; // commit from validators from the last block - bytes data_hash = 7; // transactions - - // hashes from the app output from the prev block - bytes validators_hash = 8; // validators for the current block - bytes next_validators_hash = 9; // validators for the next block - bytes consensus_hash = 10; // consensus params for current block - bytes app_hash = 11; // state after txs from the previous block - bytes last_results_hash = - 12; // root hash of all results from the txs from the previous block - - // consensus info - bytes evidence_hash = 13; // evidence included in the block - bytes proposer_address = 14; // original proposer of the block -} - -// Data contains the set of transactions included in the block -message Data { - // Txs that will be applied by state @ block.Height+1. - // NOTE: not all txs here are valid. We're just agreeing on the order first. - // This means that block.AppHash does not include these txs. - repeated bytes txs = 1; -} - -// Vote represents a prevote, precommit, or commit vote from validators for -// consensus. -message Vote { - SignedMsgType type = 1; - int64 height = 2; - int32 round = 3; - BlockID block_id = 4 [ - (gogoproto.nullable) = false, - (gogoproto.customname) = "BlockID" - ]; // zero if vote is nil. - google.protobuf.Timestamp timestamp = 5 - [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; - bytes validator_address = 6; - int32 validator_index = 7; - bytes signature = 8; - VoteExtension vote_extension = 9; -} - -// VoteExtension is app-defined additional information to the validator votes. -message VoteExtension { - bytes app_data_to_sign = 1; - bytes app_data_self_authenticating = 2; -} - -// VoteExtensionToSign is a subset of VoteExtension that is signed by the validators private key. -// VoteExtensionToSign is extracted from an existing VoteExtension. -message VoteExtensionToSign { - bytes app_data_to_sign = 1; -} - -// Commit contains the evidence that a block was committed by a set of -// validators. -message Commit { - int64 height = 1; - int32 round = 2; - BlockID block_id = 3 - [(gogoproto.nullable) = false, (gogoproto.customname) = "BlockID"]; - repeated CommitSig signatures = 4 [(gogoproto.nullable) = false]; -} - -// CommitSig is a part of the Vote included in a Commit. -message CommitSig { - BlockIDFlag block_id_flag = 1; - bytes validator_address = 2; - google.protobuf.Timestamp timestamp = 3 - [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; - bytes signature = 4; - VoteExtensionToSign vote_extension = 5; -} - -message Proposal { - SignedMsgType type = 1; - int64 height = 2; - int32 round = 3; - int32 pol_round = 4; - BlockID block_id = 5 - [(gogoproto.customname) = "BlockID", (gogoproto.nullable) = false]; - google.protobuf.Timestamp timestamp = 6 - [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; - bytes signature = 7; -} - -message SignedHeader { - Header header = 1; - Commit commit = 2; -} - -message LightBlock { - SignedHeader signed_header = 1; - tendermint.types.ValidatorSet validator_set = 2; -} - -message BlockMeta { - BlockID block_id = 1 - [(gogoproto.customname) = "BlockID", (gogoproto.nullable) = false]; - int64 block_size = 2; - Header header = 3 [(gogoproto.nullable) = false]; - int64 num_txs = 4; -} - -// TxProof represents a Merkle proof of the presence of a transaction in the -// Merkle tree. -message TxProof { - bytes root_hash = 1; - bytes data = 2; - tendermint.crypto.Proof proof = 3; -} diff --git a/proto/tendermint/types/validator.proto b/proto/tendermint/types/validator.proto index 4ab5e4c32..49860b96d 100644 --- a/proto/tendermint/types/validator.proto +++ b/proto/tendermint/types/validator.proto @@ -1,6 +1,8 @@ syntax = "proto3"; package tendermint.types; +option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; + import "gogoproto/gogo.proto"; import "tendermint/crypto/keys.proto"; diff --git a/proto/tendermint/version/types.proto b/proto/tendermint/version/types.proto index d7d4cc09f..37124dd4e 100644 --- a/proto/tendermint/version/types.proto +++ b/proto/tendermint/version/types.proto @@ -1,6 +1,8 @@ syntax = "proto3"; package tendermint.version; +option go_package = "github.com/tendermint/tendermint/proto/tendermint/version"; + import "gogoproto/gogo.proto"; // Consensus captures the consensus rules for processing a block in the diff --git a/rpc/client/eventstream/eventstream.go b/rpc/client/eventstream/eventstream.go index 887e723ce..59cfc8b5f 100644 --- a/rpc/client/eventstream/eventstream.go +++ b/rpc/client/eventstream/eventstream.go @@ -189,6 +189,5 @@ type MissedItemsError struct { // Error satisfies the error interface. func (e *MissedItemsError) Error() string { - return fmt.Sprintf("missed events matching %q between %q and %q", - e.Query, e.NewestSeen, e.OldestPresent) + return fmt.Sprintf("missed events matching %q between %q and %q", e.Query, e.NewestSeen, e.OldestPresent) } diff --git a/rpc/client/eventstream/eventstream_test.go b/rpc/client/eventstream/eventstream_test.go index ca27734e2..8cd9df30f 100644 --- a/rpc/client/eventstream/eventstream_test.go +++ b/rpc/client/eventstream/eventstream_test.go @@ -103,13 +103,16 @@ func TestMinPollTime(t *testing.T) { // wait time and reports no events. ctx := context.Background() filter := &coretypes.EventFilter{Query: `tm.event = 'good'`} - var zero cursor.Cursor t.Run("NoneMatch", func(t *testing.T) { start := time.Now() // Request a very short delay, and affirm we got the server's minimum. - rsp, err := s.env.Events(ctx, filter, 1, zero, zero, 10*time.Millisecond) + rsp, err := s.env.Events(ctx, &coretypes.RequestEvents{ + Filter: filter, + MaxItems: 1, + WaitTime: 10 * time.Millisecond, + }) if err != nil { t.Fatalf("Events failed: %v", err) } else if elapsed := time.Since(start); elapsed < time.Second { @@ -128,7 +131,11 @@ func TestMinPollTime(t *testing.T) { // Request a long-ish delay and affirm we don't block for it. // Check for this by ensuring we return sooner than the minimum delay, // since we don't know the exact timing. - rsp, err := s.env.Events(ctx, filter, 1, zero, zero, 10*time.Second) + rsp, err := s.env.Events(ctx, &coretypes.RequestEvents{ + Filter: filter, + MaxItems: 1, + WaitTime: 10 * time.Second, + }) if err != nil { t.Fatalf("Events failed: %v", err) } else if elapsed := time.Since(start); elapsed > 500*time.Millisecond { @@ -263,12 +270,5 @@ func (s *streamTester) advance(d time.Duration) { s.clock += int64(d) } // environment as if it were a local RPC client. This works because the Events // method only requires the event log, the other fields are unused. func (s *streamTester) Events(ctx context.Context, req *coretypes.RequestEvents) (*coretypes.ResultEvents, error) { - var before, after cursor.Cursor - if err := before.UnmarshalText([]byte(req.Before)); err != nil { - return nil, err - } - if err := after.UnmarshalText([]byte(req.After)); err != nil { - return nil, err - } - return s.env.Events(ctx, req.Filter, req.MaxItems, before, after, req.WaitTime) + return s.env.Events(ctx, req) } diff --git a/rpc/client/evidence_test.go b/rpc/client/evidence_test.go index 9187ddc1a..0096d0924 100644 --- a/rpc/client/evidence_test.go +++ b/rpc/client/evidence_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" tmrand "github.com/tendermint/tendermint/libs/rand" "github.com/tendermint/tendermint/privval" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -54,16 +54,16 @@ func makeEvidences( Type: tmproto.PrevoteType, Timestamp: timestamp, BlockID: types.BlockID{ - Hash: tmhash.Sum(tmrand.Bytes(tmhash.Size)), + Hash: crypto.Checksum(tmrand.Bytes(crypto.HashSize)), PartSetHeader: types.PartSetHeader{ Total: 1000, - Hash: tmhash.Sum([]byte("partset")), + Hash: crypto.Checksum([]byte("partset")), }, }, } vote2 := vote - vote2.BlockID.Hash = tmhash.Sum([]byte("blockhash2")) + vote2.BlockID.Hash = crypto.Checksum([]byte("blockhash2")) correct = newEvidence(t, val, &vote, &vote2, chainID, timestamp) fakes = make([]*types.DuplicateVoteEvidence, 0) diff --git a/rpc/client/http/http.go b/rpc/client/http/http.go index 2a9507f8e..50d78d279 100644 --- a/rpc/client/http/http.go +++ b/rpc/client/http/http.go @@ -207,24 +207,16 @@ func (c *baseRPCClient) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo return result, nil } -func (c *baseRPCClient) ABCIQuery( - ctx context.Context, - path string, - data bytes.HexBytes, -) (*coretypes.ResultABCIQuery, error) { +func (c *baseRPCClient) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes) (*coretypes.ResultABCIQuery, error) { return c.ABCIQueryWithOptions(ctx, path, data, rpcclient.DefaultABCIQueryOptions) } -func (c *baseRPCClient) ABCIQueryWithOptions( - ctx context.Context, - path string, - data bytes.HexBytes, - opts rpcclient.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { +func (c *baseRPCClient) ABCIQueryWithOptions(ctx context.Context, path string, data bytes.HexBytes, opts rpcclient.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { result := new(coretypes.ResultABCIQuery) - if err := c.caller.Call(ctx, "abci_query", abciQueryArgs{ + if err := c.caller.Call(ctx, "abci_query", &coretypes.RequestABCIQuery{ Path: path, Data: data, - Height: opts.Height, + Height: coretypes.Int64(opts.Height), Prove: opts.Prove, }, result); err != nil { return nil, err @@ -232,51 +224,39 @@ func (c *baseRPCClient) ABCIQueryWithOptions( return result, nil } -func (c *baseRPCClient) BroadcastTxCommit( - ctx context.Context, - tx types.Tx, -) (*coretypes.ResultBroadcastTxCommit, error) { +func (c *baseRPCClient) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) { result := new(coretypes.ResultBroadcastTxCommit) - if err := c.caller.Call(ctx, "broadcast_tx_commit", txArgs{Tx: tx}, result); err != nil { + if err := c.caller.Call(ctx, "broadcast_tx_commit", &coretypes.RequestBroadcastTx{ + Tx: tx, + }, result); err != nil { return nil, err } return result, nil } -func (c *baseRPCClient) BroadcastTxAsync( - ctx context.Context, - tx types.Tx, -) (*coretypes.ResultBroadcastTx, error) { +func (c *baseRPCClient) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { return c.broadcastTX(ctx, "broadcast_tx_async", tx) } -func (c *baseRPCClient) BroadcastTxSync( - ctx context.Context, - tx types.Tx, -) (*coretypes.ResultBroadcastTx, error) { +func (c *baseRPCClient) BroadcastTxSync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { return c.broadcastTX(ctx, "broadcast_tx_sync", tx) } -func (c *baseRPCClient) broadcastTX( - ctx context.Context, - route string, - tx types.Tx, -) (*coretypes.ResultBroadcastTx, error) { +func (c *baseRPCClient) broadcastTX(ctx context.Context, route string, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { result := new(coretypes.ResultBroadcastTx) - if err := c.caller.Call(ctx, route, txArgs{Tx: tx}, result); err != nil { + if err := c.caller.Call(ctx, route, &coretypes.RequestBroadcastTx{Tx: tx}, result); err != nil { return nil, err } return result, nil } -func (c *baseRPCClient) UnconfirmedTxs( - ctx context.Context, - page *int, - perPage *int, -) (*coretypes.ResultUnconfirmedTxs, error) { +func (c *baseRPCClient) UnconfirmedTxs(ctx context.Context, page *int, perPage *int) (*coretypes.ResultUnconfirmedTxs, error) { result := new(coretypes.ResultUnconfirmedTxs) - if err := c.caller.Call(ctx, "unconfirmed_txs", unconfirmedArgs{Page: page, PerPage: perPage}, result); err != nil { + if err := c.caller.Call(ctx, "unconfirmed_txs", &coretypes.RequestUnconfirmedTxs{ + Page: coretypes.Int64Ptr(page), + PerPage: coretypes.Int64Ptr(perPage), + }, result); err != nil { return nil, err } return result, nil @@ -292,14 +272,14 @@ func (c *baseRPCClient) NumUnconfirmedTxs(ctx context.Context) (*coretypes.Resul func (c *baseRPCClient) CheckTx(ctx context.Context, tx types.Tx) (*coretypes.ResultCheckTx, error) { result := new(coretypes.ResultCheckTx) - if err := c.caller.Call(ctx, "check_tx", txArgs{Tx: tx}, result); err != nil { + if err := c.caller.Call(ctx, "check_tx", &coretypes.RequestCheckTx{Tx: tx}, result); err != nil { return nil, err } return result, nil } func (c *baseRPCClient) RemoveTx(ctx context.Context, txKey types.TxKey) error { - if err := c.caller.Call(ctx, "remove_tx", txKeyArgs{TxKey: txKey[:]}, nil); err != nil { + if err := c.caller.Call(ctx, "remove_tx", &coretypes.RequestRemoveTx{TxKey: txKey}, nil); err != nil { return err } return nil @@ -329,12 +309,11 @@ func (c *baseRPCClient) ConsensusState(ctx context.Context) (*coretypes.ResultCo return result, nil } -func (c *baseRPCClient) ConsensusParams( - ctx context.Context, - height *int64, -) (*coretypes.ResultConsensusParams, error) { +func (c *baseRPCClient) ConsensusParams(ctx context.Context, height *int64) (*coretypes.ResultConsensusParams, error) { result := new(coretypes.ResultConsensusParams) - if err := c.caller.Call(ctx, "consensus_params", heightArgs{Height: height}, result); err != nil { + if err := c.caller.Call(ctx, "consensus_params", &coretypes.RequestConsensusParams{ + Height: (*coretypes.Int64)(height), + }, result); err != nil { return nil, err } return result, nil @@ -356,15 +335,11 @@ func (c *baseRPCClient) Health(ctx context.Context) (*coretypes.ResultHealth, er return result, nil } -func (c *baseRPCClient) BlockchainInfo( - ctx context.Context, - minHeight, - maxHeight int64, -) (*coretypes.ResultBlockchainInfo, error) { +func (c *baseRPCClient) BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) { result := new(coretypes.ResultBlockchainInfo) - if err := c.caller.Call(ctx, "blockchain", blockchainInfoArgs{ - MinHeight: minHeight, - MaxHeight: maxHeight, + if err := c.caller.Call(ctx, "blockchain", &coretypes.RequestBlockchainInfo{ + MinHeight: coretypes.Int64(minHeight), + MaxHeight: coretypes.Int64(maxHeight), }, result); err != nil { return nil, err } @@ -381,7 +356,9 @@ func (c *baseRPCClient) Genesis(ctx context.Context) (*coretypes.ResultGenesis, func (c *baseRPCClient) GenesisChunked(ctx context.Context, id uint) (*coretypes.ResultGenesisChunk, error) { result := new(coretypes.ResultGenesisChunk) - if err := c.caller.Call(ctx, "genesis_chunked", genesisChunkArgs{Chunk: id}, result); err != nil { + if err := c.caller.Call(ctx, "genesis_chunked", &coretypes.RequestGenesisChunked{ + Chunk: coretypes.Int64(id), + }, result); err != nil { return nil, err } return result, nil @@ -389,7 +366,9 @@ func (c *baseRPCClient) GenesisChunked(ctx context.Context, id uint) (*coretypes func (c *baseRPCClient) Block(ctx context.Context, height *int64) (*coretypes.ResultBlock, error) { result := new(coretypes.ResultBlock) - if err := c.caller.Call(ctx, "block", heightArgs{Height: height}, result); err != nil { + if err := c.caller.Call(ctx, "block", &coretypes.RequestBlockInfo{ + Height: (*coretypes.Int64)(height), + }, result); err != nil { return nil, err } return result, nil @@ -397,18 +376,17 @@ func (c *baseRPCClient) Block(ctx context.Context, height *int64) (*coretypes.Re func (c *baseRPCClient) BlockByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultBlock, error) { result := new(coretypes.ResultBlock) - if err := c.caller.Call(ctx, "block_by_hash", hashArgs{Hash: hash}, result); err != nil { + if err := c.caller.Call(ctx, "block_by_hash", &coretypes.RequestBlockByHash{Hash: hash}, result); err != nil { return nil, err } return result, nil } -func (c *baseRPCClient) BlockResults( - ctx context.Context, - height *int64, -) (*coretypes.ResultBlockResults, error) { +func (c *baseRPCClient) BlockResults(ctx context.Context, height *int64) (*coretypes.ResultBlockResults, error) { result := new(coretypes.ResultBlockResults) - if err := c.caller.Call(ctx, "block_results", heightArgs{Height: height}, result); err != nil { + if err := c.caller.Call(ctx, "block_results", &coretypes.RequestBlockInfo{ + Height: (*coretypes.Int64)(height), + }, result); err != nil { return nil, err } return result, nil @@ -416,7 +394,9 @@ func (c *baseRPCClient) BlockResults( func (c *baseRPCClient) Header(ctx context.Context, height *int64) (*coretypes.ResultHeader, error) { result := new(coretypes.ResultHeader) - if err := c.caller.Call(ctx, "header", heightArgs{Height: height}, result); err != nil { + if err := c.caller.Call(ctx, "header", &coretypes.RequestBlockInfo{ + Height: (*coretypes.Int64)(height), + }, result); err != nil { return nil, err } return result, nil @@ -424,7 +404,9 @@ func (c *baseRPCClient) Header(ctx context.Context, height *int64) (*coretypes.R func (c *baseRPCClient) HeaderByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultHeader, error) { result := new(coretypes.ResultHeader) - if err := c.caller.Call(ctx, "header_by_hash", hashArgs{Hash: hash}, result); err != nil { + if err := c.caller.Call(ctx, "header_by_hash", &coretypes.RequestBlockByHash{ + Hash: hash, + }, result); err != nil { return nil, err } return result, nil @@ -432,7 +414,9 @@ func (c *baseRPCClient) HeaderByHash(ctx context.Context, hash bytes.HexBytes) ( func (c *baseRPCClient) Commit(ctx context.Context, height *int64) (*coretypes.ResultCommit, error) { result := new(coretypes.ResultCommit) - if err := c.caller.Call(ctx, "commit", heightArgs{Height: height}, result); err != nil { + if err := c.caller.Call(ctx, "commit", &coretypes.RequestBlockInfo{ + Height: (*coretypes.Int64)(height), + }, result); err != nil { return nil, err } return result, nil @@ -440,27 +424,20 @@ func (c *baseRPCClient) Commit(ctx context.Context, height *int64) (*coretypes.R func (c *baseRPCClient) Tx(ctx context.Context, hash bytes.HexBytes, prove bool) (*coretypes.ResultTx, error) { result := new(coretypes.ResultTx) - if err := c.caller.Call(ctx, "tx", hashArgs{Hash: hash, Prove: prove}, result); err != nil { + if err := c.caller.Call(ctx, "tx", &coretypes.RequestTx{Hash: hash, Prove: prove}, result); err != nil { return nil, err } return result, nil } -func (c *baseRPCClient) TxSearch( - ctx context.Context, - query string, - prove bool, - page, - perPage *int, - orderBy string, -) (*coretypes.ResultTxSearch, error) { +func (c *baseRPCClient) TxSearch(ctx context.Context, query string, prove bool, page, perPage *int, orderBy string) (*coretypes.ResultTxSearch, error) { result := new(coretypes.ResultTxSearch) - if err := c.caller.Call(ctx, "tx_search", searchArgs{ + if err := c.caller.Call(ctx, "tx_search", &coretypes.RequestTxSearch{ Query: query, Prove: prove, OrderBy: orderBy, - Page: page, - PerPage: perPage, + Page: coretypes.Int64Ptr(page), + PerPage: coretypes.Int64Ptr(perPage), }, result); err != nil { return nil, err } @@ -468,18 +445,13 @@ func (c *baseRPCClient) TxSearch( return result, nil } -func (c *baseRPCClient) BlockSearch( - ctx context.Context, - query string, - page, perPage *int, - orderBy string, -) (*coretypes.ResultBlockSearch, error) { +func (c *baseRPCClient) BlockSearch(ctx context.Context, query string, page, perPage *int, orderBy string) (*coretypes.ResultBlockSearch, error) { result := new(coretypes.ResultBlockSearch) - if err := c.caller.Call(ctx, "block_search", searchArgs{ + if err := c.caller.Call(ctx, "block_search", &coretypes.RequestBlockSearch{ Query: query, OrderBy: orderBy, - Page: page, - PerPage: perPage, + Page: coretypes.Int64Ptr(page), + PerPage: coretypes.Int64Ptr(perPage), }, result); err != nil { return nil, err } @@ -487,30 +459,22 @@ func (c *baseRPCClient) BlockSearch( return result, nil } -func (c *baseRPCClient) Validators( - ctx context.Context, - height *int64, - page, - perPage *int, -) (*coretypes.ResultValidators, error) { +func (c *baseRPCClient) Validators(ctx context.Context, height *int64, page, perPage *int) (*coretypes.ResultValidators, error) { result := new(coretypes.ResultValidators) - if err := c.caller.Call(ctx, "validators", validatorArgs{ - Height: height, - Page: page, - PerPage: perPage, + if err := c.caller.Call(ctx, "validators", &coretypes.RequestValidators{ + Height: (*coretypes.Int64)(height), + Page: coretypes.Int64Ptr(page), + PerPage: coretypes.Int64Ptr(perPage), }, result); err != nil { return nil, err } return result, nil } -func (c *baseRPCClient) BroadcastEvidence( - ctx context.Context, - ev types.Evidence, -) (*coretypes.ResultBroadcastEvidence, error) { +func (c *baseRPCClient) BroadcastEvidence(ctx context.Context, ev types.Evidence) (*coretypes.ResultBroadcastEvidence, error) { result := new(coretypes.ResultBroadcastEvidence) - if err := c.caller.Call(ctx, "broadcast_evidence", evidenceArgs{ - Evidence: coretypes.Evidence{Value: ev}, + if err := c.caller.Call(ctx, "broadcast_evidence", &coretypes.RequestBroadcastEvidence{ + Evidence: ev, }, result); err != nil { return nil, err } diff --git a/rpc/client/http/request.go b/rpc/client/http/request.go deleted file mode 100644 index 746cb776d..000000000 --- a/rpc/client/http/request.go +++ /dev/null @@ -1,65 +0,0 @@ -package http - -// The types in this file define the JSON encoding for RPC method parameters -// from the client to the server. - -import ( - "github.com/tendermint/tendermint/libs/bytes" - "github.com/tendermint/tendermint/rpc/coretypes" -) - -type abciQueryArgs struct { - Path string `json:"path"` - Data bytes.HexBytes `json:"data"` - Height int64 `json:"height,string"` - Prove bool `json:"prove"` -} - -type txArgs struct { - Tx []byte `json:"tx"` -} - -type txKeyArgs struct { - TxKey []byte `json:"tx_key"` -} - -type unconfirmedArgs struct { - Page *int `json:"page,string,omitempty"` - PerPage *int `json:"per_page,string,omitempty"` -} - -type heightArgs struct { - Height *int64 `json:"height,string,omitempty"` -} - -type hashArgs struct { - Hash bytes.HexBytes `json:"hash"` - Prove bool `json:"prove,omitempty"` -} - -type blockchainInfoArgs struct { - MinHeight int64 `json:"minHeight,string"` - MaxHeight int64 `json:"maxHeight,string"` -} - -type genesisChunkArgs struct { - Chunk uint `json:"chunk,string"` -} - -type searchArgs struct { - Query string `json:"query"` - Prove bool `json:"prove,omitempty"` - OrderBy string `json:"order_by,omitempty"` - Page *int `json:"page,string,omitempty"` - PerPage *int `json:"per_page,string,omitempty"` -} - -type validatorArgs struct { - Height *int64 `json:"height,string,omitempty"` - Page *int `json:"page,string,omitempty"` - PerPage *int `json:"per_page,string,omitempty"` -} - -type evidenceArgs struct { - Evidence coretypes.Evidence `json:"evidence"` -} diff --git a/rpc/client/http/ws.go b/rpc/client/http/ws.go index 2f188a24d..e9a5ac829 100644 --- a/rpc/client/http/ws.go +++ b/rpc/client/http/ws.go @@ -107,6 +107,7 @@ func (w *wsEvents) Unsubscribe(ctx context.Context, subscriber, query string) er } w.mtx.Lock() + defer w.mtx.Unlock() info, ok := w.subscriptions[query] if ok { if info.id != "" { @@ -114,7 +115,6 @@ func (w *wsEvents) Unsubscribe(ctx context.Context, subscriber, query string) er } delete(w.subscriptions, info.query) } - w.mtx.Unlock() return nil } @@ -129,8 +129,8 @@ func (w *wsEvents) UnsubscribeAll(ctx context.Context, subscriber string) error } w.mtx.Lock() + defer w.mtx.Unlock() w.subscriptions = make(map[string]*wsSubscription) - w.mtx.Unlock() return nil } diff --git a/rpc/client/local/local.go b/rpc/client/local/local.go index b9091efac..8718ee504 100644 --- a/rpc/client/local/local.go +++ b/rpc/client/local/local.go @@ -7,7 +7,6 @@ import ( "time" "github.com/tendermint/tendermint/internal/eventbus" - "github.com/tendermint/tendermint/internal/eventlog/cursor" "github.com/tendermint/tendermint/internal/pubsub" "github.com/tendermint/tendermint/internal/pubsub/query" rpccore "github.com/tendermint/tendermint/internal/rpc/core" @@ -78,28 +77,29 @@ func (c *Local) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes) return c.ABCIQueryWithOptions(ctx, path, data, rpcclient.DefaultABCIQueryOptions) } -func (c *Local) ABCIQueryWithOptions( - ctx context.Context, - path string, - data bytes.HexBytes, - opts rpcclient.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { - return c.env.ABCIQuery(ctx, path, data, opts.Height, opts.Prove) +func (c *Local) ABCIQueryWithOptions(ctx context.Context, path string, data bytes.HexBytes, opts rpcclient.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { + return c.env.ABCIQuery(ctx, &coretypes.RequestABCIQuery{ + Path: path, Data: data, Height: coretypes.Int64(opts.Height), Prove: opts.Prove, + }) } func (c *Local) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) { - return c.env.BroadcastTxCommit(ctx, tx) + return c.env.BroadcastTxCommit(ctx, &coretypes.RequestBroadcastTx{Tx: tx}) } func (c *Local) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { - return c.env.BroadcastTxAsync(ctx, tx) + return c.env.BroadcastTxAsync(ctx, &coretypes.RequestBroadcastTx{Tx: tx}) } func (c *Local) BroadcastTxSync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { - return c.env.BroadcastTxSync(ctx, tx) + return c.env.BroadcastTxSync(ctx, &coretypes.RequestBroadcastTx{Tx: tx}) } func (c *Local) UnconfirmedTxs(ctx context.Context, page, perPage *int) (*coretypes.ResultUnconfirmedTxs, error) { - return c.env.UnconfirmedTxs(ctx, page, perPage) + return c.env.UnconfirmedTxs(ctx, &coretypes.RequestUnconfirmedTxs{ + Page: coretypes.Int64Ptr(page), + PerPage: coretypes.Int64Ptr(perPage), + }) } func (c *Local) NumUnconfirmedTxs(ctx context.Context) (*coretypes.ResultUnconfirmedTxs, error) { @@ -107,7 +107,7 @@ func (c *Local) NumUnconfirmedTxs(ctx context.Context) (*coretypes.ResultUnconfi } func (c *Local) CheckTx(ctx context.Context, tx types.Tx) (*coretypes.ResultCheckTx, error) { - return c.env.CheckTx(ctx, tx) + return c.env.CheckTx(ctx, &coretypes.RequestCheckTx{Tx: tx}) } func (c *Local) RemoveTx(ctx context.Context, txKey types.TxKey) error { @@ -127,18 +127,11 @@ func (c *Local) ConsensusState(ctx context.Context) (*coretypes.ResultConsensusS } func (c *Local) ConsensusParams(ctx context.Context, height *int64) (*coretypes.ResultConsensusParams, error) { - return c.env.ConsensusParams(ctx, height) + return c.env.ConsensusParams(ctx, &coretypes.RequestConsensusParams{Height: (*coretypes.Int64)(height)}) } func (c *Local) Events(ctx context.Context, req *coretypes.RequestEvents) (*coretypes.ResultEvents, error) { - var before, after cursor.Cursor - if err := before.UnmarshalText([]byte(req.Before)); err != nil { - return nil, err - } - if err := after.UnmarshalText([]byte(req.After)); err != nil { - return nil, err - } - return c.env.Events(ctx, req.Filter, req.MaxItems, before, after, req.WaitTime) + return c.env.Events(ctx, req) } func (c *Local) Health(ctx context.Context) (*coretypes.ResultHealth, error) { @@ -146,7 +139,10 @@ func (c *Local) Health(ctx context.Context) (*coretypes.ResultHealth, error) { } func (c *Local) BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) { - return c.env.BlockchainInfo(ctx, minHeight, maxHeight) + return c.env.BlockchainInfo(ctx, &coretypes.RequestBlockchainInfo{ + MinHeight: coretypes.Int64(minHeight), + MaxHeight: coretypes.Int64(maxHeight), + }) } func (c *Local) Genesis(ctx context.Context) (*coretypes.ResultGenesis, error) { @@ -154,70 +150,69 @@ func (c *Local) Genesis(ctx context.Context) (*coretypes.ResultGenesis, error) { } func (c *Local) GenesisChunked(ctx context.Context, id uint) (*coretypes.ResultGenesisChunk, error) { - return c.env.GenesisChunked(ctx, id) + return c.env.GenesisChunked(ctx, &coretypes.RequestGenesisChunked{Chunk: coretypes.Int64(id)}) } func (c *Local) Block(ctx context.Context, height *int64) (*coretypes.ResultBlock, error) { - return c.env.Block(ctx, height) + return c.env.Block(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)}) } func (c *Local) BlockByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultBlock, error) { - return c.env.BlockByHash(ctx, hash) + return c.env.BlockByHash(ctx, &coretypes.RequestBlockByHash{Hash: hash}) } func (c *Local) BlockResults(ctx context.Context, height *int64) (*coretypes.ResultBlockResults, error) { - return c.env.BlockResults(ctx, height) + return c.env.BlockResults(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)}) } func (c *Local) Header(ctx context.Context, height *int64) (*coretypes.ResultHeader, error) { - return c.env.Header(ctx, height) + return c.env.Header(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)}) } func (c *Local) HeaderByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultHeader, error) { - return c.env.HeaderByHash(ctx, hash) + return c.env.HeaderByHash(ctx, &coretypes.RequestBlockByHash{Hash: hash}) } func (c *Local) Commit(ctx context.Context, height *int64) (*coretypes.ResultCommit, error) { - return c.env.Commit(ctx, height) + return c.env.Commit(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)}) } func (c *Local) Validators(ctx context.Context, height *int64, page, perPage *int) (*coretypes.ResultValidators, error) { - return c.env.Validators(ctx, height, page, perPage) + return c.env.Validators(ctx, &coretypes.RequestValidators{ + Height: (*coretypes.Int64)(height), + Page: coretypes.Int64Ptr(page), + PerPage: coretypes.Int64Ptr(perPage), + }) } func (c *Local) Tx(ctx context.Context, hash bytes.HexBytes, prove bool) (*coretypes.ResultTx, error) { - return c.env.Tx(ctx, hash, prove) + return c.env.Tx(ctx, &coretypes.RequestTx{Hash: hash, Prove: prove}) } -func (c *Local) TxSearch( - ctx context.Context, - queryString string, - prove bool, - page, - perPage *int, - orderBy string, -) (*coretypes.ResultTxSearch, error) { - return c.env.TxSearch(ctx, queryString, prove, page, perPage, orderBy) +func (c *Local) TxSearch(ctx context.Context, queryString string, prove bool, page, perPage *int, orderBy string) (*coretypes.ResultTxSearch, error) { + return c.env.TxSearch(ctx, &coretypes.RequestTxSearch{ + Query: queryString, + Prove: prove, + Page: coretypes.Int64Ptr(page), + PerPage: coretypes.Int64Ptr(perPage), + OrderBy: orderBy, + }) } -func (c *Local) BlockSearch( - ctx context.Context, - queryString string, - page, perPage *int, - orderBy string, -) (*coretypes.ResultBlockSearch, error) { - return c.env.BlockSearch(ctx, queryString, page, perPage, orderBy) +func (c *Local) BlockSearch(ctx context.Context, queryString string, page, perPage *int, orderBy string) (*coretypes.ResultBlockSearch, error) { + return c.env.BlockSearch(ctx, &coretypes.RequestBlockSearch{ + Query: queryString, + Page: coretypes.Int64Ptr(page), + PerPage: coretypes.Int64Ptr(perPage), + OrderBy: orderBy, + }) } func (c *Local) BroadcastEvidence(ctx context.Context, ev types.Evidence) (*coretypes.ResultBroadcastEvidence, error) { - return c.env.BroadcastEvidence(ctx, coretypes.Evidence{Value: ev}) + return c.env.BroadcastEvidence(ctx, &coretypes.RequestBroadcastEvidence{Evidence: ev}) } -func (c *Local) Subscribe( - ctx context.Context, - subscriber, - queryString string, - capacity ...int) (out <-chan coretypes.ResultEvent, err error) { +func (c *Local) Subscribe(ctx context.Context, subscriber, queryString string, capacity ...int) (<-chan coretypes.ResultEvent, error) { q, err := query.New(queryString) if err != nil { return nil, fmt.Errorf("failed to parse query: %w", err) @@ -251,12 +246,7 @@ func (c *Local) Subscribe( return outc, nil } -func (c *Local) eventsRoutine( - ctx context.Context, - sub eventbus.Subscription, - subArgs pubsub.SubscribeArgs, - outc chan<- coretypes.ResultEvent, -) { +func (c *Local) eventsRoutine(ctx context.Context, sub eventbus.Subscription, subArgs pubsub.SubscribeArgs, outc chan<- coretypes.ResultEvent) { qstr := subArgs.Query.String() for { msg, err := sub.Next(ctx) @@ -271,17 +261,24 @@ func (c *Local) eventsRoutine( } continue } - outc <- coretypes.ResultEvent{ + select { + case outc <- coretypes.ResultEvent{ SubscriptionID: msg.SubscriptionID(), Query: qstr, Data: msg.Data(), Events: msg.Events(), + }: + case <-ctx.Done(): + return } } } // Try to resubscribe with exponential backoff. func (c *Local) resubscribe(ctx context.Context, subArgs pubsub.SubscribeArgs) eventbus.Subscription { + timer := time.NewTimer(0) + defer timer.Stop() + attempts := 0 for { if !c.IsRunning() { @@ -294,7 +291,13 @@ func (c *Local) resubscribe(ctx context.Context, subArgs pubsub.SubscribeArgs) e } attempts++ - time.Sleep((10 << uint(attempts)) * time.Millisecond) // 10ms -> 20ms -> 40ms + timer.Reset((10 << uint(attempts)) * time.Millisecond) // 10ms -> 20ms -> 40ms + select { + case <-timer.C: + continue + case <-ctx.Done(): + return nil + } } } diff --git a/rpc/client/mock/abci.go b/rpc/client/mock/abci.go index 8c652444b..a6aebdb14 100644 --- a/rpc/client/mock/abci.go +++ b/rpc/client/mock/abci.go @@ -25,47 +25,65 @@ var ( ) func (a ABCIApp) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, error) { - return &coretypes.ResultABCIInfo{Response: a.App.Info(proxy.RequestInfo)}, nil + res, err := a.App.Info(ctx, &proxy.RequestInfo) + if err != nil { + return nil, err + } + + return &coretypes.ResultABCIInfo{Response: *res}, nil } func (a ABCIApp) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes) (*coretypes.ResultABCIQuery, error) { return a.ABCIQueryWithOptions(ctx, path, data, client.DefaultABCIQueryOptions) } -func (a ABCIApp) ABCIQueryWithOptions( - ctx context.Context, - path string, - data bytes.HexBytes, - opts client.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { - q := a.App.Query(abci.RequestQuery{ +func (a ABCIApp) ABCIQueryWithOptions(ctx context.Context, path string, data bytes.HexBytes, opts client.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { + q, err := a.App.Query(ctx, &abci.RequestQuery{ Data: data, Path: path, Height: opts.Height, Prove: opts.Prove, }) - return &coretypes.ResultABCIQuery{Response: q}, nil + if err != nil { + return nil, err + } + + return &coretypes.ResultABCIQuery{Response: *q}, nil } // NOTE: Caller should call a.App.Commit() separately, // this function does not actually wait for a commit. // TODO: Make it wait for a commit and set res.Height appropriately. func (a ABCIApp) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) { - res := coretypes.ResultBroadcastTxCommit{} - res.CheckTx = a.App.CheckTx(abci.RequestCheckTx{Tx: tx}) - if res.CheckTx.IsErr() { - return &res, nil + resp, err := a.App.CheckTx(ctx, &abci.RequestCheckTx{Tx: tx}) + if err != nil { + return nil, err } - fb := a.App.FinalizeBlock(abci.RequestFinalizeBlock{Txs: [][]byte{tx}}) + + res := &coretypes.ResultBroadcastTxCommit{CheckTx: *resp} + if res.CheckTx.IsErr() { + return res, nil + } + + fb, err := a.App.FinalizeBlock(ctx, &abci.RequestFinalizeBlock{Txs: [][]byte{tx}}) + if err != nil { + return nil, err + } + res.TxResult = *fb.TxResults[0] res.Height = -1 // TODO - return &res, nil + return res, nil } func (a ABCIApp) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { - c := a.App.CheckTx(abci.RequestCheckTx{Tx: tx}) + c, err := a.App.CheckTx(ctx, &abci.RequestCheckTx{Tx: tx}) + if err != nil { + return nil, err + } + // and this gets written in a background thread... if !c.IsErr() { - go func() { a.App.FinalizeBlock(abci.RequestFinalizeBlock{Txs: [][]byte{tx}}) }() + go func() { _, _ = a.App.FinalizeBlock(ctx, &abci.RequestFinalizeBlock{Txs: [][]byte{tx}}) }() } return &coretypes.ResultBroadcastTx{ Code: c.Code, @@ -77,10 +95,14 @@ func (a ABCIApp) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes. } func (a ABCIApp) BroadcastTxSync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { - c := a.App.CheckTx(abci.RequestCheckTx{Tx: tx}) + c, err := a.App.CheckTx(ctx, &abci.RequestCheckTx{Tx: tx}) + if err != nil { + return nil, err + } + // and this gets written in a background thread... if !c.IsErr() { - go func() { a.App.FinalizeBlock(abci.RequestFinalizeBlock{Txs: [][]byte{tx}}) }() + go func() { _, _ = a.App.FinalizeBlock(ctx, &abci.RequestFinalizeBlock{Txs: [][]byte{tx}}) }() } return &coretypes.ResultBroadcastTx{ Code: c.Code, @@ -113,11 +135,7 @@ func (m ABCIMock) ABCIQuery(ctx context.Context, path string, data bytes.HexByte return m.ABCIQueryWithOptions(ctx, path, data, client.DefaultABCIQueryOptions) } -func (m ABCIMock) ABCIQueryWithOptions( - ctx context.Context, - path string, - data bytes.HexBytes, - opts client.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { +func (m ABCIMock) ABCIQueryWithOptions(ctx context.Context, path string, data bytes.HexBytes, opts client.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { res, err := m.Query.GetResponse(QueryArgs{path, data, opts.Height, opts.Prove}) if err != nil { return nil, err @@ -185,11 +203,7 @@ func (r *ABCIRecorder) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, return res, err } -func (r *ABCIRecorder) ABCIQuery( - ctx context.Context, - path string, - data bytes.HexBytes, -) (*coretypes.ResultABCIQuery, error) { +func (r *ABCIRecorder) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes) (*coretypes.ResultABCIQuery, error) { return r.ABCIQueryWithOptions(ctx, path, data, client.DefaultABCIQueryOptions) } diff --git a/rpc/client/mock/abci_test.go b/rpc/client/mock/abci_test.go index 489133a5b..e35ccf29c 100644 --- a/rpc/client/mock/abci_test.go +++ b/rpc/client/mock/abci_test.go @@ -185,7 +185,8 @@ func TestABCIApp(t *testing.T) { // commit // TODO: This may not be necessary in the future if res.Height == -1 { - m.App.Commit() + _, err := m.App.Commit(ctx) + require.NoError(t, err) } // check the key diff --git a/rpc/client/mock/client.go b/rpc/client/mock/client.go index b47ff1e76..a3272cb17 100644 --- a/rpc/client/mock/client.go +++ b/rpc/client/mock/client.go @@ -91,23 +91,25 @@ func (c Client) ABCIQueryWithOptions( path string, data bytes.HexBytes, opts client.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { - return c.env.ABCIQuery(ctx, path, data, opts.Height, opts.Prove) + return c.env.ABCIQuery(ctx, &coretypes.RequestABCIQuery{ + Path: path, Data: data, Height: coretypes.Int64(opts.Height), Prove: opts.Prove, + }) } func (c Client) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) { - return c.env.BroadcastTxCommit(ctx, tx) + return c.env.BroadcastTxCommit(ctx, &coretypes.RequestBroadcastTx{Tx: tx}) } func (c Client) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { - return c.env.BroadcastTxAsync(ctx, tx) + return c.env.BroadcastTxAsync(ctx, &coretypes.RequestBroadcastTx{Tx: tx}) } func (c Client) BroadcastTxSync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { - return c.env.BroadcastTxSync(ctx, tx) + return c.env.BroadcastTxSync(ctx, &coretypes.RequestBroadcastTx{Tx: tx}) } func (c Client) CheckTx(ctx context.Context, tx types.Tx) (*coretypes.ResultCheckTx, error) { - return c.env.CheckTx(ctx, tx) + return c.env.CheckTx(ctx, &coretypes.RequestCheckTx{Tx: tx}) } func (c Client) NetInfo(ctx context.Context) (*coretypes.ResultNetInfo, error) { @@ -123,7 +125,7 @@ func (c Client) DumpConsensusState(ctx context.Context) (*coretypes.ResultDumpCo } func (c Client) ConsensusParams(ctx context.Context, height *int64) (*coretypes.ResultConsensusParams, error) { - return c.env.ConsensusParams(ctx, height) + return c.env.ConsensusParams(ctx, &coretypes.RequestConsensusParams{Height: (*coretypes.Int64)(height)}) } func (c Client) Health(ctx context.Context) (*coretypes.ResultHealth, error) { @@ -131,7 +133,10 @@ func (c Client) Health(ctx context.Context) (*coretypes.ResultHealth, error) { } func (c Client) BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) { - return c.env.BlockchainInfo(ctx, minHeight, maxHeight) + return c.env.BlockchainInfo(ctx, &coretypes.RequestBlockchainInfo{ + MinHeight: coretypes.Int64(minHeight), + MaxHeight: coretypes.Int64(maxHeight), + }) } func (c Client) Genesis(ctx context.Context) (*coretypes.ResultGenesis, error) { @@ -139,21 +144,25 @@ func (c Client) Genesis(ctx context.Context) (*coretypes.ResultGenesis, error) { } func (c Client) Block(ctx context.Context, height *int64) (*coretypes.ResultBlock, error) { - return c.env.Block(ctx, height) + return c.env.Block(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)}) } func (c Client) BlockByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultBlock, error) { - return c.env.BlockByHash(ctx, hash) + return c.env.BlockByHash(ctx, &coretypes.RequestBlockByHash{Hash: hash}) } func (c Client) Commit(ctx context.Context, height *int64) (*coretypes.ResultCommit, error) { - return c.env.Commit(ctx, height) + return c.env.Commit(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)}) } func (c Client) Validators(ctx context.Context, height *int64, page, perPage *int) (*coretypes.ResultValidators, error) { - return c.env.Validators(ctx, height, page, perPage) + return c.env.Validators(ctx, &coretypes.RequestValidators{ + Height: (*coretypes.Int64)(height), + Page: coretypes.Int64Ptr(page), + PerPage: coretypes.Int64Ptr(perPage), + }) } func (c Client) BroadcastEvidence(ctx context.Context, ev types.Evidence) (*coretypes.ResultBroadcastEvidence, error) { - return c.env.BroadcastEvidence(ctx, coretypes.Evidence{Value: ev}) + return c.env.BroadcastEvidence(ctx, &coretypes.RequestBroadcastEvidence{Evidence: ev}) } diff --git a/rpc/client/mocks/client.go b/rpc/client/mocks/client.go index ffa1d1f29..9a286eaf2 100644 --- a/rpc/client/mocks/client.go +++ b/rpc/client/mocks/client.go @@ -12,6 +12,8 @@ import ( mock "github.com/stretchr/testify/mock" + testing "testing" + types "github.com/tendermint/tendermint/types" ) @@ -411,6 +413,29 @@ func (_m *Client) DumpConsensusState(_a0 context.Context) (*coretypes.ResultDump return r0, r1 } +// Events provides a mock function with given fields: ctx, req +func (_m *Client) Events(ctx context.Context, req *coretypes.RequestEvents) (*coretypes.ResultEvents, error) { + ret := _m.Called(ctx, req) + + var r0 *coretypes.ResultEvents + if rf, ok := ret.Get(0).(func(context.Context, *coretypes.RequestEvents) *coretypes.ResultEvents); ok { + r0 = rf(ctx, req) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultEvents) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *coretypes.RequestEvents) error); ok { + r1 = rf(ctx, req) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // Genesis provides a mock function with given fields: _a0 func (_m *Client) Genesis(_a0 context.Context) (*coretypes.ResultGenesis, error) { ret := _m.Called(_a0) @@ -772,3 +797,13 @@ func (_m *Client) Validators(ctx context.Context, height *int64, page *int, perP return r0, r1 } + +// NewClient creates a new instance of Client. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewClient(t testing.TB) *Client { + mock := &Client{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/rpc/coretypes/requests.go b/rpc/coretypes/requests.go new file mode 100644 index 000000000..cd4d22726 --- /dev/null +++ b/rpc/coretypes/requests.go @@ -0,0 +1,188 @@ +package coretypes + +import ( + "encoding/json" + "strconv" + "time" + + "github.com/tendermint/tendermint/internal/jsontypes" + "github.com/tendermint/tendermint/libs/bytes" + "github.com/tendermint/tendermint/types" +) + +type RequestSubscribe struct { + Query string `json:"query"` +} + +type RequestUnsubscribe struct { + Query string `json:"query"` +} + +type RequestBlockchainInfo struct { + MinHeight Int64 `json:"minHeight"` + MaxHeight Int64 `json:"maxHeight"` +} + +type RequestGenesisChunked struct { + Chunk Int64 `json:"chunk"` +} + +type RequestBlockInfo struct { + Height *Int64 `json:"height"` +} + +type RequestBlockByHash struct { + Hash bytes.HexBytes `json:"hash"` +} + +type RequestCheckTx struct { + Tx types.Tx `json:"tx"` +} + +type RequestRemoveTx struct { + TxKey types.TxKey `json:"txkey"` +} + +type RequestTx struct { + Hash bytes.HexBytes `json:"hash"` + Prove bool `json:"prove"` +} + +type RequestTxSearch struct { + Query string `json:"query"` + Prove bool `json:"prove"` + Page *Int64 `json:"page"` + PerPage *Int64 `json:"per_page"` + OrderBy string `json:"order_by"` +} + +type RequestBlockSearch struct { + Query string `json:"query"` + Page *Int64 `json:"page"` + PerPage *Int64 `json:"per_page"` + OrderBy string `json:"order_by"` +} + +type RequestValidators struct { + Height *Int64 `json:"height"` + Page *Int64 `json:"page"` + PerPage *Int64 `json:"per_page"` +} + +type RequestConsensusParams struct { + Height *Int64 `json:"height"` +} + +type RequestUnconfirmedTxs struct { + Page *Int64 `json:"page"` + PerPage *Int64 `json:"per_page"` +} + +type RequestBroadcastTx struct { + Tx types.Tx `json:"tx"` +} + +type RequestABCIQuery struct { + Path string `json:"path"` + Data bytes.HexBytes `json:"data"` + Height Int64 `json:"height"` + Prove bool `json:"prove"` +} + +type RequestBroadcastEvidence struct { + Evidence types.Evidence +} + +type requestBroadcastEvidenceJSON struct { + Evidence json.RawMessage `json:"evidence"` +} + +func (r RequestBroadcastEvidence) MarshalJSON() ([]byte, error) { + ev, err := jsontypes.Marshal(r.Evidence) + if err != nil { + return nil, err + } + return json.Marshal(requestBroadcastEvidenceJSON{ + Evidence: ev, + }) +} + +func (r *RequestBroadcastEvidence) UnmarshalJSON(data []byte) error { + var val requestBroadcastEvidenceJSON + if err := json.Unmarshal(data, &val); err != nil { + return err + } + if err := jsontypes.Unmarshal(val.Evidence, &r.Evidence); err != nil { + return err + } + return nil +} + +// RequestEvents is the argument for the "/events" RPC endpoint. +type RequestEvents struct { + // Optional filter spec. If nil or empty, all items are eligible. + Filter *EventFilter `json:"filter"` + + // The maximum number of eligible items to return. + // If zero or negative, the server will report a default number. + MaxItems int `json:"maxItems"` + + // Return only items after this cursor. If empty, the limit is just + // before the the beginning of the event log. + After string `json:"after"` + + // Return only items before this cursor. If empty, the limit is just + // after the head of the event log. + Before string `json:"before"` + + // Wait for up to this long for events to be available. + WaitTime time.Duration `json:"waitTime"` +} + +// An EventFilter specifies which events are selected by an /events request. +type EventFilter struct { + Query string `json:"query"` +} + +// Int64 is a wrapper for int64 that encodes to JSON as a string and can be +// decoded from either a string or a number value. +type Int64 int64 + +func (z *Int64) UnmarshalJSON(data []byte) error { + var s string + if len(data) != 0 && data[0] == '"' { + if err := json.Unmarshal(data, &s); err != nil { + return err + } + } else { + s = string(data) + } + v, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return err + } + *z = Int64(v) + return nil +} + +func (z Int64) MarshalJSON() ([]byte, error) { + return []byte(strconv.FormatInt(int64(z), 10)), nil +} + +// IntPtr returns a pointer to the value of *z as an int, or nil if z == nil. +func (z *Int64) IntPtr() *int { + if z == nil { + return nil + } + v := int(*z) + return &v +} + +// Int64Ptr returns an *Int64 that points to the same value as v, or nil. +func Int64Ptr(v *int) *Int64 { + if v == nil { + return nil + } + z := Int64(*v) + return &z +} diff --git a/rpc/coretypes/responses.go b/rpc/coretypes/responses.go index 8968f9868..51d988f3a 100644 --- a/rpc/coretypes/responses.go +++ b/rpc/coretypes/responses.go @@ -357,32 +357,6 @@ type Evidence struct { func (e Evidence) MarshalJSON() ([]byte, error) { return jsontypes.Marshal(e.Value) } func (e *Evidence) UnmarshalJSON(data []byte) error { return jsontypes.Unmarshal(data, &e.Value) } -// RequestEvents is the argument for the "/events" RPC endpoint. -type RequestEvents struct { - // Optional filter spec. If nil or empty, all items are eligible. - Filter *EventFilter `json:"filter"` - - // The maximum number of eligible items to return. - // If zero or negative, the server will report a default number. - MaxItems int `json:"maxItems"` - - // Return only items after this cursor. If empty, the limit is just - // before the the beginning of the event log. - After string `json:"after"` - - // Return only items before this cursor. If empty, the limit is just - // after the head of the event log. - Before string `json:"before"` - - // Wait for up to this long for events to be available. - WaitTime time.Duration `json:"waitTime"` -} - -// An EventFilter specifies which events are selected by an /events request. -type EventFilter struct { - Query string `json:"query"` -} - // ResultEvents is the response from the "/events" RPC endpoint. type ResultEvents struct { // The items matching the request parameters, from newest diff --git a/rpc/jsonrpc/client/ws_client.go b/rpc/jsonrpc/client/ws_client.go index 3a626e43a..ea14a6cf8 100644 --- a/rpc/jsonrpc/client/ws_client.go +++ b/rpc/jsonrpc/client/ws_client.go @@ -344,10 +344,6 @@ func (c *WSClient) writeRoutine(ctx context.Context) { defer func() { ticker.Stop() c.conn.Close() - // err != nil { - // ignore error; it will trigger in tests - // likely because it's closing an already closed connection - // } c.wg.Done() }() @@ -430,14 +426,7 @@ func (c *WSClient) readRoutine(ctx context.Context) { // ID. According to the spec, they should be notifications (requests // without IDs). // https://github.com/tendermint/tendermint/issues/2949 - // c.mtx.Lock() - // if _, ok := c.sentIDs[response.ID.(types.JSONRPCIntID)]; !ok { - // c.Logger.Error("unsolicited response ID", "id", response.ID, "expected", c.sentIDs) - // c.mtx.Unlock() - // continue - // } - // delete(c.sentIDs, response.ID.(types.JSONRPCIntID)) - // c.mtx.Unlock() + // // Combine a non-blocking read on BaseService.Quit with a non-blocking write on ResponsesCh to avoid blocking // c.wg.Wait() in c.Stop(). Note we rely on Quit being closed so that it sends unlimited Quit signals to stop // both readRoutine and writeRoutine diff --git a/rpc/jsonrpc/doc.go b/rpc/jsonrpc/doc.go index b014fe38d..58b522861 100644 --- a/rpc/jsonrpc/doc.go +++ b/rpc/jsonrpc/doc.go @@ -55,7 +55,7 @@ // Define some routes // // var Routes = map[string]*rpcserver.RPCFunc{ -// "status": rpcserver.NewRPCFunc(Status, "arg"), +// "status": rpcserver.NewRPCFunc(Status), // } // // An rpc function: diff --git a/rpc/jsonrpc/jsonrpc_test.go b/rpc/jsonrpc/jsonrpc_test.go index 1ba853a62..236db9b32 100644 --- a/rpc/jsonrpc/jsonrpc_test.go +++ b/rpc/jsonrpc/jsonrpc_test.go @@ -34,49 +34,65 @@ const ( testVal = "acbd" ) +type RequestEcho struct { + Value string `json:"arg"` +} + type ResultEcho struct { Value string `json:"value"` } +type RequestEchoInt struct { + Value int `json:"arg"` +} + type ResultEchoInt struct { Value int `json:"value"` } +type RequestEchoBytes struct { + Value []byte `json:"arg"` +} + type ResultEchoBytes struct { Value []byte `json:"value"` } +type RequestEchoDataBytes struct { + Value tmbytes.HexBytes `json:"arg"` +} + type ResultEchoDataBytes struct { Value tmbytes.HexBytes `json:"value"` } // Define some routes var Routes = map[string]*server.RPCFunc{ - "echo": server.NewRPCFunc(EchoResult, "arg"), - "echo_ws": server.NewWSRPCFunc(EchoWSResult, "arg"), - "echo_bytes": server.NewRPCFunc(EchoBytesResult, "arg"), - "echo_data_bytes": server.NewRPCFunc(EchoDataBytesResult, "arg"), - "echo_int": server.NewRPCFunc(EchoIntResult, "arg"), + "echo": server.NewRPCFunc(EchoResult), + "echo_ws": server.NewWSRPCFunc(EchoWSResult), + "echo_bytes": server.NewRPCFunc(EchoBytesResult), + "echo_data_bytes": server.NewRPCFunc(EchoDataBytesResult), + "echo_int": server.NewRPCFunc(EchoIntResult), } -func EchoResult(ctx context.Context, v string) (*ResultEcho, error) { - return &ResultEcho{v}, nil +func EchoResult(ctx context.Context, v *RequestEcho) (*ResultEcho, error) { + return &ResultEcho{v.Value}, nil } -func EchoWSResult(ctx context.Context, v string) (*ResultEcho, error) { - return &ResultEcho{v}, nil +func EchoWSResult(ctx context.Context, v *RequestEcho) (*ResultEcho, error) { + return &ResultEcho{v.Value}, nil } -func EchoIntResult(ctx context.Context, v int) (*ResultEchoInt, error) { - return &ResultEchoInt{v}, nil +func EchoIntResult(ctx context.Context, v *RequestEchoInt) (*ResultEchoInt, error) { + return &ResultEchoInt{v.Value}, nil } -func EchoBytesResult(ctx context.Context, v []byte) (*ResultEchoBytes, error) { - return &ResultEchoBytes{v}, nil +func EchoBytesResult(ctx context.Context, v *RequestEchoBytes) (*ResultEchoBytes, error) { + return &ResultEchoBytes{v.Value}, nil } -func EchoDataBytesResult(ctx context.Context, v tmbytes.HexBytes) (*ResultEchoDataBytes, error) { - return &ResultEchoDataBytes{v}, nil +func EchoDataBytesResult(ctx context.Context, v *RequestEchoDataBytes) (*ResultEchoDataBytes, error) { + return &ResultEchoDataBytes{v.Value}, nil } // launch unix and tcp servers diff --git a/rpc/jsonrpc/server/http_json_handler.go b/rpc/jsonrpc/server/http_json_handler.go index 94da974de..2eeded2d7 100644 --- a/rpc/jsonrpc/server/http_json_handler.go +++ b/rpc/jsonrpc/server/http_json_handler.go @@ -2,15 +2,11 @@ package server import ( "bytes" - "context" "encoding/json" - "errors" "fmt" "html/template" "io" "net/http" - "reflect" - "strconv" "strings" "github.com/tendermint/tendermint/libs/log" @@ -70,19 +66,11 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han RPCRequest: &req, HTTPRequest: hreq, }) - args, err := parseParams(ctx, rpcFunc, req.Params) + result, err := rpcFunc.Call(ctx, req.Params) if err != nil { - responses = append(responses, - req.MakeErrorf(rpctypes.CodeInvalidParams, "converting JSON parameters: %v", err)) - continue - } - - returns := rpcFunc.f.Call(args) - result, err := unreflectResult(returns) - if err == nil { - responses = append(responses, req.MakeResponse(result)) - } else { responses = append(responses, req.MakeError(err)) + } else { + responses = append(responses, req.MakeResponse(result)) } } @@ -124,120 +112,21 @@ func parseRequests(data []byte) ([]rpctypes.RPCRequest, error) { return reqs, nil } -// parseParams parses the JSON parameters of rpcReq into the arguments of fn, -// returning the corresponding argument values or an error. -func parseParams(ctx context.Context, fn *RPCFunc, paramData []byte) ([]reflect.Value, error) { - params, err := parseJSONParams(fn, paramData) - if err != nil { - return nil, err - } - - args := make([]reflect.Value, 1+len(params)) - args[0] = reflect.ValueOf(ctx) - for i, param := range params { - ptype := fn.args[i+1] - if len(param) == 0 { - args[i+1] = reflect.Zero(ptype) - continue - } - - var pval reflect.Value - isPtr := ptype.Kind() == reflect.Ptr - if isPtr { - pval = reflect.New(ptype.Elem()) - } else { - pval = reflect.New(ptype) - } - baseType := pval.Type().Elem() - - if isIntType(baseType) && isStringValue(param) { - var z int64String - if err := json.Unmarshal(param, &z); err != nil { - return nil, fmt.Errorf("decoding string %q: %w", fn.argNames[i], err) - } - pval.Elem().Set(reflect.ValueOf(z).Convert(baseType)) - } else if err := json.Unmarshal(param, pval.Interface()); err != nil { - return nil, fmt.Errorf("decoding %q: %w", fn.argNames[i], err) - } - - if isPtr { - args[i+1] = pval - } else { - args[i+1] = pval.Elem() - } - } - return args, nil -} - -// parseJSONParams parses data and returns a slice of JSON values matching the -// positional parameters of fn. It reports an error if data is not "null" and -// does not encode an object or an array, or if the number of array parameters -// does not match the argument list of fn (excluding the context). -func parseJSONParams(fn *RPCFunc, data []byte) ([]json.RawMessage, error) { - base := bytes.TrimSpace(data) - if bytes.HasPrefix(base, []byte("{")) { - var m map[string]json.RawMessage - if err := json.Unmarshal(base, &m); err != nil { - return nil, fmt.Errorf("decoding parameter object: %w", err) - } - out := make([]json.RawMessage, len(fn.argNames)) - for i, name := range fn.argNames { - if p, ok := m[name]; ok { - out[i] = p - } - } - return out, nil - - } else if bytes.HasPrefix(base, []byte("[")) { - var m []json.RawMessage - if err := json.Unmarshal(base, &m); err != nil { - return nil, fmt.Errorf("decoding parameter array: %w", err) - } - if len(m) != len(fn.argNames) { - return nil, fmt.Errorf("got %d parameters, want %d", len(m), len(fn.argNames)) - } - return m, nil - - } else if bytes.Equal(base, []byte("null")) { - return make([]json.RawMessage, len(fn.argNames)), nil - } - - return nil, errors.New("parameters must be an object or an array") -} - -// isStringValue reports whether data is a JSON string value. -func isStringValue(data json.RawMessage) bool { - return len(data) != 0 && data[0] == '"' -} - -type int64String int64 - -func (z *int64String) UnmarshalText(data []byte) error { - v, err := strconv.ParseInt(string(data), 10, 64) - if err != nil { - return err - } - *z = int64String(v) - return nil -} - // writes a list of available rpc endpoints as an html page func writeListOfEndpoints(w http.ResponseWriter, r *http.Request, funcMap map[string]*RPCFunc) { hasArgs := make(map[string]string) noArgs := make(map[string]string) for name, rf := range funcMap { base := fmt.Sprintf("//%s/%s", r.Host, name) - // N.B. Check argNames, not args, since the type list includes the type - // of the leading context argument. - if len(rf.argNames) == 0 { + if len(rf.args) == 0 { noArgs[name] = base - } else { - query := append([]string(nil), rf.argNames...) - for i, arg := range query { - query[i] = arg + "=_" - } - hasArgs[name] = base + "?" + strings.Join(query, "&") + continue } + var query []string + for _, arg := range rf.args { + query = append(query, arg.name+"=_") + } + hasArgs[name] = base + "?" + strings.Join(query, "&") } w.Header().Set("Content-Type", "text/html") _ = listOfEndpoints.Execute(w, map[string]map[string]string{ diff --git a/rpc/jsonrpc/server/http_json_handler_test.go b/rpc/jsonrpc/server/http_json_handler_test.go index 1f5d2c320..77c74ffbc 100644 --- a/rpc/jsonrpc/server/http_json_handler_test.go +++ b/rpc/jsonrpc/server/http_json_handler_test.go @@ -17,9 +17,16 @@ import ( ) func testMux() *http.ServeMux { + type testArgs struct { + S string `json:"s"` + I json.Number `json:"i"` + } + type blockArgs struct { + H json.Number `json:"h"` + } funcMap := map[string]*RPCFunc{ - "c": NewRPCFunc(func(ctx context.Context, s string, i int) (string, error) { return "foo", nil }, "s", "i"), - "block": NewRPCFunc(func(ctx context.Context, h int) (string, error) { return "block", nil }, "height"), + "c": NewRPCFunc(func(ctx context.Context, arg *testArgs) (string, error) { return "foo", nil }), + "block": NewRPCFunc(func(ctx context.Context, arg *blockArgs) (string, error) { return "block", nil }), } mux := http.NewServeMux() logger := log.NewNopLogger() @@ -46,7 +53,7 @@ func TestRPCParams(t *testing.T) { // id not captured in JSON parsing failures {`{"method": "c", "id": "0", "params": a}`, "invalid character", ""}, {`{"method": "c", "id": "0", "params": ["a"]}`, "got 1", `"0"`}, - {`{"method": "c", "id": "0", "params": ["a", "b"]}`, "invalid syntax", `"0"`}, + {`{"method": "c", "id": "0", "params": ["a", "b"]}`, "invalid number", `"0"`}, {`{"method": "c", "id": "0", "params": [1, 1]}`, "of type string", `"0"`}, // no ID - notification diff --git a/rpc/jsonrpc/server/http_server.go b/rpc/jsonrpc/server/http_server.go index 32917b8cb..0b715835d 100644 --- a/rpc/jsonrpc/server/http_server.go +++ b/rpc/jsonrpc/server/http_server.go @@ -46,13 +46,7 @@ func DefaultConfig() *Config { // Serve creates a http.Server and calls Serve with the given listener. It // wraps handler to recover panics and limit the request body size. -func Serve( - ctx context.Context, - listener net.Listener, - handler http.Handler, - logger log.Logger, - config *Config, -) error { +func Serve(ctx context.Context, listener net.Listener, handler http.Handler, logger log.Logger, config *Config) error { logger.Info(fmt.Sprintf("Starting RPC HTTP server on %s", listener.Addr())) h := recoverAndLogHandler(MaxBytesHandler(handler, config.MaxBodyBytes), logger) s := &http.Server{ @@ -83,19 +77,14 @@ func Serve( // Serve creates a http.Server and calls ServeTLS with the given listener, // certFile and keyFile. It wraps handler to recover panics and limit the // request body size. -func ServeTLS( - ctx context.Context, - listener net.Listener, - handler http.Handler, - certFile, keyFile string, - logger log.Logger, - config *Config, -) error { - logger.Info(fmt.Sprintf("Starting RPC HTTPS server on %s (cert: %q, key: %q)", - listener.Addr(), certFile, keyFile)) - h := recoverAndLogHandler(MaxBytesHandler(handler, config.MaxBodyBytes), logger) +func ServeTLS(ctx context.Context, listener net.Listener, handler http.Handler, certFile, keyFile string, logger log.Logger, config *Config) error { + logger.Info("Starting RPC HTTPS server", + "listenterAddr", listener.Addr(), + "certFile", certFile, + "keyFile", keyFile) + s := &http.Server{ - Handler: h, + Handler: recoverAndLogHandler(MaxBytesHandler(handler, config.MaxBodyBytes), logger), ReadTimeout: config.ReadTimeout, WriteTimeout: config.WriteTimeout, MaxHeaderBytes: config.MaxHeaderBytes, diff --git a/rpc/jsonrpc/server/http_uri_handler.go b/rpc/jsonrpc/server/http_uri_handler.go index ad0fbb7b3..c755bbaf1 100644 --- a/rpc/jsonrpc/server/http_uri_handler.go +++ b/rpc/jsonrpc/server/http_uri_handler.go @@ -1,13 +1,11 @@ package server import ( - "context" - "encoding" "encoding/hex" "encoding/json" + "errors" "fmt" "net/http" - "reflect" "strconv" "strings" @@ -25,7 +23,7 @@ func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWrit ctx := rpctypes.WithCallInfo(req.Context(), &rpctypes.CallInfo{ HTTPRequest: req, }) - args, err := parseURLParams(ctx, rpcFunc, req) + args, err := parseURLParams(rpcFunc.args, req) if err != nil { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusBadRequest) @@ -33,10 +31,7 @@ func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWrit return } jreq := rpctypes.NewRequest(uriReqID) - outs := rpcFunc.f.Call(args) - - logger.Debug("HTTPRestRPC", "method", req.URL.Path, "args", args, "returns", outs) - result, err := unreflectResult(outs) + result, err := rpcFunc.Call(ctx, args) if err == nil { writeHTTPResponse(w, logger, jreq.MakeResponse(result)) } else { @@ -45,7 +40,7 @@ func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWrit } } -func parseURLParams(ctx context.Context, rf *RPCFunc, req *http.Request) ([]reflect.Value, error) { +func parseURLParams(args []argInfo, req *http.Request) ([]byte, error) { if err := req.ParseForm(); err != nil { return nil, fmt.Errorf("invalid HTTP request: %w", err) } @@ -56,112 +51,43 @@ func parseURLParams(ctx context.Context, rf *RPCFunc, req *http.Request) ([]refl return "", false } - vals := make([]reflect.Value, len(rf.argNames)+1) - vals[0] = reflect.ValueOf(ctx) - for i, name := range rf.argNames { - atype := rf.args[i+1] - - text, ok := getArg(name) + params := make(map[string]interface{}) + for _, arg := range args { + v, ok := getArg(arg.name) if !ok { - vals[i+1] = reflect.Zero(atype) continue } - - val, err := parseArgValue(atype, text) - if err != nil { - return nil, fmt.Errorf("decoding parameter %q: %w", name, err) + if z, err := decodeInteger(v); err == nil { + params[arg.name] = z + } else if b, err := strconv.ParseBool(v); err == nil { + params[arg.name] = b + } else if lc := strings.ToLower(v); strings.HasPrefix(lc, "0x") { + dec, err := hex.DecodeString(lc[2:]) + if err != nil { + return nil, fmt.Errorf("invalid hex string: %w", err) + } else if len(dec) == 0 { + return nil, errors.New("invalid empty hex string") + } + if arg.isBinary { + params[arg.name] = dec + } else { + params[arg.name] = string(dec) + } + } else if isQuotedString(v) { + var dec string + if err := json.Unmarshal([]byte(v), &dec); err != nil { + return nil, fmt.Errorf("invalid quoted string: %w", err) + } + if arg.isBinary { + params[arg.name] = []byte(dec) + } else { + params[arg.name] = dec + } + } else { + params[arg.name] = v } - vals[i+1] = val - } - return vals, nil -} - -func parseArgValue(atype reflect.Type, text string) (reflect.Value, error) { - // Regardless whether the argument is a pointer type, allocate a pointer so - // we can set the computed value. - var out reflect.Value - isPtr := atype.Kind() == reflect.Ptr - if isPtr { - out = reflect.New(atype.Elem()) - } else { - out = reflect.New(atype) - } - - baseType := out.Type().Elem() - if isIntType(baseType) { - // Integral type: Require a base-10 digit string. For compatibility with - // existing use allow quotation marks. - v, err := decodeInteger(text) - if err != nil { - return reflect.Value{}, fmt.Errorf("invalid integer: %w", err) - } - out.Elem().Set(reflect.ValueOf(v).Convert(baseType)) - - } else if isStringOrBytes(baseType) { - // String or byte slice: Check for quotes, hex encoding. - dec, err := decodeString(text) - if err != nil { - return reflect.Value{}, err - } - out.Elem().Set(reflect.ValueOf(dec).Convert(baseType)) - - } else if baseType.Kind() == reflect.Bool { - b, err := strconv.ParseBool(text) - if err != nil { - return reflect.Value{}, fmt.Errorf("invalid boolean: %w", err) - } - out.Elem().Set(reflect.ValueOf(b)) - - } else if out.Type().Implements(textUnmarshalerType) { - s, err := decodeString(text) - if err != nil { - return reflect.Value{}, err - } - v := reflect.New(baseType) - dec := v.Interface().(encoding.TextUnmarshaler) - if err := dec.UnmarshalText(s); err != nil { - return reflect.Value{}, fmt.Errorf("invalid text: %w", err) - } - out.Elem().Set(v.Elem()) - - } else { - // We don't know how to represent other types. - return reflect.Value{}, fmt.Errorf("unsupported argument type %v", baseType) - } - - // If the argument wants a pointer, return the value as-is, otherwise - // indirect the pointer back off. - if isPtr { - return out, nil - } - return out.Elem(), nil -} - -var ( - uint64Type = reflect.TypeOf(uint64(0)) - textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() -) - -// isIntType reports whether atype is an integer-shaped type. -func isIntType(atype reflect.Type) bool { - switch atype.Kind() { - case reflect.Float32, reflect.Float64: - return false - default: - return atype.ConvertibleTo(uint64Type) - } -} - -// isStringOrBytes reports whether atype is a string or []byte. -func isStringOrBytes(atype reflect.Type) bool { - switch atype.Kind() { - case reflect.String: - return true - case reflect.Slice: - return atype.Elem().Kind() == reflect.Uint8 - default: - return false } + return json.Marshal(params) } // isQuotedString reports whether s is enclosed in double quotes. @@ -177,19 +103,3 @@ func decodeInteger(s string) (int64, error) { } return strconv.ParseInt(s, 10, 64) } - -// decodeString decodes s into a byte slice. If s has an 0x prefix, it is -// treated as a hex-encoded string. If it is "double quoted" it is treated as a -// JSON string value. Otherwise, s is converted to bytes directly. -func decodeString(s string) ([]byte, error) { - if lc := strings.ToLower(s); strings.HasPrefix(lc, "0x") { - return hex.DecodeString(lc[2:]) - } else if isQuotedString(s) { - var dec string - if err := json.Unmarshal([]byte(s), &dec); err != nil { - return nil, fmt.Errorf("invalid quoted string: %w", err) - } - return []byte(dec), nil - } - return []byte(s), nil -} diff --git a/rpc/jsonrpc/server/parse_test.go b/rpc/jsonrpc/server/parse_test.go index ee0ab5d79..4a0e92ad1 100644 --- a/rpc/jsonrpc/server/parse_test.go +++ b/rpc/jsonrpc/server/parse_test.go @@ -3,7 +3,6 @@ package server import ( "context" "encoding/json" - "fmt" "net/http" "strconv" "testing" @@ -134,8 +133,12 @@ func TestParseJSONArray(t *testing.T) { } func TestParseJSONRPC(t *testing.T) { - demo := func(ctx context.Context, height int, name string) error { return nil } - call := NewRPCFunc(demo, "height", "name") + type demoArgs struct { + Height int `json:"height,string"` + Name string `json:"name"` + } + demo := func(ctx context.Context, _ *demoArgs) error { return nil } + rfunc := NewRPCFunc(demo) cases := []struct { raw string @@ -156,14 +159,16 @@ func TestParseJSONRPC(t *testing.T) { ctx := context.Background() for idx, tc := range cases { i := strconv.Itoa(idx) - vals, err := parseParams(ctx, call, []byte(tc.raw)) + vals, err := rfunc.parseParams(ctx, []byte(tc.raw)) if tc.fail { assert.Error(t, err, i) } else { assert.NoError(t, err, "%s: %+v", i, err) - if assert.Equal(t, 3, len(vals), i) { // ctx, height, name - assert.Equal(t, tc.height, vals[1].Int(), i) - assert.Equal(t, tc.name, vals[2].String(), i) + assert.Equal(t, 2, len(vals), i) + p, ok := vals[1].Interface().(*demoArgs) + if assert.True(t, ok) { + assert.Equal(t, tc.height, int64(p.Height), i) + assert.Equal(t, tc.name, p.Name, i) } } @@ -171,50 +176,153 @@ func TestParseJSONRPC(t *testing.T) { } func TestParseURI(t *testing.T) { - demo := func(ctx context.Context, height int, name string) error { return nil } - call := NewRPCFunc(demo, "height", "name") + // URI parameter parsing happens in two phases: + // + // Phase 1 swizzles the query parameters into JSON. The result of this + // phase must be valid JSON, but may fail the second stage. + // + // Phase 2 decodes the JSON to obtain the actual arguments. A failure at + // this stage means the JSON is not compatible with the target. - cases := []struct { - raw []string - height int64 - name string - fail bool - }{ - // can parse numbers unquoted and strings quoted - {[]string{"7", `"flew"`}, 7, "flew", false}, - {[]string{"22", `"john"`}, 22, "john", false}, - {[]string{"-10", `"bob"`}, -10, "bob", false}, - // can parse numbers quoted, too - {[]string{`"7"`, `"flew"`}, 7, "flew", false}, - {[]string{`"-10"`, `"bob"`}, -10, "bob", false}, - // can parse strings hex-escaped, in either case - {[]string{`-9`, `0x626f62`}, -9, "bob", false}, - {[]string{`-9`, `0X646F7567`}, -9, "doug", false}, - // can parse strings unquoted (as per OpenAPI docs) - {[]string{`0`, `hey you`}, 0, "hey you", false}, - // fail for invalid numbers, strings, hex - {[]string{`"-xx"`, `bob`}, 0, "", true}, // bad number - {[]string{`"95""`, `"bob`}, 0, "", true}, // bad string - {[]string{`15`, `0xa`}, 0, "", true}, // bad hex - } - for idx, tc := range cases { - i := strconv.Itoa(idx) - // data := []byte(tc.raw) - url := fmt.Sprintf( - "test.com/method?height=%v&name=%v", - tc.raw[0], tc.raw[1]) - req, err := http.NewRequest("GET", url, nil) - assert.NoError(t, err) - vals, err := parseURLParams(context.Background(), call, req) - if tc.fail { - assert.Error(t, err, i) - } else { - assert.NoError(t, err, "%s: %+v", i, err) - if assert.Equal(t, 3, len(vals), i) { - assert.Equal(t, tc.height, vals[1].Int(), i) - assert.Equal(t, tc.name, vals[2].String(), i) - } + t.Run("Swizzle", func(t *testing.T) { + tests := []struct { + name string + url string + args []argInfo + want string + fail bool + }{ + { + name: "quoted numbers and strings", + url: `http://localhost?num="7"&str="flew"&neg="-10"`, + args: []argInfo{{name: "neg"}, {name: "num"}, {name: "str"}, {name: "other"}}, + want: `{"neg":-10,"num":7,"str":"flew"}`, + }, + { + name: "unquoted numbers and strings", + url: `http://localhost?num1=7&str1=cabbage&num2=-199&str2=hey+you`, + args: []argInfo{{name: "num1"}, {name: "num2"}, {name: "str1"}, {name: "str2"}, {name: "other"}}, + want: `{"num1":7,"num2":-199,"str1":"cabbage","str2":"hey you"}`, + }, + { + name: "quoted byte strings", + url: `http://localhost?left="Fahrvergnügen"&right="Applesauce"`, + args: []argInfo{{name: "left", isBinary: true}, {name: "right", isBinary: false}}, + want: `{"left":"RmFocnZlcmduw7xnZW4=","right":"Applesauce"}`, + }, + { + name: "hexadecimal byte strings", + url: `http://localhost?lower=0x626f62&upper=0X646F7567`, + args: []argInfo{{name: "upper", isBinary: true}, {name: "lower", isBinary: false}, {name: "other"}}, + want: `{"lower":"bob","upper":"ZG91Zw=="}`, + }, + { + name: "invalid hex odd length", + url: `http://localhost?bad=0xa`, + args: []argInfo{{name: "bad"}, {name: "superbad"}}, + fail: true, + }, + { + name: "invalid hex empty", + url: `http://localhost?bad=0x`, + args: []argInfo{{name: "bad"}}, + fail: true, + }, + { + name: "invalid quoted string", + url: `http://localhost?bad="double""`, + args: []argInfo{{name: "bad"}}, + fail: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + hreq, err := http.NewRequest("GET", test.url, nil) + if err != nil { + t.Fatalf("NewRequest for %q: %v", test.url, err) + } + + bits, err := parseURLParams(test.args, hreq) + if err != nil && !test.fail { + t.Fatalf("Parse %q: unexpected error: %v", test.url, err) + } else if err == nil && test.fail { + t.Fatalf("Parse %q: got %#q, wanted error", test.url, string(bits)) + } + if got := string(bits); got != test.want { + t.Errorf("Parse %q: got %#q, want %#q", test.url, got, test.want) + } + }) + } + }) + + t.Run("Decode", func(t *testing.T) { + type argValue struct { + Height json.Number `json:"height"` + Name string `json:"name"` + Flag bool `json:"flag"` } - } + echo := NewRPCFunc(func(_ context.Context, arg *argValue) (*argValue, error) { + return arg, nil + }) + + tests := []struct { + name string + url string + fail string + want interface{} + }{ + { + name: "valid all args", + url: `http://localhost?height=235&flag=true&name="bogart"`, + want: &argValue{ + Height: "235", + Flag: true, + Name: "bogart", + }, + }, + { + name: "valid partial args", + url: `http://localhost?height="1987"&name=free+willy`, + want: &argValue{ + Height: "1987", + Name: "free willy", + }, + }, + { + name: "invalid quoted number", + url: `http://localhost?height="-xx"`, + fail: "invalid number literal", + }, + { + name: "invalid unquoted number", + url: `http://localhost?height=25*q`, + fail: "invalid number literal", + }, + { + name: "invalid boolean", + url: `http://localhost?flag="garbage"`, + fail: "flag of type bool", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + hreq, err := http.NewRequest("GET", test.url, nil) + if err != nil { + t.Fatalf("NewRequest for %q: %v", test.url, err) + } + bits, err := parseURLParams(echo.args, hreq) + if err != nil { + t.Fatalf("Parse %#q: unexpected error: %v", test.url, err) + } + rsp, err := echo.Call(context.Background(), bits) + if test.want != nil { + assert.Equal(t, test.want, rsp) + } + if test.fail != "" { + assert.ErrorContains(t, err, test.fail) + } + }) + } + }) } diff --git a/rpc/jsonrpc/server/rpc_func.go b/rpc/jsonrpc/server/rpc_func.go index a58973c6e..8eba28728 100644 --- a/rpc/jsonrpc/server/rpc_func.go +++ b/rpc/jsonrpc/server/rpc_func.go @@ -1,13 +1,17 @@ package server import ( + "bytes" "context" + "encoding/json" "errors" "fmt" "net/http" "reflect" + "strings" "github.com/tendermint/tendermint/libs/log" + rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types" ) // RegisterRPCFuncs adds a route to mux for each non-websocket function in the @@ -28,27 +32,106 @@ func RegisterRPCFuncs(mux *http.ServeMux, funcMap map[string]*RPCFunc, logger lo // RPCFunc contains the introspected type information for a function. type RPCFunc struct { - f reflect.Value // underlying rpc function - args []reflect.Type // type of each function arg - returns []reflect.Type // type of each return arg - argNames []string // name of each argument - ws bool // websocket only + f reflect.Value // underlying rpc function + param reflect.Type // the parameter struct, or nil + result reflect.Type // the non-error result type, or nil + args []argInfo // names and type information (for URL decoding) + ws bool // websocket only +} + +// argInfo records the name of a field, along with a bit to tell whether the +// value of the field requires binary data, having underlying type []byte. The +// flag is needed when decoding URL parameters, where we permit quoted strings +// to be passed for either argument type. +type argInfo struct { + name string + isBinary bool // value wants binary data +} + +// Call parses the given JSON parameters and calls the function wrapped by rf +// with the resulting argument value. It reports an error if parameter parsing +// fails, otherwise it returns the result from the wrapped function. +func (rf *RPCFunc) Call(ctx context.Context, params json.RawMessage) (interface{}, error) { + args, err := rf.parseParams(ctx, params) + if err != nil { + return nil, err + } + returns := rf.f.Call(args) + + // Case 1: There is no non-error result type. + if rf.result == nil { + if oerr := returns[0].Interface(); oerr != nil { + return nil, oerr.(error) + } + return nil, nil + } + + // Case 2: There is a non-error result. + if oerr := returns[1].Interface(); oerr != nil { + // In case of error, report the error and ignore the result. + return nil, oerr.(error) + } + return returns[0].Interface(), nil +} + +// parseParams parses the parameters of a JSON-RPC request and returns the +// corresponding argument values. On success, the first argument value will be +// the value of ctx. +func (rf *RPCFunc) parseParams(ctx context.Context, params json.RawMessage) ([]reflect.Value, error) { + // If rf does not accept parameters, there is no decoding to do, but verify + // that no parameters were passed. + if rf.param == nil { + if !isNullOrEmpty(params) { + return nil, invalidParamsError("no parameters accepted for this method") + } + return []reflect.Value{reflect.ValueOf(ctx)}, nil + } + bits, err := rf.adjustParams(params) + if err != nil { + return nil, invalidParamsError(err.Error()) + } + arg := reflect.New(rf.param) + if err := json.Unmarshal(bits, arg.Interface()); err != nil { + return nil, invalidParamsError(err.Error()) + } + return []reflect.Value{reflect.ValueOf(ctx), arg}, nil +} + +// adjustParams checks whether data is encoded as a JSON array, and if so +// adjusts the values to match the corresponding parameter names. +func (rf *RPCFunc) adjustParams(data []byte) (json.RawMessage, error) { + base := bytes.TrimSpace(data) + if bytes.HasPrefix(base, []byte("[")) { + var args []json.RawMessage + if err := json.Unmarshal(base, &args); err != nil { + return nil, err + } else if len(args) != len(rf.args) { + return nil, fmt.Errorf("got %d arguments, want %d", len(args), len(rf.args)) + } + m := make(map[string]json.RawMessage) + for i, arg := range args { + m[rf.args[i].name] = arg + } + return json.Marshal(m) + } else if bytes.HasPrefix(base, []byte("{")) || bytes.Equal(base, []byte("null")) { + return base, nil + } + return nil, errors.New("parameters must be an object or an array") + } // NewRPCFunc constructs an RPCFunc for f, which must be a function whose type // signature matches one of these schemes: // -// func(context.Context, T1, T2, ...) error -// func(context.Context, T1, T2, ...) (R, error) +// func(context.Context) error +// func(context.Context) (R, error) +// func(context.Context, *T) error +// func(context.Context, *T) (R, error) // -// for arbitrary types T_i and R. The number of argNames must exactly match the -// number of non-context arguments to f. Otherwise, NewRPCFunc panics. -// -// The parameter names given are used to map JSON object keys to the -// corresonding parameter of the function. The names do not need to match the -// declared names, but must match what the client sends in a request. -func NewRPCFunc(f interface{}, argNames ...string) *RPCFunc { - rf, err := newRPCFunc(f, argNames) +// for an arbitrary struct type T and type R. NewRPCFunc will panic if f does +// not have one of these forms. +func NewRPCFunc(f interface{}) *RPCFunc { + rf, err := newRPCFunc(f) if err != nil { panic("invalid RPC function: " + err.Error()) } @@ -57,8 +140,8 @@ func NewRPCFunc(f interface{}, argNames ...string) *RPCFunc { // NewWSRPCFunc behaves as NewRPCFunc, but marks the resulting function for use // via websocket. -func NewWSRPCFunc(f interface{}, argNames ...string) *RPCFunc { - rf := NewRPCFunc(f, argNames...) +func NewWSRPCFunc(f interface{}) *RPCFunc { + rf := NewRPCFunc(f) rf.ws = true return rf } @@ -69,7 +152,7 @@ var ( ) // newRPCFunc constructs an RPCFunc for f. See the comment at NewRPCFunc. -func newRPCFunc(f interface{}, argNames []string) (*RPCFunc, error) { +func newRPCFunc(f interface{}) (*RPCFunc, error) { if f == nil { return nil, errors.New("nil function") } @@ -80,49 +163,85 @@ func newRPCFunc(f interface{}, argNames []string) (*RPCFunc, error) { return nil, errors.New("not a function") } + var ptype reflect.Type ft := fv.Type() - if np := ft.NumIn(); np == 0 { + if np := ft.NumIn(); np == 0 || np > 2 { return nil, errors.New("wrong number of parameters") } else if ft.In(0) != ctxType { return nil, errors.New("first parameter is not context.Context") - } else if np-1 != len(argNames) { - return nil, fmt.Errorf("have %d names for %d parameters", len(argNames), np-1) + } else if np == 2 { + ptype = ft.In(1) + if ptype.Kind() != reflect.Ptr { + return nil, errors.New("parameter type is not a pointer") + } + ptype = ptype.Elem() + if ptype.Kind() != reflect.Struct { + return nil, errors.New("parameter type is not a struct") + } } + var rtype reflect.Type if no := ft.NumOut(); no < 1 || no > 2 { return nil, errors.New("wrong number of results") } else if ft.Out(no-1) != errType { return nil, errors.New("last result is not error") + } else if no == 2 { + rtype = ft.Out(0) } - args := make([]reflect.Type, ft.NumIn()) - for i := 0; i < ft.NumIn(); i++ { - args[i] = ft.In(i) - } - outs := make([]reflect.Type, ft.NumOut()) - for i := 0; i < ft.NumOut(); i++ { - outs[i] = ft.Out(i) + var args []argInfo + if ptype != nil { + for i := 0; i < ptype.NumField(); i++ { + field := ptype.Field(i) + if tag := strings.SplitN(field.Tag.Get("json"), ",", 2)[0]; tag != "" && tag != "-" { + args = append(args, argInfo{ + name: tag, + isBinary: isByteArray(field.Type), + }) + } else if tag == "-" { + // If the tag is "-" the field should explicitly be ignored, even + // if it is otherwise eligible. + } else if field.IsExported() && !field.Anonymous { + // Examples: Name → name, MaxEffort → maxEffort. + // Note that this is an aesthetic choice; the standard decoder will + // match without regard to case anyway. + name := strings.ToLower(field.Name[:1]) + field.Name[1:] + args = append(args, argInfo{ + name: name, + isBinary: isByteArray(field.Type), + }) + } + } } + return &RPCFunc{ - f: fv, - args: args, - returns: outs, - argNames: argNames, + f: fv, + param: ptype, + result: rtype, + args: args, }, nil } -//------------------------------------------------------------- - -// NOTE: assume returns is result struct and error. If error is not nil, return it -func unreflectResult(returns []reflect.Value) (interface{}, error) { - errV := returns[1] - if err, ok := errV.Interface().(error); ok && err != nil { - return nil, err +// invalidParamsError returns an RPC invalid parameters error with the given +// detail message. +func invalidParamsError(msg string, args ...interface{}) error { + return &rpctypes.RPCError{ + Code: int(rpctypes.CodeInvalidParams), + Message: rpctypes.CodeInvalidParams.String(), + Data: fmt.Sprintf(msg, args...), } - rv := returns[0] - // the result is a registered interface, - // we need a pointer to it so we can marshal with type byte - rvp := reflect.New(rv.Type()) - rvp.Elem().Set(rv) - return rvp.Interface(), nil +} + +// isNullOrEmpty reports whether params is either itself empty or represents an +// empty parameter (null, empty object, or empty array). +func isNullOrEmpty(params json.RawMessage) bool { + return len(params) == 0 || + bytes.Equal(params, []byte("null")) || + bytes.Equal(params, []byte("{}")) || + bytes.Equal(params, []byte("[]")) +} + +// isByteArray reports whether t is (equivalent to) []byte. +func isByteArray(t reflect.Type) bool { + return t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 } diff --git a/rpc/jsonrpc/server/ws_handler.go b/rpc/jsonrpc/server/ws_handler.go index 3fc86b86f..3a259757b 100644 --- a/rpc/jsonrpc/server/ws_handler.go +++ b/rpc/jsonrpc/server/ws_handler.go @@ -36,11 +36,7 @@ type WebsocketManager struct { // NewWebsocketManager returns a new WebsocketManager that passes a map of // functions, connection options and logger to new WS connections. -func NewWebsocketManager( - logger log.Logger, - funcMap map[string]*RPCFunc, - wsConnOptions ...func(*wsConnection), -) *WebsocketManager { +func NewWebsocketManager(logger log.Logger, funcMap map[string]*RPCFunc, wsConnOptions ...func(*wsConnection)) *WebsocketManager { return &WebsocketManager{ funcMap: funcMap, Upgrader: websocket.Upgrader{ @@ -137,12 +133,7 @@ type wsConnection struct { // description of how to configure ping period and pong wait time. NOTE: if the // write buffer is full, pongs may be dropped, which may cause clients to // disconnect. see https://github.com/gorilla/websocket/issues/97 -func newWSConnection( - baseConn *websocket.Conn, - funcMap map[string]*RPCFunc, - logger log.Logger, - options ...func(*wsConnection), -) *wsConnection { +func newWSConnection(baseConn *websocket.Conn, funcMap map[string]*RPCFunc, logger log.Logger, options ...func(*wsConnection)) *wsConnection { wsc := &wsConnection{ Logger: logger, remoteAddr: baseConn.RemoteAddr().String(), @@ -340,22 +331,8 @@ func (wsc *wsConnection) readRoutine(ctx context.Context) { RPCRequest: &request, WSConn: wsc, }) - args, err := parseParams(fctx, rpcFunc, request.Params) - if err != nil { - if err := wsc.WriteRPCResponse(writeCtx, request.MakeErrorf(rpctypes.CodeInvalidParams, - "converting JSON parameters: %v", err)); err != nil { - wsc.Logger.Error("error writing RPC response", "err", err) - } - continue - } - - returns := rpcFunc.f.Call(args) - - // TODO: Need to encode args/returns to string if we want to log them - wsc.Logger.Info("WSJSONRPC", "method", request.Method) - var resp rpctypes.RPCResponse - result, err := unreflectResult(returns) + result, err := rpcFunc.Call(fctx, request.Params) if err == nil { resp = request.MakeResponse(result) } else { diff --git a/rpc/jsonrpc/server/ws_handler_test.go b/rpc/jsonrpc/server/ws_handler_test.go index ae73a953b..ce1bcd973 100644 --- a/rpc/jsonrpc/server/ws_handler_test.go +++ b/rpc/jsonrpc/server/ws_handler_test.go @@ -2,6 +2,7 @@ package server import ( "context" + "encoding/json" "net/http" "net/http/httptest" "testing" @@ -44,8 +45,12 @@ func TestWebsocketManagerHandler(t *testing.T) { } func newWSServer(t *testing.T, logger log.Logger) *httptest.Server { + type args struct { + S string `json:"s"` + I json.Number `json:"i"` + } funcMap := map[string]*RPCFunc{ - "c": NewWSRPCFunc(func(ctx context.Context, s string, i int) (string, error) { return "foo", nil }, "s", "i"), + "c": NewWSRPCFunc(func(context.Context, *args) (string, error) { return "foo", nil }), } wm := NewWebsocketManager(logger, funcMap) diff --git a/rpc/jsonrpc/test/main.go b/rpc/jsonrpc/test/main.go index 07a3b28f3..2ed013c17 100644 --- a/rpc/jsonrpc/test/main.go +++ b/rpc/jsonrpc/test/main.go @@ -14,7 +14,7 @@ import ( ) var routes = map[string]*rpcserver.RPCFunc{ - "hello_world": rpcserver.NewRPCFunc(HelloWorld, "name", "num"), + "hello_world": rpcserver.NewRPCFunc(HelloWorld), } func HelloWorld(ctx context.Context, name string, num int) (Result, error) { diff --git a/scripts/abci-gen.sh b/scripts/abci-gen.sh deleted file mode 100755 index fe3728ad4..000000000 --- a/scripts/abci-gen.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash - -# This file was added during development of ABCI++. This file is a script to allow -# the intermediate proto files to be built while active development proceeds -# on ABCI++. -# This file should be removed when work on ABCI++ is complete. -# For more information, see https://github.com/tendermint/tendermint/issues/8066. -set -euo pipefail - -cp ./proto/tendermint/abci/types.proto.intermediate ./proto/tendermint/abci/types.proto -cp ./proto/tendermint/types/types.proto.intermediate ./proto/tendermint/types/types.proto - -MODNAME="$(go list -m)" -find ./proto/tendermint -name '*.proto' -not -path "./proto/tendermint/abci/types.proto" \ - -exec sh ./scripts/protopackage.sh {} "$MODNAME" ';' - -sh ./scripts/protopackage.sh ./proto/tendermint/abci/types.proto $MODNAME "abci/types" - -make proto-gen - -mv ./proto/tendermint/abci/types.pb.go ./abci/types - -echo "proto files have been compiled" - -echo "checking out copied files" - -find proto/tendermint/ -name '*.proto' -not -path "*.intermediate"\ - | xargs -I {} git checkout {} - -find proto/tendermint/ -name '*.pb.go' \ - | xargs -I {} git checkout {} diff --git a/scripts/confix/condiff/condiff.go b/scripts/confix/condiff/condiff.go new file mode 100644 index 000000000..6b11e4e2c --- /dev/null +++ b/scripts/confix/condiff/condiff.go @@ -0,0 +1,152 @@ +// Program condiff performs a keyspace diff on two TOML documents. +package main + +import ( + "context" + "flag" + "fmt" + "io" + "log" + "os" + "path/filepath" + "sort" + "strings" + + "github.com/creachadair/tomledit" + "github.com/creachadair/tomledit/parser" + "github.com/creachadair/tomledit/transform" +) + +var ( + doDesnake = flag.Bool("desnake", false, "Convert snake_case to kebab-case before comparing") +) + +func init() { + flag.Usage = func() { + fmt.Fprintf(os.Stderr, `Usage: %[1]s [options] f1 f2 + +Diff the keyspaces of the TOML documents in files f1 and f2. +The output prints one line per key that differs: + + -S name -- section exists in f1 but not f2 + +S name -- section exists in f2 but not f1 + -M name -- mapping exists in f1 but not f2 + +M name -- mapping exists in f2 but not f1 + +Comments, order, and values are ignored for comparison purposes. + +Options: +`, filepath.Base(os.Args[0])) + flag.PrintDefaults() + } +} + +func main() { + flag.Parse() + + if flag.NArg() != 2 { + log.Fatalf("Usage: %[1]s ", filepath.Base(os.Args[0])) + } + lhs := mustParse(flag.Arg(0)) + rhs := mustParse(flag.Arg(1)) + if *doDesnake { + log.Printf("Converting all names from snake_case to kebab-case") + fix := transform.SnakeToKebab() + _ = fix(context.Background(), lhs) + _ = fix(context.Background(), rhs) + } + diffDocs(os.Stdout, lhs, rhs) +} + +func mustParse(path string) *tomledit.Document { + f, err := os.Open(path) + if err != nil { + log.Fatalf("Opening TOML input: %v", err) + } + defer f.Close() + doc, err := tomledit.Parse(f) + if err != nil { + log.Fatalf("Parsing %q: %v", path, err) + } + return doc +} + +func allKeys(s *tomledit.Section) []string { + var keys []string + s.Scan(func(key parser.Key, _ *tomledit.Entry) bool { + keys = append(keys, key.String()) + return true + }) + return keys +} + +const ( + delSection = "-S" + delMapping = "-M" + addSection = "+S" + addMapping = "+M" + + delMapSep = "\n" + delMapping + " " + addMapSep = "\n" + addMapping + " " +) + +func diffDocs(w io.Writer, lhs, rhs *tomledit.Document) { + diffSections(w, lhs.Global, rhs.Global) + lsec, rsec := lhs.Sections, rhs.Sections + transform.SortSectionsByName(lsec) + transform.SortSectionsByName(rsec) + + i, j := 0, 0 + for i < len(lsec) && j < len(rsec) { + if lsec[i].Name.Before(rsec[j].Name) { + fmt.Fprintln(w, delSection, lsec[i].Name) + fmt.Fprintln(w, delMapping, strings.Join(allKeys(lsec[i]), delMapSep)) + i++ + } else if rsec[j].Name.Before(lsec[i].Name) { + fmt.Fprintln(w, addSection, rsec[j].Name) + fmt.Fprintln(w, addMapping, strings.Join(allKeys(rsec[j]), addMapSep)) + j++ + } else { + diffSections(w, lsec[i], rsec[j]) + i++ + j++ + } + } + for ; i < len(lsec); i++ { + fmt.Fprintln(w, delSection, lsec[i].Name) + fmt.Fprintln(w, delMapping, strings.Join(allKeys(lsec[i]), delMapSep)) + } + for ; j < len(rsec); j++ { + fmt.Fprintln(w, addSection, rsec[j].Name) + fmt.Fprintln(w, addMapping, strings.Join(allKeys(rsec[j]), addMapSep)) + } +} + +func diffSections(w io.Writer, lhs, rhs *tomledit.Section) { + diffKeys(w, allKeys(lhs), allKeys(rhs)) +} + +func diffKeys(w io.Writer, lhs, rhs []string) { + sort.Strings(lhs) + sort.Strings(rhs) + + i, j := 0, 0 + for i < len(lhs) && j < len(rhs) { + if lhs[i] < rhs[j] { + fmt.Fprintln(w, delMapping, lhs[i]) + i++ + } else if lhs[i] > rhs[j] { + fmt.Fprintln(w, addMapping, rhs[j]) + j++ + } else { + i++ + j++ + } + } + for ; i < len(lhs); i++ { + fmt.Fprintln(w, delMapping, lhs[i]) + } + for ; j < len(rhs); j++ { + fmt.Fprintln(w, addMapping, rhs[j]) + } +} diff --git a/scripts/confix/confix.go b/scripts/confix/confix.go new file mode 100644 index 000000000..6677f0b49 --- /dev/null +++ b/scripts/confix/confix.go @@ -0,0 +1,163 @@ +// Program confix applies fixes to a Tendermint TOML configuration file to +// update a file created with an older version of Tendermint to a compatible +// format for a newer version. +package main + +import ( + "bytes" + "context" + "errors" + "flag" + "fmt" + "log" + "os" + "path/filepath" + + "github.com/creachadair/atomicfile" + "github.com/creachadair/tomledit" + "github.com/creachadair/tomledit/transform" + "github.com/spf13/viper" + "github.com/tendermint/tendermint/config" +) + +func init() { + flag.Usage = func() { + fmt.Fprintf(os.Stderr, `Usage: %[1]s -config [-out ] + +Modify the contents of the specified -config TOML file to update the names, +locations, and values of configuration settings to the current configuration +layout. The output is written to -out, or to stdout. + +It is valid to set -config and -out to the same path. In that case, the file will +be modified in-place. In case of any error in updating the file, no output is +written. + +Options: +`, filepath.Base(os.Args[0])) + flag.PrintDefaults() + } +} + +var ( + configPath = flag.String("config", "", "Config file path (required)") + outPath = flag.String("out", "", "Output file path (default stdout)") +) + +func main() { + flag.Parse() + if *configPath == "" { + log.Fatal("You must specify a non-empty -config path") + } + + doc, err := LoadConfig(*configPath) + if err != nil { + log.Fatalf("Loading config: %v", err) + } + + ctx := transform.WithLogWriter(context.Background(), os.Stderr) + if err := ApplyFixes(ctx, doc); err != nil { + log.Fatalf("Updating %q: %v", *configPath, err) + } + + var buf bytes.Buffer + if err := tomledit.Format(&buf, doc); err != nil { + log.Fatalf("Formatting config: %v", err) + } + + // Verify that Tendermint can parse the results after our edits. + if err := CheckValid(buf.Bytes()); err != nil { + log.Fatalf("Updated config is invalid: %v", err) + } + + if *outPath == "" { + os.Stdout.Write(buf.Bytes()) + } else if err := atomicfile.WriteData(*outPath, buf.Bytes(), 0600); err != nil { + log.Fatalf("Writing output: %v", err) + } +} + +// ApplyFixes transforms doc and reports whether it succeeded. +func ApplyFixes(ctx context.Context, doc *tomledit.Document) error { + // Check what version of Tendermint might have created this config file, as + // a safety check for the updates we are about to make. + tmVersion := GuessConfigVersion(doc) + if tmVersion == vUnknown { + return errors.New("cannot tell what Tendermint version created this config") + } else if tmVersion < v34 || tmVersion > v36 { + // TODO(creachadair): Add in rewrites for older versions. This will + // require some digging to discover what the changes were. The upgrade + // instructions do not give specifics. + return fmt.Errorf("unable to update version %s config", tmVersion) + } + return plan.Apply(ctx, doc) +} + +// LoadConfig loads and parses the TOML document from path. +func LoadConfig(path string) (*tomledit.Document, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + return tomledit.Parse(f) +} + +const ( + vUnknown = "" + v32 = "v0.32" + v33 = "v0.33" + v34 = "v0.34" + v35 = "v0.35" + v36 = "v0.36" +) + +// GuessConfigVersion attempts to figure out which version of Tendermint +// created the specified config document. It returns "" if the creating version +// cannot be determined, otherwise a string of the form "vX.YY". +func GuessConfigVersion(doc *tomledit.Document) string { + hasDisableWS := doc.First("rpc", "experimental-disable-websocket") != nil + hasUseLegacy := doc.First("p2p", "use-legacy") != nil // v0.35 only + if hasDisableWS && !hasUseLegacy { + return v36 + } + + hasBlockSync := transform.FindTable(doc, "blocksync") != nil // add: v0.35 + hasStateSync := transform.FindTable(doc, "statesync") != nil // add: v0.34 + if hasBlockSync && hasStateSync { + return v35 + } else if hasStateSync { + return v34 + } + + hasIndexKeys := doc.First("tx_index", "index_keys") != nil // add: v0.33 + hasIndexTags := doc.First("tx_index", "index_tags") != nil // rem: v0.33 + if hasIndexKeys && !hasIndexTags { + return v33 + } + + hasFastSync := transform.FindTable(doc, "fastsync") != nil // add: v0.32 + if hasIndexTags && hasFastSync { + return v32 + } + + // Something older, probably. + return vUnknown +} + +// CheckValid checks whether the specified config appears to be a valid +// Tendermint config file. This emulates how the node loads the config. +func CheckValid(data []byte) error { + v := viper.New() + v.SetConfigType("toml") + + if err := v.ReadConfig(bytes.NewReader(data)); err != nil { + return fmt.Errorf("reading config: %w", err) + } + + var cfg config.Config + if err := v.Unmarshal(&cfg); err != nil { + return fmt.Errorf("decoding config: %w", err) + } + + return cfg.ValidateBasic() +} diff --git a/scripts/confix/confix_test.go b/scripts/confix/confix_test.go new file mode 100644 index 000000000..ec258f4ca --- /dev/null +++ b/scripts/confix/confix_test.go @@ -0,0 +1,99 @@ +package main_test + +import ( + "bytes" + "context" + "strings" + "testing" + + "github.com/creachadair/tomledit" + "github.com/google/go-cmp/cmp" + + confix "github.com/tendermint/tendermint/scripts/confix" +) + +func mustParseConfig(t *testing.T, path string) *tomledit.Document { + doc, err := confix.LoadConfig(path) + if err != nil { + t.Fatalf("Loading config: %v", err) + } + return doc +} + +func TestGuessConfigVersion(t *testing.T) { + tests := []struct { + path, want string + }{ + {"testdata/non-config.toml", ""}, + {"testdata/v30-config.toml", ""}, + {"testdata/v31-config.toml", ""}, + {"testdata/v32-config.toml", "v0.32"}, + {"testdata/v33-config.toml", "v0.33"}, + {"testdata/v34-config.toml", "v0.34"}, + {"testdata/v35-config.toml", "v0.35"}, + {"testdata/v36-config.toml", "v0.36"}, + } + for _, test := range tests { + t.Run(test.path, func(t *testing.T) { + got := confix.GuessConfigVersion(mustParseConfig(t, test.path)) + if got != test.want { + t.Errorf("Wrong version: got %q, want %q", got, test.want) + } + }) + } +} + +func TestApplyFixes(t *testing.T) { + ctx := context.Background() + + t.Run("Unknown", func(t *testing.T) { + err := confix.ApplyFixes(ctx, mustParseConfig(t, "testdata/v31-config.toml")) + if err == nil || !strings.Contains(err.Error(), "cannot tell what Tendermint version") { + t.Error("ApplyFixes succeeded, but should have failed for an unknown version") + } + }) + t.Run("TooOld", func(t *testing.T) { + err := confix.ApplyFixes(ctx, mustParseConfig(t, "testdata/v33-config.toml")) + if err == nil || !strings.Contains(err.Error(), "unable to update version v0.33 config") { + t.Errorf("ApplyFixes: got %v, want version error", err) + } + }) + t.Run("OK", func(t *testing.T) { + doc := mustParseConfig(t, "testdata/v34-config.toml") + if err := confix.ApplyFixes(ctx, doc); err != nil { + t.Fatalf("ApplyFixes: unexpected error: %v", err) + } + + t.Run("Fixpoint", func(t *testing.T) { + // Verify that reapplying fixes to the same config succeeds, and does not + // make any additional changes. + var before bytes.Buffer + if err := tomledit.Format(&before, doc); err != nil { + t.Fatalf("Formatting document: %v", err) + } + if err := confix.CheckValid(before.Bytes()); err != nil { + t.Fatalf("Validating output: %v", err) + } + want := before.String() + + // Re-parse the output from the first round of transformations. + doc2, err := tomledit.Parse(&before) + if err != nil { + t.Fatalf("Parsing fixed output: %v", err) + } + if err := confix.ApplyFixes(ctx, doc2); err != nil { + t.Fatalf("ApplyFixes: unexpected error: %v", err) + } + + var after bytes.Buffer + if err := tomledit.Format(&after, doc2); err != nil { + t.Fatalf("Formatting document: %v", err) + } + got := after.String() + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("Reapplied fixes changed something: (-want, +got)\n%s", diff) + } + }) + }) +} diff --git a/scripts/confix/plan.go b/scripts/confix/plan.go new file mode 100644 index 000000000..653bca9fd --- /dev/null +++ b/scripts/confix/plan.go @@ -0,0 +1,225 @@ +package main + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/creachadair/tomledit" + "github.com/creachadair/tomledit/parser" + "github.com/creachadair/tomledit/transform" +) + +// The plan is the sequence of transformation steps that should be applied, in +// the given order, to convert a configuration file to be compatible with the +// current version of the config grammar. +// +// Transformation steps are specific to the target config version. For this +// reason, you must exercise caution when backporting changes to this script +// into older releases. +var plan = transform.Plan{ + { + // Since https://github.com/tendermint/tendermint/pull/5777. + Desc: "Rename everything from snake_case to kebab-case", + T: transform.SnakeToKebab(), + }, + { + // [fastsync] renamed in https://github.com/tendermint/tendermint/pull/6896. + // [blocksync] removed in https://github.com/tendermint/tendermint/pull/7159. + Desc: "Remove [fastsync] and [blocksync] sections", + T: transform.Func(func(_ context.Context, doc *tomledit.Document) error { + doc.First("fast-sync").Remove() + transform.FindTable(doc, "fastsync").Remove() + transform.FindTable(doc, "blocksync").Remove() + return nil + }), + ErrorOK: true, + }, + { + // Since https://github.com/tendermint/tendermint/pull/6241. + Desc: `Add top-level mode setting (default "full")`, + T: transform.EnsureKey(nil, &parser.KeyValue{ + Block: parser.Comments{"Mode of Node: full | validator | seed"}, + Name: parser.Key{"mode"}, + Value: parser.MustValue(`"full"`), + }), + ErrorOK: true, + }, + { + // Since https://github.com/tendermint/tendermint/pull/7121. + Desc: "Remove gRPC settings from the [rpc] section", + T: transform.Func(func(_ context.Context, doc *tomledit.Document) error { + doc.First("rpc", "grpc-laddr").Remove() + doc.First("rpc", "grpc-max-open-connections").Remove() + return nil + }), + }, + { + // Since https://github.com/tendermint/tendermint/pull/8217. + Desc: "Remove per-node consensus timeouts (converted to consensus parameters)", + T: transform.Remove( + parser.Key{"consensus", "skip-timeout-commit"}, + parser.Key{"consensus", "timeout-commit"}, + parser.Key{"consensus", "timeout-precommit"}, + parser.Key{"consensus", "timeout-precommit-delta"}, + parser.Key{"consensus", "timeout-prevote"}, + parser.Key{"consensus", "timeout-prevote-delta"}, + parser.Key{"consensus", "timeout-propose"}, + parser.Key{"consensus", "timeout-propose-delta"}, + ), + ErrorOK: true, + }, + { + // Removed wal-dir: https://github.com/tendermint/tendermint/pull/6396. + // Removed version: https://github.com/tendermint/tendermint/pull/7171. + Desc: "Remove vestigial mempool.wal-dir settings", + T: transform.Remove( + parser.Key{"mempool", "wal-dir"}, + parser.Key{"mempool", "version"}, + ), + ErrorOK: true, + }, + { + // Since https://github.com/tendermint/tendermint/pull/6323. + Desc: "Add new [p2p] queue-type setting", + T: transform.EnsureKey(parser.Key{"p2p"}, &parser.KeyValue{ + Block: parser.Comments{"Select the p2p internal queue"}, + Name: parser.Key{"queue-type"}, + Value: parser.MustValue(`"priority"`), + }), + ErrorOK: true, + }, + { + // Since https://github.com/tendermint/tendermint/pull/6353. + Desc: "Add [p2p] connection count and rate limit settings", + T: transform.Func(func(_ context.Context, doc *tomledit.Document) error { + tab := transform.FindTable(doc, "p2p") + if tab == nil { + return errors.New("p2p table not found") + } + transform.InsertMapping(tab.Section, &parser.KeyValue{ + Block: parser.Comments{"Maximum number of connections (inbound and outbound)."}, + Name: parser.Key{"max-connections"}, + Value: parser.MustValue("64"), + }, false) + transform.InsertMapping(tab.Section, &parser.KeyValue{ + Block: parser.Comments{ + "Rate limits the number of incoming connection attempts per IP address.", + }, + Name: parser.Key{"max-incoming-connection-attempts"}, + Value: parser.MustValue("100"), + }, false) + return nil + }), + }, + { + // Added "chunk-fetchers" https://github.com/tendermint/tendermint/pull/6566. + // This value was backported into v0.34.11 (modulo casing). + // Renamed to "fetchers" https://github.com/tendermint/tendermint/pull/6587. + Desc: "Rename statesync.chunk-fetchers to statesync.fetchers", + T: transform.Func(func(ctx context.Context, doc *tomledit.Document) error { + // If the key already exists, rename it preserving its value. + if found := doc.First("statesync", "chunk-fetchers"); found != nil { + found.KeyValue.Name = parser.Key{"fetchers"} + return nil + } + + // Otherwise, add it. + return transform.EnsureKey(parser.Key{"statesync"}, &parser.KeyValue{ + Block: parser.Comments{ + "The number of concurrent chunk and block fetchers to run (default: 4).", + }, + Name: parser.Key{"fetchers"}, + Value: parser.MustValue("4"), + })(ctx, doc) + }), + }, + { + // Since https://github.com/tendermint/tendermint/pull/6807. + // Backported into v0.34.13 (modulo casing). + Desc: "Add statesync.use-p2p setting", + T: transform.EnsureKey(parser.Key{"statesync"}, &parser.KeyValue{ + Block: parser.Comments{ + "# State sync uses light client verification to verify state. This can be done either through the", + "# P2P layer or RPC layer. Set this to true to use the P2P layer. If false (default), RPC layer", + "# will be used.", + }, + Name: parser.Key{"use-p2p"}, + Value: parser.MustValue("false"), + }), + }, + { + // Since https://github.com/tendermint/tendermint/pull/6462. + Desc: "Move priv-validator settings under [priv-validator]", + T: transform.Func(func(_ context.Context, doc *tomledit.Document) error { + const pvPrefix = "priv-validator-" + + var found []*tomledit.Entry + doc.Global.Scan(func(key parser.Key, e *tomledit.Entry) bool { + if len(key) == 1 && strings.HasPrefix(key[0], pvPrefix) { + found = append(found, e) + } + return true + }) + if len(found) == 0 { + return nil // nothing to do + } + + // Now that we know we have work to do, find the target table. + var sec *tomledit.Section + if dst := transform.FindTable(doc, "priv-validator"); dst == nil { + // If the table doesn't exist, create it. Old config files + // probably will not have it, so plug in the comment too. + sec = &tomledit.Section{ + Heading: &parser.Heading{ + Block: parser.Comments{ + "#######################################################", + "### Priv Validator Configuration ###", + "#######################################################", + }, + Name: parser.Key{"priv-validator"}, + }, + } + doc.Sections = append(doc.Sections, sec) + } else { + sec = dst.Section + } + + for _, e := range found { + e.Remove() + e.Name = parser.Key{strings.TrimPrefix(e.Name[0], pvPrefix)} + sec.Items = append(sec.Items, e.KeyValue) + } + return nil + }), + }, + { + // Since https://github.com/tendermint/tendermint/pull/6411. + Desc: "Convert tx-index.indexer from a string to a list of strings", + T: transform.Func(func(ctx context.Context, doc *tomledit.Document) error { + idx := doc.First("tx-index", "indexer") + if idx == nil { + // No previous indexer setting: Default to ["null"] per #8222. + return transform.EnsureKey(parser.Key{"tx-index"}, &parser.KeyValue{ + Block: parser.Comments{"The backend database list to back the indexer."}, + Name: parser.Key{"indexer"}, + Value: parser.MustValue(`["null"]`), + })(ctx, doc) + } + + // Versions prior to v0.35 had a string value here, v0.35 and onward + // use an array of strings. + switch idx.KeyValue.Value.X.(type) { + case parser.Array: + // OK, this is already up-to-date. + return nil + case parser.Token: + // Wrap the value in a single-element array. + idx.KeyValue.Value.X = parser.Array{idx.KeyValue.Value} + return nil + } + return fmt.Errorf("unrecognized value: %v", idx.KeyValue) + }), + }, +} diff --git a/scripts/confix/testdata/README.md b/scripts/confix/testdata/README.md new file mode 100644 index 000000000..5bbfa795f --- /dev/null +++ b/scripts/confix/testdata/README.md @@ -0,0 +1,52 @@ +# Test data for `confix` and `condiff` + +The files in this directory are stock Tendermint configuration files generated +by the last point release of each version series from v0.26 to present, along +with diffs between consecutive versions. + +## Config Samples + +The files named `vXX-config.toml` were generated by checking out and building +the corresponding version of Tendermint v0.xx.y and initializing a new node in +an empty home directory. The resulting `config.toml` file was copied here. +The exact build instructions vary a bit, but a general repro looks like: + +```shell +# This example uses v0.31, substitute the version of your choice. +# Note that the branch names and tags may differ. +# Versions prior to v0.26 may not build. +git checkout v0.31.9 +git clean -fdx + +# Versions prior to v0.32 do not have Go module files. +# Those that do may need some dependencies manually updated. +go mod init github.com/tendermint/tendermint +go mod tidy +go get golang.org/x/sys + +# Once you sort out the dependencies, this should usually work. +make build + +# Confirm you go the version you expected, and generate the file. +./build/tendermint --home=tmhome version +./build/tendermint --home=tmhome init + +# Copy the file out. +cp ./tmhome/config/config.toml v31-config.toml +``` + +## Version Diffs + +The files named `diff-XX-YY.txt` were generated by using the `condiff` tool on +the config samples for versions v0.XX and v0.YY: + +```shell +go run ./scripts/confix/condiff -desnake vXX-config vYY-config.toml > diff-XX-YY.txt +``` + +The `baseline.txt` was computed in the same way, but using an empty starting +file so that we capture all the settings in the target: + +```shell +go run ./scripts/confix/condiff -desnake /dev/null v26-config.toml > baseline.txt +``` diff --git a/scripts/confix/testdata/baseline.txt b/scripts/confix/testdata/baseline.txt new file mode 100644 index 000000000..421343704 --- /dev/null +++ b/scripts/confix/testdata/baseline.txt @@ -0,0 +1,73 @@ ++M abci ++M db-backend ++M db-dir ++M fast-sync ++M filter-peers ++M genesis-file ++M log-format ++M log-level ++M moniker ++M node-key-file ++M priv-validator-file ++M priv-validator-laddr ++M prof-laddr ++M proxy-app ++S consensus ++M consensus.wal-file ++M consensus.timeout-propose ++M consensus.timeout-propose-delta ++M consensus.timeout-prevote ++M consensus.timeout-prevote-delta ++M consensus.timeout-precommit ++M consensus.timeout-precommit-delta ++M consensus.timeout-commit ++M consensus.skip-timeout-commit ++M consensus.create-empty-blocks ++M consensus.create-empty-blocks-interval ++M consensus.peer-gossip-sleep-duration ++M consensus.peer-query-maj23-sleep-duration ++M consensus.blocktime-iota ++S instrumentation ++M instrumentation.prometheus ++M instrumentation.prometheus-listen-addr ++M instrumentation.max-open-connections ++M instrumentation.namespace ++S mempool ++M mempool.recheck ++M mempool.broadcast ++M mempool.wal-dir ++M mempool.size ++M mempool.cache-size ++S p2p ++M p2p.laddr ++M p2p.external-address ++M p2p.seeds ++M p2p.persistent-peers ++M p2p.upnp ++M p2p.addr-book-file ++M p2p.addr-book-strict ++M p2p.max-num-inbound-peers ++M p2p.max-num-outbound-peers ++M p2p.flush-throttle-timeout ++M p2p.max-packet-msg-payload-size ++M p2p.send-rate ++M p2p.recv-rate ++M p2p.pex ++M p2p.seed-mode ++M p2p.private-peer-ids ++M p2p.allow-duplicate-ip ++M p2p.handshake-timeout ++M p2p.dial-timeout ++S rpc ++M rpc.laddr ++M rpc.cors-allowed-origins ++M rpc.cors-allowed-methods ++M rpc.cors-allowed-headers ++M rpc.grpc-laddr ++M rpc.grpc-max-open-connections ++M rpc.unsafe ++M rpc.max-open-connections ++S tx-index ++M tx-index.indexer ++M tx-index.index-tags ++M tx-index.index-all-tags diff --git a/test/fuzz/mempool/testdata/cases/empty b/scripts/confix/testdata/diff-26-27.txt similarity index 100% rename from test/fuzz/mempool/testdata/cases/empty rename to scripts/confix/testdata/diff-26-27.txt diff --git a/scripts/confix/testdata/diff-27-28.txt b/scripts/confix/testdata/diff-27-28.txt new file mode 100644 index 000000000..c5c00449d --- /dev/null +++ b/scripts/confix/testdata/diff-27-28.txt @@ -0,0 +1,3 @@ +-M priv-validator-file ++M priv-validator-key-file ++M priv-validator-state-file diff --git a/test/fuzz/p2p/secretconnection/testdata/cases/empty b/scripts/confix/testdata/diff-28-29.txt similarity index 100% rename from test/fuzz/p2p/secretconnection/testdata/cases/empty rename to scripts/confix/testdata/diff-28-29.txt diff --git a/test/fuzz/rpc/jsonrpc/server/testdata/cases/empty b/scripts/confix/testdata/diff-29-30.txt similarity index 100% rename from test/fuzz/rpc/jsonrpc/server/testdata/cases/empty rename to scripts/confix/testdata/diff-29-30.txt diff --git a/scripts/confix/testdata/diff-30-31.txt b/scripts/confix/testdata/diff-30-31.txt new file mode 100644 index 000000000..0f93b761e --- /dev/null +++ b/scripts/confix/testdata/diff-30-31.txt @@ -0,0 +1,7 @@ +-M consensus.blocktime-iota ++M mempool.max-txs-bytes ++M rpc.max-subscription-clients ++M rpc.max-subscriptions-per-client ++M rpc.timeout-broadcast-tx-commit ++M rpc.tls-cert-file ++M rpc.tls-key-file diff --git a/scripts/confix/testdata/diff-31-32.txt b/scripts/confix/testdata/diff-31-32.txt new file mode 100644 index 000000000..98855bade --- /dev/null +++ b/scripts/confix/testdata/diff-31-32.txt @@ -0,0 +1,5 @@ ++S fastsync ++M fastsync.version ++M mempool.max-tx-bytes ++M rpc.max-body-bytes ++M rpc.max-header-bytes diff --git a/scripts/confix/testdata/diff-32-33.txt b/scripts/confix/testdata/diff-32-33.txt new file mode 100644 index 000000000..7aa61856a --- /dev/null +++ b/scripts/confix/testdata/diff-32-33.txt @@ -0,0 +1,6 @@ ++M p2p.persistent-peers-max-dial-period ++M p2p.unconditional-peer-ids ++M tx-index.index-all-keys +-M tx-index.index-all-tags ++M tx-index.index-keys +-M tx-index.index-tags diff --git a/scripts/confix/testdata/diff-33-34.txt b/scripts/confix/testdata/diff-33-34.txt new file mode 100644 index 000000000..a0ac7a98d --- /dev/null +++ b/scripts/confix/testdata/diff-33-34.txt @@ -0,0 +1,20 @@ +-M prof-laddr ++M consensus.double-sign-check-height ++M mempool.keep-invalid-txs-in-cache ++M mempool.max-batch-bytes ++M rpc.experimental-close-on-slow-client ++M rpc.experimental-subscription-buffer-size ++M rpc.experimental-websocket-write-buffer-size ++M rpc.pprof-laddr ++S statesync ++M statesync.enable ++M statesync.rpc-servers ++M statesync.trust-height ++M statesync.trust-hash ++M statesync.trust-period ++M statesync.discovery-time ++M statesync.temp-dir ++M statesync.chunk-request-timeout ++M statesync.chunk-fetchers +-M tx-index.index-all-keys +-M tx-index.index-keys diff --git a/scripts/confix/testdata/diff-34-35.txt b/scripts/confix/testdata/diff-34-35.txt new file mode 100644 index 000000000..13a4432a0 --- /dev/null +++ b/scripts/confix/testdata/diff-34-35.txt @@ -0,0 +1,31 @@ +-M fast-sync ++M mode +-M priv-validator-key-file +-M priv-validator-laddr +-M priv-validator-state-file ++S blocksync ++M blocksync.enable ++M blocksync.version +-S fastsync +-M fastsync.version ++M mempool.ttl-duration ++M mempool.ttl-num-blocks ++M mempool.version +-M mempool.wal-dir ++M p2p.bootstrap-peers ++M p2p.max-connections ++M p2p.max-incoming-connection-attempts ++M p2p.queue-type +-M p2p.seed-mode ++M p2p.use-legacy ++S priv-validator ++M priv-validator.key-file ++M priv-validator.state-file ++M priv-validator.laddr ++M priv-validator.client-certificate-file ++M priv-validator.client-key-file ++M priv-validator.root-ca-file +-M statesync.chunk-fetchers ++M statesync.fetchers ++M statesync.use-p2p ++M tx-index.psql-conn diff --git a/scripts/confix/testdata/diff-35-36.txt b/scripts/confix/testdata/diff-35-36.txt new file mode 100644 index 000000000..13fd268af --- /dev/null +++ b/scripts/confix/testdata/diff-35-36.txt @@ -0,0 +1,27 @@ +-S blocksync +-M blocksync.enable +-M blocksync.version +-M consensus.skip-timeout-commit +-M consensus.timeout-commit +-M consensus.timeout-precommit +-M consensus.timeout-precommit-delta +-M consensus.timeout-prevote +-M consensus.timeout-prevote-delta +-M consensus.timeout-propose +-M consensus.timeout-propose-delta +-M mempool.version +-M p2p.addr-book-file +-M p2p.addr-book-strict +-M p2p.max-num-inbound-peers +-M p2p.max-num-outbound-peers +-M p2p.persistent-peers-max-dial-period +-M p2p.unconditional-peer-ids +-M p2p.use-legacy ++M rpc.event-log-max-items ++M rpc.event-log-window-size +-M rpc.experimental-close-on-slow-client ++M rpc.experimental-disable-websocket +-M rpc.experimental-subscription-buffer-size +-M rpc.experimental-websocket-write-buffer-size +-M rpc.grpc-laddr +-M rpc.grpc-max-open-connections diff --git a/scripts/confix/testdata/non-config.toml b/scripts/confix/testdata/non-config.toml new file mode 100644 index 000000000..abfd48667 --- /dev/null +++ b/scripts/confix/testdata/non-config.toml @@ -0,0 +1,6 @@ +# This is not a Tendermint config file. + +[ test ] +key = 'value' + +# Nothing to see here, move along. diff --git a/scripts/confix/testdata/v26-config.toml b/scripts/confix/testdata/v26-config.toml new file mode 100644 index 000000000..a9237056b --- /dev/null +++ b/scripts/confix/testdata/v26-config.toml @@ -0,0 +1,249 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +##### main base config options ##### + +# TCP or UNIX socket address of the ABCI application, +# or the name of an ABCI application compiled in with the Tendermint binary +proxy_app = "tcp://127.0.0.1:26658" + +# A custom human readable name for this node +moniker = "localhost" + +# If this node is many blocks behind the tip of the chain, FastSync +# allows them to catchup quickly by downloading blocks in parallel +# and verifying their commits +fast_sync = true + +# Database backend: leveldb | memdb | cleveldb +db_backend = "leveldb" + +# Database directory +db_dir = "data" + +# Output level for logging, including package level options +log_level = "main:info,state:info,*:error" + +# Output format: 'plain' (colored text) or 'json' +log_format = "plain" + +##### additional base config options ##### + +# Path to the JSON file containing the initial validator set and other meta data +genesis_file = "config/genesis.json" + +# Path to the JSON file containing the private key to use as a validator in the consensus protocol +priv_validator_file = "config/priv_validator.json" + +# TCP or UNIX socket address for Tendermint to listen on for +# connections from an external PrivValidator process +priv_validator_laddr = "" + +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node_key_file = "config/node_key.json" + +# Mechanism to connect to the ABCI application: socket | grpc +abci = "socket" + +# TCP or UNIX socket address for the profiling server to listen on +prof_laddr = "" + +# If true, query the ABCI app on connecting to a new peer +# so the app can decide if we should keep the connection or not +filter_peers = false + +##### advanced configuration options ##### + +##### rpc server configuration options ##### +[rpc] + +# TCP or UNIX socket address for the RPC server to listen on +laddr = "tcp://0.0.0.0:26657" + +# A list of origins a cross-domain request can be executed from +# Default value '[]' disables cors support +# Use '["*"]' to allow any origin +cors_allowed_origins = "[]" + +# A list of methods the client is allowed to use with cross-domain requests +cors_allowed_methods = "[HEAD GET POST]" + +# A list of non simple headers the client is allowed to use with cross-domain requests +cors_allowed_headers = "[Origin Accept Content-Type X-Requested-With X-Server-Time]" + +# TCP or UNIX socket address for the gRPC server to listen on +# NOTE: This server only supports /broadcast_tx_commit +grpc_laddr = "" + +# Maximum number of simultaneous connections. +# Does not include RPC (HTTP&WebSocket) connections. See max_open_connections +# If you want to accept more significant number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +grpc_max_open_connections = 900 + +# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool +unsafe = false + +# Maximum number of simultaneous connections (including WebSocket). +# Does not include gRPC connections. See grpc_max_open_connections +# If you want to accept more significant number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +max_open_connections = 900 + +##### peer to peer configuration options ##### +[p2p] + +# Address to listen for incoming connections +laddr = "tcp://0.0.0.0:26656" + +# Address to advertise to peers for them to dial +# If empty, will use the same port as the laddr, +# and will introspect on the listener or use UPnP +# to figure out the address. +external_address = "" + +# Comma separated list of seed nodes to connect to +seeds = "" + +# Comma separated list of nodes to keep persistent connections to +persistent_peers = "" + +# UPNP port forwarding +upnp = false + +# Path to address book +addr_book_file = "config/addrbook.json" + +# Set true for strict address routability rules +# Set false for private or local networks +addr_book_strict = true + +# Maximum number of inbound peers +max_num_inbound_peers = 40 + +# Maximum number of outbound peers to connect to, excluding persistent peers +max_num_outbound_peers = 10 + +# Time to wait before flushing messages out on the connection +flush_throttle_timeout = "100ms" + +# Maximum size of a message packet payload, in bytes +max_packet_msg_payload_size = 1024 + +# Rate at which packets can be sent, in bytes/second +send_rate = 5120000 + +# Rate at which packets can be received, in bytes/second +recv_rate = 5120000 + +# Set true to enable the peer-exchange reactor +pex = true + +# Seed mode, in which node constantly crawls the network and looks for +# peers. If another node asks it for addresses, it responds and disconnects. +# +# Does not work if the peer-exchange reactor is disabled. +seed_mode = false + +# Comma separated list of peer IDs to keep private (will not be gossiped to other peers) +private_peer_ids = "" + +# Toggle to disable guard against peers connecting from the same ip. +allow_duplicate_ip = true + +# Peer connection configuration. +handshake_timeout = "20s" +dial_timeout = "3s" + +##### mempool configuration options ##### +[mempool] + +recheck = true +broadcast = true +wal_dir = "" + +# size of the mempool +size = 5000 + +# size of the cache (used to filter transactions we saw earlier) +cache_size = 10000 + +##### consensus configuration options ##### +[consensus] + +wal_file = "data/cs.wal/wal" + +timeout_propose = "3s" +timeout_propose_delta = "500ms" +timeout_prevote = "1s" +timeout_prevote_delta = "500ms" +timeout_precommit = "1s" +timeout_precommit_delta = "500ms" +timeout_commit = "1s" + +# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) +skip_timeout_commit = false + +# EmptyBlocks mode and possible interval between empty blocks +create_empty_blocks = true +create_empty_blocks_interval = "0s" + +# Reactor sleep duration parameters +peer_gossip_sleep_duration = "100ms" +peer_query_maj23_sleep_duration = "2s" + +# Block time parameters. Corresponds to the minimum time increment between consecutive blocks. +blocktime_iota = "1s" + +##### transactions indexer configuration options ##### +[tx_index] + +# What indexer to use for transactions +# +# Options: +# 1) "null" (default) +# 2) "kv" - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). +indexer = "kv" + +# Comma-separated list of tags to index (by default the only tag is "tx.hash") +# +# You can also index transactions by height by adding "tx.height" tag here. +# +# It's recommended to index only a subset of tags due to possible memory +# bloat. This is, of course, depends on the indexer's DB and the volume of +# transactions. +index_tags = "" + +# When set to true, tells indexer to index all tags (predefined tags: +# "tx.hash", "tx.height" and all tags from DeliverTx responses). +# +# Note this may be not desirable (see the comment above). IndexTags has a +# precedence over IndexAllTags (i.e. when given both, IndexTags will be +# indexed). +index_all_tags = false + +##### instrumentation configuration options ##### +[instrumentation] + +# When true, Prometheus metrics are served under /metrics on +# PrometheusListenAddr. +# Check out the documentation for the list of available metrics. +prometheus = false + +# Address to listen for Prometheus collector(s) connections +prometheus_listen_addr = ":26660" + +# Maximum number of simultaneous connections. +# If you want to accept more significant number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +max_open_connections = 3 + +# Instrumentation namespace +namespace = "tendermint" diff --git a/scripts/confix/testdata/v27-config.toml b/scripts/confix/testdata/v27-config.toml new file mode 100644 index 000000000..25e3b582f --- /dev/null +++ b/scripts/confix/testdata/v27-config.toml @@ -0,0 +1,249 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +##### main base config options ##### + +# TCP or UNIX socket address of the ABCI application, +# or the name of an ABCI application compiled in with the Tendermint binary +proxy_app = "tcp://127.0.0.1:26658" + +# A custom human readable name for this node +moniker = "localhost" + +# If this node is many blocks behind the tip of the chain, FastSync +# allows them to catchup quickly by downloading blocks in parallel +# and verifying their commits +fast_sync = true + +# Database backend: leveldb | memdb | cleveldb +db_backend = "leveldb" + +# Database directory +db_dir = "data" + +# Output level for logging, including package level options +log_level = "main:info,state:info,*:error" + +# Output format: 'plain' (colored text) or 'json' +log_format = "plain" + +##### additional base config options ##### + +# Path to the JSON file containing the initial validator set and other meta data +genesis_file = "config/genesis.json" + +# Path to the JSON file containing the private key to use as a validator in the consensus protocol +priv_validator_file = "config/priv_validator.json" + +# TCP or UNIX socket address for Tendermint to listen on for +# connections from an external PrivValidator process +priv_validator_laddr = "" + +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node_key_file = "config/node_key.json" + +# Mechanism to connect to the ABCI application: socket | grpc +abci = "socket" + +# TCP or UNIX socket address for the profiling server to listen on +prof_laddr = "" + +# If true, query the ABCI app on connecting to a new peer +# so the app can decide if we should keep the connection or not +filter_peers = false + +##### advanced configuration options ##### + +##### rpc server configuration options ##### +[rpc] + +# TCP or UNIX socket address for the RPC server to listen on +laddr = "tcp://0.0.0.0:26657" + +# A list of origins a cross-domain request can be executed from +# Default value '[]' disables cors support +# Use '["*"]' to allow any origin +cors_allowed_origins = [] + +# A list of methods the client is allowed to use with cross-domain requests +cors_allowed_methods = ["HEAD", "GET", "POST", ] + +# A list of non simple headers the client is allowed to use with cross-domain requests +cors_allowed_headers = ["Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time", ] + +# TCP or UNIX socket address for the gRPC server to listen on +# NOTE: This server only supports /broadcast_tx_commit +grpc_laddr = "" + +# Maximum number of simultaneous connections. +# Does not include RPC (HTTP&WebSocket) connections. See max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +grpc_max_open_connections = 900 + +# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool +unsafe = false + +# Maximum number of simultaneous connections (including WebSocket). +# Does not include gRPC connections. See grpc_max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +max_open_connections = 900 + +##### peer to peer configuration options ##### +[p2p] + +# Address to listen for incoming connections +laddr = "tcp://0.0.0.0:26656" + +# Address to advertise to peers for them to dial +# If empty, will use the same port as the laddr, +# and will introspect on the listener or use UPnP +# to figure out the address. +external_address = "" + +# Comma separated list of seed nodes to connect to +seeds = "" + +# Comma separated list of nodes to keep persistent connections to +persistent_peers = "" + +# UPNP port forwarding +upnp = false + +# Path to address book +addr_book_file = "config/addrbook.json" + +# Set true for strict address routability rules +# Set false for private or local networks +addr_book_strict = true + +# Maximum number of inbound peers +max_num_inbound_peers = 40 + +# Maximum number of outbound peers to connect to, excluding persistent peers +max_num_outbound_peers = 10 + +# Time to wait before flushing messages out on the connection +flush_throttle_timeout = "100ms" + +# Maximum size of a message packet payload, in bytes +max_packet_msg_payload_size = 1024 + +# Rate at which packets can be sent, in bytes/second +send_rate = 5120000 + +# Rate at which packets can be received, in bytes/second +recv_rate = 5120000 + +# Set true to enable the peer-exchange reactor +pex = true + +# Seed mode, in which node constantly crawls the network and looks for +# peers. If another node asks it for addresses, it responds and disconnects. +# +# Does not work if the peer-exchange reactor is disabled. +seed_mode = false + +# Comma separated list of peer IDs to keep private (will not be gossiped to other peers) +private_peer_ids = "" + +# Toggle to disable guard against peers connecting from the same ip. +allow_duplicate_ip = true + +# Peer connection configuration. +handshake_timeout = "20s" +dial_timeout = "3s" + +##### mempool configuration options ##### +[mempool] + +recheck = true +broadcast = true +wal_dir = "" + +# size of the mempool +size = 5000 + +# size of the cache (used to filter transactions we saw earlier) +cache_size = 10000 + +##### consensus configuration options ##### +[consensus] + +wal_file = "data/cs.wal/wal" + +timeout_propose = "3s" +timeout_propose_delta = "500ms" +timeout_prevote = "1s" +timeout_prevote_delta = "500ms" +timeout_precommit = "1s" +timeout_precommit_delta = "500ms" +timeout_commit = "1s" + +# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) +skip_timeout_commit = false + +# EmptyBlocks mode and possible interval between empty blocks +create_empty_blocks = true +create_empty_blocks_interval = "0s" + +# Reactor sleep duration parameters +peer_gossip_sleep_duration = "100ms" +peer_query_maj23_sleep_duration = "2s" + +# Block time parameters. Corresponds to the minimum time increment between consecutive blocks. +blocktime_iota = "1s" + +##### transactions indexer configuration options ##### +[tx_index] + +# What indexer to use for transactions +# +# Options: +# 1) "null" +# 2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). +indexer = "kv" + +# Comma-separated list of tags to index (by default the only tag is "tx.hash") +# +# You can also index transactions by height by adding "tx.height" tag here. +# +# It's recommended to index only a subset of tags due to possible memory +# bloat. This is, of course, depends on the indexer's DB and the volume of +# transactions. +index_tags = "" + +# When set to true, tells indexer to index all tags (predefined tags: +# "tx.hash", "tx.height" and all tags from DeliverTx responses). +# +# Note this may be not desirable (see the comment above). IndexTags has a +# precedence over IndexAllTags (i.e. when given both, IndexTags will be +# indexed). +index_all_tags = false + +##### instrumentation configuration options ##### +[instrumentation] + +# When true, Prometheus metrics are served under /metrics on +# PrometheusListenAddr. +# Check out the documentation for the list of available metrics. +prometheus = false + +# Address to listen for Prometheus collector(s) connections +prometheus_listen_addr = ":26660" + +# Maximum number of simultaneous connections. +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +max_open_connections = 3 + +# Instrumentation namespace +namespace = "tendermint" diff --git a/scripts/confix/testdata/v28-config.toml b/scripts/confix/testdata/v28-config.toml new file mode 100644 index 000000000..b4aaa5aae --- /dev/null +++ b/scripts/confix/testdata/v28-config.toml @@ -0,0 +1,252 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +##### main base config options ##### + +# TCP or UNIX socket address of the ABCI application, +# or the name of an ABCI application compiled in with the Tendermint binary +proxy_app = "tcp://127.0.0.1:26658" + +# A custom human readable name for this node +moniker = "localhost" + +# If this node is many blocks behind the tip of the chain, FastSync +# allows them to catchup quickly by downloading blocks in parallel +# and verifying their commits +fast_sync = true + +# Database backend: leveldb | memdb | cleveldb +db_backend = "leveldb" + +# Database directory +db_dir = "data" + +# Output level for logging, including package level options +log_level = "main:info,state:info,*:error" + +# Output format: 'plain' (colored text) or 'json' +log_format = "plain" + +##### additional base config options ##### + +# Path to the JSON file containing the initial validator set and other meta data +genesis_file = "config/genesis.json" + +# Path to the JSON file containing the private key to use as a validator in the consensus protocol +priv_validator_key_file = "config/priv_validator_key.json" + +# Path to the JSON file containing the last sign state of a validator +priv_validator_state_file = "data/priv_validator_state.json" + +# TCP or UNIX socket address for Tendermint to listen on for +# connections from an external PrivValidator process +priv_validator_laddr = "" + +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node_key_file = "config/node_key.json" + +# Mechanism to connect to the ABCI application: socket | grpc +abci = "socket" + +# TCP or UNIX socket address for the profiling server to listen on +prof_laddr = "" + +# If true, query the ABCI app on connecting to a new peer +# so the app can decide if we should keep the connection or not +filter_peers = false + +##### advanced configuration options ##### + +##### rpc server configuration options ##### +[rpc] + +# TCP or UNIX socket address for the RPC server to listen on +laddr = "tcp://0.0.0.0:26657" + +# A list of origins a cross-domain request can be executed from +# Default value '[]' disables cors support +# Use '["*"]' to allow any origin +cors_allowed_origins = [] + +# A list of methods the client is allowed to use with cross-domain requests +cors_allowed_methods = ["HEAD", "GET", "POST", ] + +# A list of non simple headers the client is allowed to use with cross-domain requests +cors_allowed_headers = ["Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time", ] + +# TCP or UNIX socket address for the gRPC server to listen on +# NOTE: This server only supports /broadcast_tx_commit +grpc_laddr = "" + +# Maximum number of simultaneous connections. +# Does not include RPC (HTTP&WebSocket) connections. See max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +grpc_max_open_connections = 900 + +# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool +unsafe = false + +# Maximum number of simultaneous connections (including WebSocket). +# Does not include gRPC connections. See grpc_max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +max_open_connections = 900 + +##### peer to peer configuration options ##### +[p2p] + +# Address to listen for incoming connections +laddr = "tcp://0.0.0.0:26656" + +# Address to advertise to peers for them to dial +# If empty, will use the same port as the laddr, +# and will introspect on the listener or use UPnP +# to figure out the address. +external_address = "" + +# Comma separated list of seed nodes to connect to +seeds = "" + +# Comma separated list of nodes to keep persistent connections to +persistent_peers = "" + +# UPNP port forwarding +upnp = false + +# Path to address book +addr_book_file = "config/addrbook.json" + +# Set true for strict address routability rules +# Set false for private or local networks +addr_book_strict = true + +# Maximum number of inbound peers +max_num_inbound_peers = 40 + +# Maximum number of outbound peers to connect to, excluding persistent peers +max_num_outbound_peers = 10 + +# Time to wait before flushing messages out on the connection +flush_throttle_timeout = "100ms" + +# Maximum size of a message packet payload, in bytes +max_packet_msg_payload_size = 1024 + +# Rate at which packets can be sent, in bytes/second +send_rate = 5120000 + +# Rate at which packets can be received, in bytes/second +recv_rate = 5120000 + +# Set true to enable the peer-exchange reactor +pex = true + +# Seed mode, in which node constantly crawls the network and looks for +# peers. If another node asks it for addresses, it responds and disconnects. +# +# Does not work if the peer-exchange reactor is disabled. +seed_mode = false + +# Comma separated list of peer IDs to keep private (will not be gossiped to other peers) +private_peer_ids = "" + +# Toggle to disable guard against peers connecting from the same ip. +allow_duplicate_ip = false + +# Peer connection configuration. +handshake_timeout = "20s" +dial_timeout = "3s" + +##### mempool configuration options ##### +[mempool] + +recheck = true +broadcast = true +wal_dir = "" + +# size of the mempool +size = 5000 + +# size of the cache (used to filter transactions we saw earlier) +cache_size = 10000 + +##### consensus configuration options ##### +[consensus] + +wal_file = "data/cs.wal/wal" + +timeout_propose = "3s" +timeout_propose_delta = "500ms" +timeout_prevote = "1s" +timeout_prevote_delta = "500ms" +timeout_precommit = "1s" +timeout_precommit_delta = "500ms" +timeout_commit = "1s" + +# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) +skip_timeout_commit = false + +# EmptyBlocks mode and possible interval between empty blocks +create_empty_blocks = true +create_empty_blocks_interval = "0s" + +# Reactor sleep duration parameters +peer_gossip_sleep_duration = "100ms" +peer_query_maj23_sleep_duration = "2s" + +# Block time parameters. Corresponds to the minimum time increment between consecutive blocks. +blocktime_iota = "1s" + +##### transactions indexer configuration options ##### +[tx_index] + +# What indexer to use for transactions +# +# Options: +# 1) "null" +# 2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). +indexer = "kv" + +# Comma-separated list of tags to index (by default the only tag is "tx.hash") +# +# You can also index transactions by height by adding "tx.height" tag here. +# +# It's recommended to index only a subset of tags due to possible memory +# bloat. This is, of course, depends on the indexer's DB and the volume of +# transactions. +index_tags = "" + +# When set to true, tells indexer to index all tags (predefined tags: +# "tx.hash", "tx.height" and all tags from DeliverTx responses). +# +# Note this may be not desirable (see the comment above). IndexTags has a +# precedence over IndexAllTags (i.e. when given both, IndexTags will be +# indexed). +index_all_tags = false + +##### instrumentation configuration options ##### +[instrumentation] + +# When true, Prometheus metrics are served under /metrics on +# PrometheusListenAddr. +# Check out the documentation for the list of available metrics. +prometheus = false + +# Address to listen for Prometheus collector(s) connections +prometheus_listen_addr = ":26660" + +# Maximum number of simultaneous connections. +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +max_open_connections = 3 + +# Instrumentation namespace +namespace = "tendermint" diff --git a/scripts/confix/testdata/v29-config.toml b/scripts/confix/testdata/v29-config.toml new file mode 100644 index 000000000..b4aaa5aae --- /dev/null +++ b/scripts/confix/testdata/v29-config.toml @@ -0,0 +1,252 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +##### main base config options ##### + +# TCP or UNIX socket address of the ABCI application, +# or the name of an ABCI application compiled in with the Tendermint binary +proxy_app = "tcp://127.0.0.1:26658" + +# A custom human readable name for this node +moniker = "localhost" + +# If this node is many blocks behind the tip of the chain, FastSync +# allows them to catchup quickly by downloading blocks in parallel +# and verifying their commits +fast_sync = true + +# Database backend: leveldb | memdb | cleveldb +db_backend = "leveldb" + +# Database directory +db_dir = "data" + +# Output level for logging, including package level options +log_level = "main:info,state:info,*:error" + +# Output format: 'plain' (colored text) or 'json' +log_format = "plain" + +##### additional base config options ##### + +# Path to the JSON file containing the initial validator set and other meta data +genesis_file = "config/genesis.json" + +# Path to the JSON file containing the private key to use as a validator in the consensus protocol +priv_validator_key_file = "config/priv_validator_key.json" + +# Path to the JSON file containing the last sign state of a validator +priv_validator_state_file = "data/priv_validator_state.json" + +# TCP or UNIX socket address for Tendermint to listen on for +# connections from an external PrivValidator process +priv_validator_laddr = "" + +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node_key_file = "config/node_key.json" + +# Mechanism to connect to the ABCI application: socket | grpc +abci = "socket" + +# TCP or UNIX socket address for the profiling server to listen on +prof_laddr = "" + +# If true, query the ABCI app on connecting to a new peer +# so the app can decide if we should keep the connection or not +filter_peers = false + +##### advanced configuration options ##### + +##### rpc server configuration options ##### +[rpc] + +# TCP or UNIX socket address for the RPC server to listen on +laddr = "tcp://0.0.0.0:26657" + +# A list of origins a cross-domain request can be executed from +# Default value '[]' disables cors support +# Use '["*"]' to allow any origin +cors_allowed_origins = [] + +# A list of methods the client is allowed to use with cross-domain requests +cors_allowed_methods = ["HEAD", "GET", "POST", ] + +# A list of non simple headers the client is allowed to use with cross-domain requests +cors_allowed_headers = ["Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time", ] + +# TCP or UNIX socket address for the gRPC server to listen on +# NOTE: This server only supports /broadcast_tx_commit +grpc_laddr = "" + +# Maximum number of simultaneous connections. +# Does not include RPC (HTTP&WebSocket) connections. See max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +grpc_max_open_connections = 900 + +# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool +unsafe = false + +# Maximum number of simultaneous connections (including WebSocket). +# Does not include gRPC connections. See grpc_max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +max_open_connections = 900 + +##### peer to peer configuration options ##### +[p2p] + +# Address to listen for incoming connections +laddr = "tcp://0.0.0.0:26656" + +# Address to advertise to peers for them to dial +# If empty, will use the same port as the laddr, +# and will introspect on the listener or use UPnP +# to figure out the address. +external_address = "" + +# Comma separated list of seed nodes to connect to +seeds = "" + +# Comma separated list of nodes to keep persistent connections to +persistent_peers = "" + +# UPNP port forwarding +upnp = false + +# Path to address book +addr_book_file = "config/addrbook.json" + +# Set true for strict address routability rules +# Set false for private or local networks +addr_book_strict = true + +# Maximum number of inbound peers +max_num_inbound_peers = 40 + +# Maximum number of outbound peers to connect to, excluding persistent peers +max_num_outbound_peers = 10 + +# Time to wait before flushing messages out on the connection +flush_throttle_timeout = "100ms" + +# Maximum size of a message packet payload, in bytes +max_packet_msg_payload_size = 1024 + +# Rate at which packets can be sent, in bytes/second +send_rate = 5120000 + +# Rate at which packets can be received, in bytes/second +recv_rate = 5120000 + +# Set true to enable the peer-exchange reactor +pex = true + +# Seed mode, in which node constantly crawls the network and looks for +# peers. If another node asks it for addresses, it responds and disconnects. +# +# Does not work if the peer-exchange reactor is disabled. +seed_mode = false + +# Comma separated list of peer IDs to keep private (will not be gossiped to other peers) +private_peer_ids = "" + +# Toggle to disable guard against peers connecting from the same ip. +allow_duplicate_ip = false + +# Peer connection configuration. +handshake_timeout = "20s" +dial_timeout = "3s" + +##### mempool configuration options ##### +[mempool] + +recheck = true +broadcast = true +wal_dir = "" + +# size of the mempool +size = 5000 + +# size of the cache (used to filter transactions we saw earlier) +cache_size = 10000 + +##### consensus configuration options ##### +[consensus] + +wal_file = "data/cs.wal/wal" + +timeout_propose = "3s" +timeout_propose_delta = "500ms" +timeout_prevote = "1s" +timeout_prevote_delta = "500ms" +timeout_precommit = "1s" +timeout_precommit_delta = "500ms" +timeout_commit = "1s" + +# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) +skip_timeout_commit = false + +# EmptyBlocks mode and possible interval between empty blocks +create_empty_blocks = true +create_empty_blocks_interval = "0s" + +# Reactor sleep duration parameters +peer_gossip_sleep_duration = "100ms" +peer_query_maj23_sleep_duration = "2s" + +# Block time parameters. Corresponds to the minimum time increment between consecutive blocks. +blocktime_iota = "1s" + +##### transactions indexer configuration options ##### +[tx_index] + +# What indexer to use for transactions +# +# Options: +# 1) "null" +# 2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). +indexer = "kv" + +# Comma-separated list of tags to index (by default the only tag is "tx.hash") +# +# You can also index transactions by height by adding "tx.height" tag here. +# +# It's recommended to index only a subset of tags due to possible memory +# bloat. This is, of course, depends on the indexer's DB and the volume of +# transactions. +index_tags = "" + +# When set to true, tells indexer to index all tags (predefined tags: +# "tx.hash", "tx.height" and all tags from DeliverTx responses). +# +# Note this may be not desirable (see the comment above). IndexTags has a +# precedence over IndexAllTags (i.e. when given both, IndexTags will be +# indexed). +index_all_tags = false + +##### instrumentation configuration options ##### +[instrumentation] + +# When true, Prometheus metrics are served under /metrics on +# PrometheusListenAddr. +# Check out the documentation for the list of available metrics. +prometheus = false + +# Address to listen for Prometheus collector(s) connections +prometheus_listen_addr = ":26660" + +# Maximum number of simultaneous connections. +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +max_open_connections = 3 + +# Instrumentation namespace +namespace = "tendermint" diff --git a/scripts/confix/testdata/v30-config.toml b/scripts/confix/testdata/v30-config.toml new file mode 100644 index 000000000..b4aaa5aae --- /dev/null +++ b/scripts/confix/testdata/v30-config.toml @@ -0,0 +1,252 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +##### main base config options ##### + +# TCP or UNIX socket address of the ABCI application, +# or the name of an ABCI application compiled in with the Tendermint binary +proxy_app = "tcp://127.0.0.1:26658" + +# A custom human readable name for this node +moniker = "localhost" + +# If this node is many blocks behind the tip of the chain, FastSync +# allows them to catchup quickly by downloading blocks in parallel +# and verifying their commits +fast_sync = true + +# Database backend: leveldb | memdb | cleveldb +db_backend = "leveldb" + +# Database directory +db_dir = "data" + +# Output level for logging, including package level options +log_level = "main:info,state:info,*:error" + +# Output format: 'plain' (colored text) or 'json' +log_format = "plain" + +##### additional base config options ##### + +# Path to the JSON file containing the initial validator set and other meta data +genesis_file = "config/genesis.json" + +# Path to the JSON file containing the private key to use as a validator in the consensus protocol +priv_validator_key_file = "config/priv_validator_key.json" + +# Path to the JSON file containing the last sign state of a validator +priv_validator_state_file = "data/priv_validator_state.json" + +# TCP or UNIX socket address for Tendermint to listen on for +# connections from an external PrivValidator process +priv_validator_laddr = "" + +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node_key_file = "config/node_key.json" + +# Mechanism to connect to the ABCI application: socket | grpc +abci = "socket" + +# TCP or UNIX socket address for the profiling server to listen on +prof_laddr = "" + +# If true, query the ABCI app on connecting to a new peer +# so the app can decide if we should keep the connection or not +filter_peers = false + +##### advanced configuration options ##### + +##### rpc server configuration options ##### +[rpc] + +# TCP or UNIX socket address for the RPC server to listen on +laddr = "tcp://0.0.0.0:26657" + +# A list of origins a cross-domain request can be executed from +# Default value '[]' disables cors support +# Use '["*"]' to allow any origin +cors_allowed_origins = [] + +# A list of methods the client is allowed to use with cross-domain requests +cors_allowed_methods = ["HEAD", "GET", "POST", ] + +# A list of non simple headers the client is allowed to use with cross-domain requests +cors_allowed_headers = ["Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time", ] + +# TCP or UNIX socket address for the gRPC server to listen on +# NOTE: This server only supports /broadcast_tx_commit +grpc_laddr = "" + +# Maximum number of simultaneous connections. +# Does not include RPC (HTTP&WebSocket) connections. See max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +grpc_max_open_connections = 900 + +# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool +unsafe = false + +# Maximum number of simultaneous connections (including WebSocket). +# Does not include gRPC connections. See grpc_max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +max_open_connections = 900 + +##### peer to peer configuration options ##### +[p2p] + +# Address to listen for incoming connections +laddr = "tcp://0.0.0.0:26656" + +# Address to advertise to peers for them to dial +# If empty, will use the same port as the laddr, +# and will introspect on the listener or use UPnP +# to figure out the address. +external_address = "" + +# Comma separated list of seed nodes to connect to +seeds = "" + +# Comma separated list of nodes to keep persistent connections to +persistent_peers = "" + +# UPNP port forwarding +upnp = false + +# Path to address book +addr_book_file = "config/addrbook.json" + +# Set true for strict address routability rules +# Set false for private or local networks +addr_book_strict = true + +# Maximum number of inbound peers +max_num_inbound_peers = 40 + +# Maximum number of outbound peers to connect to, excluding persistent peers +max_num_outbound_peers = 10 + +# Time to wait before flushing messages out on the connection +flush_throttle_timeout = "100ms" + +# Maximum size of a message packet payload, in bytes +max_packet_msg_payload_size = 1024 + +# Rate at which packets can be sent, in bytes/second +send_rate = 5120000 + +# Rate at which packets can be received, in bytes/second +recv_rate = 5120000 + +# Set true to enable the peer-exchange reactor +pex = true + +# Seed mode, in which node constantly crawls the network and looks for +# peers. If another node asks it for addresses, it responds and disconnects. +# +# Does not work if the peer-exchange reactor is disabled. +seed_mode = false + +# Comma separated list of peer IDs to keep private (will not be gossiped to other peers) +private_peer_ids = "" + +# Toggle to disable guard against peers connecting from the same ip. +allow_duplicate_ip = false + +# Peer connection configuration. +handshake_timeout = "20s" +dial_timeout = "3s" + +##### mempool configuration options ##### +[mempool] + +recheck = true +broadcast = true +wal_dir = "" + +# size of the mempool +size = 5000 + +# size of the cache (used to filter transactions we saw earlier) +cache_size = 10000 + +##### consensus configuration options ##### +[consensus] + +wal_file = "data/cs.wal/wal" + +timeout_propose = "3s" +timeout_propose_delta = "500ms" +timeout_prevote = "1s" +timeout_prevote_delta = "500ms" +timeout_precommit = "1s" +timeout_precommit_delta = "500ms" +timeout_commit = "1s" + +# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) +skip_timeout_commit = false + +# EmptyBlocks mode and possible interval between empty blocks +create_empty_blocks = true +create_empty_blocks_interval = "0s" + +# Reactor sleep duration parameters +peer_gossip_sleep_duration = "100ms" +peer_query_maj23_sleep_duration = "2s" + +# Block time parameters. Corresponds to the minimum time increment between consecutive blocks. +blocktime_iota = "1s" + +##### transactions indexer configuration options ##### +[tx_index] + +# What indexer to use for transactions +# +# Options: +# 1) "null" +# 2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). +indexer = "kv" + +# Comma-separated list of tags to index (by default the only tag is "tx.hash") +# +# You can also index transactions by height by adding "tx.height" tag here. +# +# It's recommended to index only a subset of tags due to possible memory +# bloat. This is, of course, depends on the indexer's DB and the volume of +# transactions. +index_tags = "" + +# When set to true, tells indexer to index all tags (predefined tags: +# "tx.hash", "tx.height" and all tags from DeliverTx responses). +# +# Note this may be not desirable (see the comment above). IndexTags has a +# precedence over IndexAllTags (i.e. when given both, IndexTags will be +# indexed). +index_all_tags = false + +##### instrumentation configuration options ##### +[instrumentation] + +# When true, Prometheus metrics are served under /metrics on +# PrometheusListenAddr. +# Check out the documentation for the list of available metrics. +prometheus = false + +# Address to listen for Prometheus collector(s) connections +prometheus_listen_addr = ":26660" + +# Maximum number of simultaneous connections. +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +max_open_connections = 3 + +# Instrumentation namespace +namespace = "tendermint" diff --git a/scripts/confix/testdata/v31-config.toml b/scripts/confix/testdata/v31-config.toml new file mode 100644 index 000000000..247d2da2e --- /dev/null +++ b/scripts/confix/testdata/v31-config.toml @@ -0,0 +1,292 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +##### main base config options ##### + +# TCP or UNIX socket address of the ABCI application, +# or the name of an ABCI application compiled in with the Tendermint binary +proxy_app = "tcp://127.0.0.1:26658" + +# A custom human readable name for this node +moniker = "localhost" + +# If this node is many blocks behind the tip of the chain, FastSync +# allows them to catchup quickly by downloading blocks in parallel +# and verifying their commits +fast_sync = true + +# Database backend: goleveldb | cleveldb | boltdb +# * goleveldb (github.com/syndtr/goleveldb - most popular implementation) +# - pure go +# - stable +# * cleveldb (uses levigo wrapper) +# - fast +# - requires gcc +# - use cleveldb build tag (go build -tags cleveldb) +# * boltdb (uses etcd's fork of bolt - github.com/etcd-io/bbolt) +# - EXPERIMENTAL +# - may be faster is some use-cases (random reads - indexer) +# - use boltdb build tag (go build -tags boltdb) +db_backend = "goleveldb" + +# Database directory +db_dir = "data" + +# Output level for logging, including package level options +log_level = "main:info,state:info,*:error" + +# Output format: 'plain' (colored text) or 'json' +log_format = "plain" + +##### additional base config options ##### + +# Path to the JSON file containing the initial validator set and other meta data +genesis_file = "config/genesis.json" + +# Path to the JSON file containing the private key to use as a validator in the consensus protocol +priv_validator_key_file = "config/priv_validator_key.json" + +# Path to the JSON file containing the last sign state of a validator +priv_validator_state_file = "data/priv_validator_state.json" + +# TCP or UNIX socket address for Tendermint to listen on for +# connections from an external PrivValidator process +priv_validator_laddr = "" + +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node_key_file = "config/node_key.json" + +# Mechanism to connect to the ABCI application: socket | grpc +abci = "socket" + +# TCP or UNIX socket address for the profiling server to listen on +prof_laddr = "" + +# If true, query the ABCI app on connecting to a new peer +# so the app can decide if we should keep the connection or not +filter_peers = false + +##### advanced configuration options ##### + +##### rpc server configuration options ##### +[rpc] + +# TCP or UNIX socket address for the RPC server to listen on +laddr = "tcp://0.0.0.0:26657" + +# A list of origins a cross-domain request can be executed from +# Default value '[]' disables cors support +# Use '["*"]' to allow any origin +cors_allowed_origins = [] + +# A list of methods the client is allowed to use with cross-domain requests +cors_allowed_methods = ["HEAD", "GET", "POST", ] + +# A list of non simple headers the client is allowed to use with cross-domain requests +cors_allowed_headers = ["Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time", ] + +# TCP or UNIX socket address for the gRPC server to listen on +# NOTE: This server only supports /broadcast_tx_commit +grpc_laddr = "" + +# Maximum number of simultaneous connections. +# Does not include RPC (HTTP&WebSocket) connections. See max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +grpc_max_open_connections = 900 + +# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool +unsafe = false + +# Maximum number of simultaneous connections (including WebSocket). +# Does not include gRPC connections. See grpc_max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +max_open_connections = 900 + +# Maximum number of unique clientIDs that can /subscribe +# If you're using /broadcast_tx_commit, set to the estimated maximum number +# of broadcast_tx_commit calls per block. +max_subscription_clients = 100 + +# Maximum number of unique queries a given client can /subscribe to +# If you're using GRPC (or Local RPC client) and /broadcast_tx_commit, set to +# the estimated # maximum number of broadcast_tx_commit calls per block. +max_subscriptions_per_client = 5 + +# How long to wait for a tx to be committed during /broadcast_tx_commit. +# WARNING: Using a value larger than 10s will result in increasing the +# global HTTP write timeout, which applies to all connections and endpoints. +# See https://github.com/tendermint/tendermint/issues/3435 +timeout_broadcast_tx_commit = "10s" + +# The name of a file containing certificate that is used to create the HTTPS server. +# If the certificate is signed by a certificate authority, +# the certFile should be the concatenation of the server's certificate, any intermediates, +# and the CA's certificate. +# NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. +tls_cert_file = "" + +# The name of a file containing matching private key that is used to create the HTTPS server. +# NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. +tls_key_file = "" + +##### peer to peer configuration options ##### +[p2p] + +# Address to listen for incoming connections +laddr = "tcp://0.0.0.0:26656" + +# Address to advertise to peers for them to dial +# If empty, will use the same port as the laddr, +# and will introspect on the listener or use UPnP +# to figure out the address. +external_address = "" + +# Comma separated list of seed nodes to connect to +seeds = "" + +# Comma separated list of nodes to keep persistent connections to +persistent_peers = "" + +# UPNP port forwarding +upnp = false + +# Path to address book +addr_book_file = "config/addrbook.json" + +# Set true for strict address routability rules +# Set false for private or local networks +addr_book_strict = true + +# Maximum number of inbound peers +max_num_inbound_peers = 40 + +# Maximum number of outbound peers to connect to, excluding persistent peers +max_num_outbound_peers = 10 + +# Time to wait before flushing messages out on the connection +flush_throttle_timeout = "100ms" + +# Maximum size of a message packet payload, in bytes +max_packet_msg_payload_size = 1024 + +# Rate at which packets can be sent, in bytes/second +send_rate = 5120000 + +# Rate at which packets can be received, in bytes/second +recv_rate = 5120000 + +# Set true to enable the peer-exchange reactor +pex = true + +# Seed mode, in which node constantly crawls the network and looks for +# peers. If another node asks it for addresses, it responds and disconnects. +# +# Does not work if the peer-exchange reactor is disabled. +seed_mode = false + +# Comma separated list of peer IDs to keep private (will not be gossiped to other peers) +private_peer_ids = "" + +# Toggle to disable guard against peers connecting from the same ip. +allow_duplicate_ip = false + +# Peer connection configuration. +handshake_timeout = "20s" +dial_timeout = "3s" + +##### mempool configuration options ##### +[mempool] + +recheck = true +broadcast = true +wal_dir = "" + +# Maximum number of transactions in the mempool +size = 5000 + +# Limit the total size of all txs in the mempool. +# This only accounts for raw transactions (e.g. given 1MB transactions and +# max_txs_bytes=5MB, mempool will only accept 5 transactions). +max_txs_bytes = 1073741824 + +# Size of the cache (used to filter transactions we saw earlier) in transactions +cache_size = 10000 + +##### consensus configuration options ##### +[consensus] + +wal_file = "data/cs.wal/wal" + +timeout_propose = "3s" +timeout_propose_delta = "500ms" +timeout_prevote = "1s" +timeout_prevote_delta = "500ms" +timeout_precommit = "1s" +timeout_precommit_delta = "500ms" +timeout_commit = "1s" + +# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) +skip_timeout_commit = false + +# EmptyBlocks mode and possible interval between empty blocks +create_empty_blocks = true +create_empty_blocks_interval = "0s" + +# Reactor sleep duration parameters +peer_gossip_sleep_duration = "100ms" +peer_query_maj23_sleep_duration = "2s" + +##### transactions indexer configuration options ##### +[tx_index] + +# What indexer to use for transactions +# +# Options: +# 1) "null" +# 2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). +indexer = "kv" + +# Comma-separated list of tags to index (by default the only tag is "tx.hash") +# +# You can also index transactions by height by adding "tx.height" tag here. +# +# It's recommended to index only a subset of tags due to possible memory +# bloat. This is, of course, depends on the indexer's DB and the volume of +# transactions. +index_tags = "" + +# When set to true, tells indexer to index all tags (predefined tags: +# "tx.hash", "tx.height" and all tags from DeliverTx responses). +# +# Note this may be not desirable (see the comment above). IndexTags has a +# precedence over IndexAllTags (i.e. when given both, IndexTags will be +# indexed). +index_all_tags = false + +##### instrumentation configuration options ##### +[instrumentation] + +# When true, Prometheus metrics are served under /metrics on +# PrometheusListenAddr. +# Check out the documentation for the list of available metrics. +prometheus = false + +# Address to listen for Prometheus collector(s) connections +prometheus_listen_addr = ":26660" + +# Maximum number of simultaneous connections. +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +max_open_connections = 3 + +# Instrumentation namespace +namespace = "tendermint" diff --git a/scripts/confix/testdata/v32-config.toml b/scripts/confix/testdata/v32-config.toml new file mode 100644 index 000000000..e0b897525 --- /dev/null +++ b/scripts/confix/testdata/v32-config.toml @@ -0,0 +1,319 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +# NOTE: Any path below can be absolute (e.g. "/var/myawesomeapp/data") or +# relative to the home directory (e.g. "data"). The home directory is +# "$HOME/.tendermint" by default, but could be changed via $TMHOME env variable +# or --home cmd flag. + +##### main base config options ##### + +# TCP or UNIX socket address of the ABCI application, +# or the name of an ABCI application compiled in with the Tendermint binary +proxy_app = "tcp://127.0.0.1:26658" + +# A custom human readable name for this node +moniker = "localhost" + +# If this node is many blocks behind the tip of the chain, FastSync +# allows them to catchup quickly by downloading blocks in parallel +# and verifying their commits +fast_sync = true + +# Database backend: goleveldb | cleveldb | boltdb +# * goleveldb (github.com/syndtr/goleveldb - most popular implementation) +# - pure go +# - stable +# * cleveldb (uses levigo wrapper) +# - fast +# - requires gcc +# - use cleveldb build tag (go build -tags cleveldb) +# * boltdb (uses etcd's fork of bolt - github.com/etcd-io/bbolt) +# - EXPERIMENTAL +# - may be faster is some use-cases (random reads - indexer) +# - use boltdb build tag (go build -tags boltdb) +db_backend = "goleveldb" + +# Database directory +db_dir = "data" + +# Output level for logging, including package level options +log_level = "main:info,state:info,*:error" + +# Output format: 'plain' (colored text) or 'json' +log_format = "plain" + +##### additional base config options ##### + +# Path to the JSON file containing the initial validator set and other meta data +genesis_file = "config/genesis.json" + +# Path to the JSON file containing the private key to use as a validator in the consensus protocol +priv_validator_key_file = "config/priv_validator_key.json" + +# Path to the JSON file containing the last sign state of a validator +priv_validator_state_file = "data/priv_validator_state.json" + +# TCP or UNIX socket address for Tendermint to listen on for +# connections from an external PrivValidator process +priv_validator_laddr = "" + +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node_key_file = "config/node_key.json" + +# Mechanism to connect to the ABCI application: socket | grpc +abci = "socket" + +# TCP or UNIX socket address for the profiling server to listen on +prof_laddr = "" + +# If true, query the ABCI app on connecting to a new peer +# so the app can decide if we should keep the connection or not +filter_peers = false + +##### advanced configuration options ##### + +##### rpc server configuration options ##### +[rpc] + +# TCP or UNIX socket address for the RPC server to listen on +laddr = "tcp://127.0.0.1:26657" + +# A list of origins a cross-domain request can be executed from +# Default value '[]' disables cors support +# Use '["*"]' to allow any origin +cors_allowed_origins = [] + +# A list of methods the client is allowed to use with cross-domain requests +cors_allowed_methods = ["HEAD", "GET", "POST", ] + +# A list of non simple headers the client is allowed to use with cross-domain requests +cors_allowed_headers = ["Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time", ] + +# TCP or UNIX socket address for the gRPC server to listen on +# NOTE: This server only supports /broadcast_tx_commit +grpc_laddr = "" + +# Maximum number of simultaneous connections. +# Does not include RPC (HTTP&WebSocket) connections. See max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +grpc_max_open_connections = 900 + +# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool +unsafe = false + +# Maximum number of simultaneous connections (including WebSocket). +# Does not include gRPC connections. See grpc_max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +max_open_connections = 900 + +# Maximum number of unique clientIDs that can /subscribe +# If you're using /broadcast_tx_commit, set to the estimated maximum number +# of broadcast_tx_commit calls per block. +max_subscription_clients = 100 + +# Maximum number of unique queries a given client can /subscribe to +# If you're using GRPC (or Local RPC client) and /broadcast_tx_commit, set to +# the estimated # maximum number of broadcast_tx_commit calls per block. +max_subscriptions_per_client = 5 + +# How long to wait for a tx to be committed during /broadcast_tx_commit. +# WARNING: Using a value larger than 10s will result in increasing the +# global HTTP write timeout, which applies to all connections and endpoints. +# See https://github.com/tendermint/tendermint/issues/3435 +timeout_broadcast_tx_commit = "10s" + +# Maximum size of request body, in bytes +max_body_bytes = 1000000 + +# Maximum size of request header, in bytes +max_header_bytes = 1048576 + +# The path to a file containing certificate that is used to create the HTTPS server. +# Migth be either absolute path or path related to tendermint's config directory. +# If the certificate is signed by a certificate authority, +# the certFile should be the concatenation of the server's certificate, any intermediates, +# and the CA's certificate. +# NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. +# Otherwise, HTTP server is run. +tls_cert_file = "" + +# The path to a file containing matching private key that is used to create the HTTPS server. +# Migth be either absolute path or path related to tendermint's config directory. +# NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. +# Otherwise, HTTP server is run. +tls_key_file = "" + +##### peer to peer configuration options ##### +[p2p] + +# Address to listen for incoming connections +laddr = "tcp://0.0.0.0:26656" + +# Address to advertise to peers for them to dial +# If empty, will use the same port as the laddr, +# and will introspect on the listener or use UPnP +# to figure out the address. +external_address = "" + +# Comma separated list of seed nodes to connect to +seeds = "" + +# Comma separated list of nodes to keep persistent connections to +persistent_peers = "" + +# UPNP port forwarding +upnp = false + +# Path to address book +addr_book_file = "config/addrbook.json" + +# Set true for strict address routability rules +# Set false for private or local networks +addr_book_strict = true + +# Maximum number of inbound peers +max_num_inbound_peers = 40 + +# Maximum number of outbound peers to connect to, excluding persistent peers +max_num_outbound_peers = 10 + +# Time to wait before flushing messages out on the connection +flush_throttle_timeout = "100ms" + +# Maximum size of a message packet payload, in bytes +max_packet_msg_payload_size = 1024 + +# Rate at which packets can be sent, in bytes/second +send_rate = 5120000 + +# Rate at which packets can be received, in bytes/second +recv_rate = 5120000 + +# Set true to enable the peer-exchange reactor +pex = true + +# Seed mode, in which node constantly crawls the network and looks for +# peers. If another node asks it for addresses, it responds and disconnects. +# +# Does not work if the peer-exchange reactor is disabled. +seed_mode = false + +# Comma separated list of peer IDs to keep private (will not be gossiped to other peers) +private_peer_ids = "" + +# Toggle to disable guard against peers connecting from the same ip. +allow_duplicate_ip = false + +# Peer connection configuration. +handshake_timeout = "20s" +dial_timeout = "3s" + +##### mempool configuration options ##### +[mempool] + +recheck = true +broadcast = true +wal_dir = "" + +# Maximum number of transactions in the mempool +size = 5000 + +# Limit the total size of all txs in the mempool. +# This only accounts for raw transactions (e.g. given 1MB transactions and +# max_txs_bytes=5MB, mempool will only accept 5 transactions). +max_txs_bytes = 1073741824 + +# Size of the cache (used to filter transactions we saw earlier) in transactions +cache_size = 10000 + +# Maximum size of a single transaction. +# NOTE: the max size of a tx transmitted over the network is {max_tx_bytes} + {amino overhead}. +max_tx_bytes = 1048576 + +##### fast sync configuration options ##### +[fastsync] + +# Fast Sync version to use: +# 1) "v0" (default) - the legacy fast sync implementation +# 2) "v1" - refactor of v0 version for better testability +version = "v0" + +##### consensus configuration options ##### +[consensus] + +wal_file = "data/cs.wal/wal" + +timeout_propose = "3s" +timeout_propose_delta = "500ms" +timeout_prevote = "1s" +timeout_prevote_delta = "500ms" +timeout_precommit = "1s" +timeout_precommit_delta = "500ms" +timeout_commit = "1s" + +# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) +skip_timeout_commit = false + +# EmptyBlocks mode and possible interval between empty blocks +create_empty_blocks = true +create_empty_blocks_interval = "0s" + +# Reactor sleep duration parameters +peer_gossip_sleep_duration = "100ms" +peer_query_maj23_sleep_duration = "2s" + +##### transactions indexer configuration options ##### +[tx_index] + +# What indexer to use for transactions +# +# Options: +# 1) "null" +# 2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). +indexer = "kv" + +# Comma-separated list of tags to index (by default the only tag is "tx.hash") +# +# You can also index transactions by height by adding "tx.height" tag here. +# +# It's recommended to index only a subset of tags due to possible memory +# bloat. This is, of course, depends on the indexer's DB and the volume of +# transactions. +index_tags = "" + +# When set to true, tells indexer to index all tags (predefined tags: +# "tx.hash", "tx.height" and all tags from DeliverTx responses). +# +# Note this may be not desirable (see the comment above). IndexTags has a +# precedence over IndexAllTags (i.e. when given both, IndexTags will be +# indexed). +index_all_tags = false + +##### instrumentation configuration options ##### +[instrumentation] + +# When true, Prometheus metrics are served under /metrics on +# PrometheusListenAddr. +# Check out the documentation for the list of available metrics. +prometheus = false + +# Address to listen for Prometheus collector(s) connections +prometheus_listen_addr = ":26660" + +# Maximum number of simultaneous connections. +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +max_open_connections = 3 + +# Instrumentation namespace +namespace = "tendermint" diff --git a/scripts/confix/testdata/v33-config.toml b/scripts/confix/testdata/v33-config.toml new file mode 100644 index 000000000..b728f08a1 --- /dev/null +++ b/scripts/confix/testdata/v33-config.toml @@ -0,0 +1,335 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +# NOTE: Any path below can be absolute (e.g. "/var/myawesomeapp/data") or +# relative to the home directory (e.g. "data"). The home directory is +# "$HOME/.tendermint" by default, but could be changed via $TMHOME env variable +# or --home cmd flag. + +##### main base config options ##### + +# TCP or UNIX socket address of the ABCI application, +# or the name of an ABCI application compiled in with the Tendermint binary +proxy_app = "tcp://127.0.0.1:26658" + +# A custom human readable name for this node +moniker = "localhost" + +# If this node is many blocks behind the tip of the chain, FastSync +# allows them to catchup quickly by downloading blocks in parallel +# and verifying their commits +fast_sync = true + +# Database backend: goleveldb | cleveldb | boltdb | rocksdb +# * goleveldb (github.com/syndtr/goleveldb - most popular implementation) +# - pure go +# - stable +# * cleveldb (uses levigo wrapper) +# - fast +# - requires gcc +# - use cleveldb build tag (go build -tags cleveldb) +# * boltdb (uses etcd's fork of bolt - github.com/etcd-io/bbolt) +# - EXPERIMENTAL +# - may be faster is some use-cases (random reads - indexer) +# - use boltdb build tag (go build -tags boltdb) +# * rocksdb (uses github.com/tecbot/gorocksdb) +# - EXPERIMENTAL +# - requires gcc +# - use rocksdb build tag (go build -tags rocksdb) +db_backend = "goleveldb" + +# Database directory +db_dir = "data" + +# Output level for logging, including package level options +log_level = "main:info,state:info,*:error" + +# Output format: 'plain' (colored text) or 'json' +log_format = "plain" + +##### additional base config options ##### + +# Path to the JSON file containing the initial validator set and other meta data +genesis_file = "config/genesis.json" + +# Path to the JSON file containing the private key to use as a validator in the consensus protocol +priv_validator_key_file = "config/priv_validator_key.json" + +# Path to the JSON file containing the last sign state of a validator +priv_validator_state_file = "data/priv_validator_state.json" + +# TCP or UNIX socket address for Tendermint to listen on for +# connections from an external PrivValidator process +priv_validator_laddr = "" + +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node_key_file = "config/node_key.json" + +# Mechanism to connect to the ABCI application: socket | grpc +abci = "socket" + +# TCP or UNIX socket address for the profiling server to listen on +prof_laddr = "" + +# If true, query the ABCI app on connecting to a new peer +# so the app can decide if we should keep the connection or not +filter_peers = false + +##### advanced configuration options ##### + +##### rpc server configuration options ##### +[rpc] + +# TCP or UNIX socket address for the RPC server to listen on +laddr = "tcp://127.0.0.1:26657" + +# A list of origins a cross-domain request can be executed from +# Default value '[]' disables cors support +# Use '["*"]' to allow any origin +cors_allowed_origins = [] + +# A list of methods the client is allowed to use with cross-domain requests +cors_allowed_methods = ["HEAD", "GET", "POST", ] + +# A list of non simple headers the client is allowed to use with cross-domain requests +cors_allowed_headers = ["Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time", ] + +# TCP or UNIX socket address for the gRPC server to listen on +# NOTE: This server only supports /broadcast_tx_commit +grpc_laddr = "" + +# Maximum number of simultaneous connections. +# Does not include RPC (HTTP&WebSocket) connections. See max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +grpc_max_open_connections = 900 + +# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool +unsafe = false + +# Maximum number of simultaneous connections (including WebSocket). +# Does not include gRPC connections. See grpc_max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +max_open_connections = 900 + +# Maximum number of unique clientIDs that can /subscribe +# If you're using /broadcast_tx_commit, set to the estimated maximum number +# of broadcast_tx_commit calls per block. +max_subscription_clients = 100 + +# Maximum number of unique queries a given client can /subscribe to +# If you're using GRPC (or Local RPC client) and /broadcast_tx_commit, set to +# the estimated # maximum number of broadcast_tx_commit calls per block. +max_subscriptions_per_client = 5 + +# How long to wait for a tx to be committed during /broadcast_tx_commit. +# WARNING: Using a value larger than 10s will result in increasing the +# global HTTP write timeout, which applies to all connections and endpoints. +# See https://github.com/tendermint/tendermint/issues/3435 +timeout_broadcast_tx_commit = "10s" + +# Maximum size of request body, in bytes +max_body_bytes = 1000000 + +# Maximum size of request header, in bytes +max_header_bytes = 1048576 + +# The path to a file containing certificate that is used to create the HTTPS server. +# Migth be either absolute path or path related to tendermint's config directory. +# If the certificate is signed by a certificate authority, +# the certFile should be the concatenation of the server's certificate, any intermediates, +# and the CA's certificate. +# NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. +# Otherwise, HTTP server is run. +tls_cert_file = "" + +# The path to a file containing matching private key that is used to create the HTTPS server. +# Migth be either absolute path or path related to tendermint's config directory. +# NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. +# Otherwise, HTTP server is run. +tls_key_file = "" + +##### peer to peer configuration options ##### +[p2p] + +# Address to listen for incoming connections +laddr = "tcp://0.0.0.0:26656" + +# Address to advertise to peers for them to dial +# If empty, will use the same port as the laddr, +# and will introspect on the listener or use UPnP +# to figure out the address. +external_address = "" + +# Comma separated list of seed nodes to connect to +seeds = "" + +# Comma separated list of nodes to keep persistent connections to +persistent_peers = "" + +# UPNP port forwarding +upnp = false + +# Path to address book +addr_book_file = "config/addrbook.json" + +# Set true for strict address routability rules +# Set false for private or local networks +addr_book_strict = true + +# Maximum number of inbound peers +max_num_inbound_peers = 40 + +# Maximum number of outbound peers to connect to, excluding persistent peers +max_num_outbound_peers = 10 + +# List of node IDs, to which a connection will be (re)established ignoring any existing limits +unconditional_peer_ids = "" + +# Maximum pause when redialing a persistent peer (if zero, exponential backoff is used) +persistent_peers_max_dial_period = "0s" + +# Time to wait before flushing messages out on the connection +flush_throttle_timeout = "100ms" + +# Maximum size of a message packet payload, in bytes +max_packet_msg_payload_size = 1024 + +# Rate at which packets can be sent, in bytes/second +send_rate = 5120000 + +# Rate at which packets can be received, in bytes/second +recv_rate = 5120000 + +# Set true to enable the peer-exchange reactor +pex = true + +# Seed mode, in which node constantly crawls the network and looks for +# peers. If another node asks it for addresses, it responds and disconnects. +# +# Does not work if the peer-exchange reactor is disabled. +seed_mode = false + +# Comma separated list of peer IDs to keep private (will not be gossiped to other peers) +private_peer_ids = "" + +# Toggle to disable guard against peers connecting from the same ip. +allow_duplicate_ip = false + +# Peer connection configuration. +handshake_timeout = "20s" +dial_timeout = "3s" + +##### mempool configuration options ##### +[mempool] + +recheck = true +broadcast = true +wal_dir = "" + +# Maximum number of transactions in the mempool +size = 5000 + +# Limit the total size of all txs in the mempool. +# This only accounts for raw transactions (e.g. given 1MB transactions and +# max_txs_bytes=5MB, mempool will only accept 5 transactions). +max_txs_bytes = 1073741824 + +# Size of the cache (used to filter transactions we saw earlier) in transactions +cache_size = 10000 + +# Maximum size of a single transaction. +# NOTE: the max size of a tx transmitted over the network is {max_tx_bytes} + {amino overhead}. +max_tx_bytes = 1048576 + +##### fast sync configuration options ##### +[fastsync] + +# Fast Sync version to use: +# 1) "v0" (default) - the legacy fast sync implementation +# 2) "v1" - refactor of v0 version for better testability +# 3) "v2" - refactor of v1 version for better usability +version = "v0" + +##### consensus configuration options ##### +[consensus] + +wal_file = "data/cs.wal/wal" + +timeout_propose = "3s" +timeout_propose_delta = "500ms" +timeout_prevote = "1s" +timeout_prevote_delta = "500ms" +timeout_precommit = "1s" +timeout_precommit_delta = "500ms" +timeout_commit = "1s" + +# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) +skip_timeout_commit = false + +# EmptyBlocks mode and possible interval between empty blocks +create_empty_blocks = true +create_empty_blocks_interval = "0s" + +# Reactor sleep duration parameters +peer_gossip_sleep_duration = "100ms" +peer_query_maj23_sleep_duration = "2s" + +##### transactions indexer configuration options ##### +[tx_index] + +# What indexer to use for transactions +# +# Options: +# 1) "null" +# 2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). +indexer = "kv" + +# Comma-separated list of compositeKeys to index (by default the only key is "tx.hash") +# Remember that Event has the following structure: type.key +# type: [ +# key: value, +# ... +# ] +# +# You can also index transactions by height by adding "tx.height" key here. +# +# It's recommended to index only a subset of keys due to possible memory +# bloat. This is, of course, depends on the indexer's DB and the volume of +# transactions. +index_keys = "" + +# When set to true, tells indexer to index all compositeKeys (predefined keys: +# "tx.hash", "tx.height" and all keys from DeliverTx responses). +# +# Note this may be not desirable (see the comment above). IndexKeys has a +# precedence over IndexAllKeys (i.e. when given both, IndexKeys will be +# indexed). +index_all_keys = false + +##### instrumentation configuration options ##### +[instrumentation] + +# When true, Prometheus metrics are served under /metrics on +# PrometheusListenAddr. +# Check out the documentation for the list of available metrics. +prometheus = false + +# Address to listen for Prometheus collector(s) connections +prometheus_listen_addr = ":26660" + +# Maximum number of simultaneous connections. +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +max_open_connections = 3 + +# Instrumentation namespace +namespace = "tendermint" diff --git a/scripts/confix/testdata/v34-config.toml b/scripts/confix/testdata/v34-config.toml new file mode 100644 index 000000000..0ef8b25eb --- /dev/null +++ b/scripts/confix/testdata/v34-config.toml @@ -0,0 +1,428 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +# NOTE: Any path below can be absolute (e.g. "/var/myawesomeapp/data") or +# relative to the home directory (e.g. "data"). The home directory is +# "$HOME/.tendermint" by default, but could be changed via $TMHOME env variable +# or --home cmd flag. + +####################################################################### +### Main Base Config Options ### +####################################################################### + +# TCP or UNIX socket address of the ABCI application, +# or the name of an ABCI application compiled in with the Tendermint binary +proxy_app = "tcp://127.0.0.1:26658" + +# A custom human readable name for this node +moniker = "localhost" + +# If this node is many blocks behind the tip of the chain, FastSync +# allows them to catchup quickly by downloading blocks in parallel +# and verifying their commits +fast_sync = true + +# Database backend: goleveldb | cleveldb | boltdb | rocksdb | badgerdb +# * goleveldb (github.com/syndtr/goleveldb - most popular implementation) +# - pure go +# - stable +# * cleveldb (uses levigo wrapper) +# - fast +# - requires gcc +# - use cleveldb build tag (go build -tags cleveldb) +# * boltdb (uses etcd's fork of bolt - github.com/etcd-io/bbolt) +# - EXPERIMENTAL +# - may be faster is some use-cases (random reads - indexer) +# - use boltdb build tag (go build -tags boltdb) +# * rocksdb (uses github.com/tecbot/gorocksdb) +# - EXPERIMENTAL +# - requires gcc +# - use rocksdb build tag (go build -tags rocksdb) +# * badgerdb (uses github.com/dgraph-io/badger) +# - EXPERIMENTAL +# - use badgerdb build tag (go build -tags badgerdb) +db_backend = "goleveldb" + +# Database directory +db_dir = "data" + +# Output level for logging, including package level options +log_level = "info" + +# Output format: 'plain' (colored text) or 'json' +log_format = "plain" + +##### additional base config options ##### + +# Path to the JSON file containing the initial validator set and other meta data +genesis_file = "config/genesis.json" + +# Path to the JSON file containing the private key to use as a validator in the consensus protocol +priv_validator_key_file = "config/priv_validator_key.json" + +# Path to the JSON file containing the last sign state of a validator +priv_validator_state_file = "data/priv_validator_state.json" + +# TCP or UNIX socket address for Tendermint to listen on for +# connections from an external PrivValidator process +priv_validator_laddr = "" + +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node_key_file = "config/node_key.json" + +# Mechanism to connect to the ABCI application: socket | grpc +abci = "socket" + +# If true, query the ABCI app on connecting to a new peer +# so the app can decide if we should keep the connection or not +filter_peers = false + + +####################################################################### +### Advanced Configuration Options ### +####################################################################### + +####################################################### +### RPC Server Configuration Options ### +####################################################### +[rpc] + +# TCP or UNIX socket address for the RPC server to listen on +laddr = "tcp://127.0.0.1:26657" + +# A list of origins a cross-domain request can be executed from +# Default value '[]' disables cors support +# Use '["*"]' to allow any origin +cors_allowed_origins = [] + +# A list of methods the client is allowed to use with cross-domain requests +cors_allowed_methods = ["HEAD", "GET", "POST", ] + +# A list of non simple headers the client is allowed to use with cross-domain requests +cors_allowed_headers = ["Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time", ] + +# TCP or UNIX socket address for the gRPC server to listen on +# NOTE: This server only supports /broadcast_tx_commit +grpc_laddr = "" + +# Maximum number of simultaneous connections. +# Does not include RPC (HTTP&WebSocket) connections. See max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +grpc_max_open_connections = 900 + +# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool +unsafe = false + +# Maximum number of simultaneous connections (including WebSocket). +# Does not include gRPC connections. See grpc_max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +max_open_connections = 900 + +# Maximum number of unique clientIDs that can /subscribe +# If you're using /broadcast_tx_commit, set to the estimated maximum number +# of broadcast_tx_commit calls per block. +max_subscription_clients = 100 + +# Maximum number of unique queries a given client can /subscribe to +# If you're using GRPC (or Local RPC client) and /broadcast_tx_commit, set to +# the estimated # maximum number of broadcast_tx_commit calls per block. +max_subscriptions_per_client = 5 + +# Experimental parameter to specify the maximum number of events a node will +# buffer, per subscription, before returning an error and closing the +# subscription. Must be set to at least 100, but higher values will accommodate +# higher event throughput rates (and will use more memory). +experimental_subscription_buffer_size = 200 + +# Experimental parameter to specify the maximum number of RPC responses that +# can be buffered per WebSocket client. If clients cannot read from the +# WebSocket endpoint fast enough, they will be disconnected, so increasing this +# parameter may reduce the chances of them being disconnected (but will cause +# the node to use more memory). +# +# Must be at least the same as "experimental_subscription_buffer_size", +# otherwise connections could be dropped unnecessarily. This value should +# ideally be somewhat higher than "experimental_subscription_buffer_size" to +# accommodate non-subscription-related RPC responses. +experimental_websocket_write_buffer_size = 200 + +# If a WebSocket client cannot read fast enough, at present we may +# silently drop events instead of generating an error or disconnecting the +# client. +# +# Enabling this experimental parameter will cause the WebSocket connection to +# be closed instead if it cannot read fast enough, allowing for greater +# predictability in subscription behaviour. +experimental_close_on_slow_client = false + +# How long to wait for a tx to be committed during /broadcast_tx_commit. +# WARNING: Using a value larger than 10s will result in increasing the +# global HTTP write timeout, which applies to all connections and endpoints. +# See https://github.com/tendermint/tendermint/issues/3435 +timeout_broadcast_tx_commit = "10s" + +# Maximum size of request body, in bytes +max_body_bytes = 1000000 + +# Maximum size of request header, in bytes +max_header_bytes = 1048576 + +# The path to a file containing certificate that is used to create the HTTPS server. +# Might be either absolute path or path related to Tendermint's config directory. +# If the certificate is signed by a certificate authority, +# the certFile should be the concatenation of the server's certificate, any intermediates, +# and the CA's certificate. +# NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. +# Otherwise, HTTP server is run. +tls_cert_file = "" + +# The path to a file containing matching private key that is used to create the HTTPS server. +# Might be either absolute path or path related to Tendermint's config directory. +# NOTE: both tls-cert-file and tls-key-file must be present for Tendermint to create HTTPS server. +# Otherwise, HTTP server is run. +tls_key_file = "" + +# pprof listen address (https://golang.org/pkg/net/http/pprof) +pprof_laddr = "" + +####################################################### +### P2P Configuration Options ### +####################################################### +[p2p] + +# Address to listen for incoming connections +laddr = "tcp://0.0.0.0:26656" + +# Address to advertise to peers for them to dial +# If empty, will use the same port as the laddr, +# and will introspect on the listener or use UPnP +# to figure out the address. ip and port are required +# example: 159.89.10.97:26656 +external_address = "" + +# Comma separated list of seed nodes to connect to +seeds = "" + +# Comma separated list of nodes to keep persistent connections to +persistent_peers = "" + +# UPNP port forwarding +upnp = false + +# Path to address book +addr_book_file = "config/addrbook.json" + +# Set true for strict address routability rules +# Set false for private or local networks +addr_book_strict = true + +# Maximum number of inbound peers +max_num_inbound_peers = 40 + +# Maximum number of outbound peers to connect to, excluding persistent peers +max_num_outbound_peers = 10 + +# List of node IDs, to which a connection will be (re)established ignoring any existing limits +unconditional_peer_ids = "" + +# Maximum pause when redialing a persistent peer (if zero, exponential backoff is used) +persistent_peers_max_dial_period = "0s" + +# Time to wait before flushing messages out on the connection +flush_throttle_timeout = "100ms" + +# Maximum size of a message packet payload, in bytes +max_packet_msg_payload_size = 1024 + +# Rate at which packets can be sent, in bytes/second +send_rate = 5120000 + +# Rate at which packets can be received, in bytes/second +recv_rate = 5120000 + +# Set true to enable the peer-exchange reactor +pex = true + +# Seed mode, in which node constantly crawls the network and looks for +# peers. If another node asks it for addresses, it responds and disconnects. +# +# Does not work if the peer-exchange reactor is disabled. +seed_mode = false + +# Comma separated list of peer IDs to keep private (will not be gossiped to other peers) +private_peer_ids = "" + +# Toggle to disable guard against peers connecting from the same ip. +allow_duplicate_ip = false + +# Peer connection configuration. +handshake_timeout = "20s" +dial_timeout = "3s" + +####################################################### +### Mempool Configuration Option ### +####################################################### +[mempool] + +recheck = true +broadcast = true +wal_dir = "" + +# Maximum number of transactions in the mempool +size = 5000 + +# Limit the total size of all txs in the mempool. +# This only accounts for raw transactions (e.g. given 1MB transactions and +# max_txs_bytes=5MB, mempool will only accept 5 transactions). +max_txs_bytes = 1073741824 + +# Size of the cache (used to filter transactions we saw earlier) in transactions +cache_size = 10000 + +# Do not remove invalid transactions from the cache (default: false) +# Set to true if it's not possible for any invalid transaction to become valid +# again in the future. +keep-invalid-txs-in-cache = false + +# Maximum size of a single transaction. +# NOTE: the max size of a tx transmitted over the network is {max_tx_bytes}. +max_tx_bytes = 1048576 + +# Maximum size of a batch of transactions to send to a peer +# Including space needed by encoding (one varint per transaction). +# XXX: Unused due to https://github.com/tendermint/tendermint/issues/5796 +max_batch_bytes = 0 + +####################################################### +### State Sync Configuration Options ### +####################################################### +[statesync] +# State sync rapidly bootstraps a new node by discovering, fetching, and restoring a state machine +# snapshot from peers instead of fetching and replaying historical blocks. Requires some peers in +# the network to take and serve state machine snapshots. State sync is not attempted if the node +# has any local state (LastBlockHeight > 0). The node will have a truncated block history, +# starting from the height of the snapshot. +enable = false + +# RPC servers (comma-separated) for light client verification of the synced state machine and +# retrieval of state data for node bootstrapping. Also needs a trusted height and corresponding +# header hash obtained from a trusted source, and a period during which validators can be trusted. +# +# For Cosmos SDK-based chains, trust_period should usually be about 2/3 of the unbonding time (~2 +# weeks) during which they can be financially punished (slashed) for misbehavior. +rpc_servers = "" +trust_height = 0 +trust_hash = "" +trust_period = "168h0m0s" + +# Time to spend discovering snapshots before initiating a restore. +discovery_time = "15s" + +# Temporary directory for state sync snapshot chunks, defaults to the OS tempdir (typically /tmp). +# Will create a new, randomly named directory within, and remove it when done. +temp_dir = "" + +# The timeout duration before re-requesting a chunk, possibly from a different +# peer (default: 1 minute). +chunk_request_timeout = "10s" + +# The number of concurrent chunk fetchers to run (default: 1). +chunk_fetchers = "4" + +####################################################### +### Fast Sync Configuration Connections ### +####################################################### +[fastsync] + +# Fast Sync version to use: +# 1) "v0" (default) - the legacy fast sync implementation +# 2) "v1" - refactor of v0 version for better testability +# 2) "v2" - complete redesign of v0, optimized for testability & readability +version = "v0" + +####################################################### +### Consensus Configuration Options ### +####################################################### +[consensus] + +wal_file = "data/cs.wal/wal" + +# How long we wait for a proposal block before prevoting nil +timeout_propose = "3s" +# How much timeout_propose increases with each round +timeout_propose_delta = "500ms" +# How long we wait after receiving +2/3 prevotes for “anything” (ie. not a single block or nil) +timeout_prevote = "1s" +# How much the timeout_prevote increases with each round +timeout_prevote_delta = "500ms" +# How long we wait after receiving +2/3 precommits for “anything” (ie. not a single block or nil) +timeout_precommit = "1s" +# How much the timeout_precommit increases with each round +timeout_precommit_delta = "500ms" +# How long we wait after committing a block, before starting on the new +# height (this gives us a chance to receive some more precommits, even +# though we already have +2/3). +timeout_commit = "1s" + +# How many blocks to look back to check existence of the node's consensus votes before joining consensus +# When non-zero, the node will panic upon restart +# if the same consensus key was used to sign {double_sign_check_height} last blocks. +# So, validators should stop the state machine, wait for some blocks, and then restart the state machine to avoid panic. +double_sign_check_height = 0 + +# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) +skip_timeout_commit = false + +# EmptyBlocks mode and possible interval between empty blocks +create_empty_blocks = true +create_empty_blocks_interval = "0s" + +# Reactor sleep duration parameters +peer_gossip_sleep_duration = "100ms" +peer_query_maj23_sleep_duration = "2s" + +####################################################### +### Transaction Indexer Configuration Options ### +####################################################### +[tx_index] + +# What indexer to use for transactions +# +# The application will set which txs to index. In some cases a node operator will be able +# to decide which txs to index based on configuration set in the application. +# +# Options: +# 1) "null" +# 2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). +# - When "kv" is chosen "tx.height" and "tx.hash" will always be indexed. +indexer = "kv" + +####################################################### +### Instrumentation Configuration Options ### +####################################################### +[instrumentation] + +# When true, Prometheus metrics are served under /metrics on +# PrometheusListenAddr. +# Check out the documentation for the list of available metrics. +prometheus = false + +# Address to listen for Prometheus collector(s) connections +prometheus_listen_addr = ":26660" + +# Maximum number of simultaneous connections. +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +max_open_connections = 3 + +# Instrumentation namespace +namespace = "tendermint" diff --git a/scripts/confix/testdata/v35-config.toml b/scripts/confix/testdata/v35-config.toml new file mode 100644 index 000000000..79616d6cd --- /dev/null +++ b/scripts/confix/testdata/v35-config.toml @@ -0,0 +1,527 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +# NOTE: Any path below can be absolute (e.g. "/var/myawesomeapp/data") or +# relative to the home directory (e.g. "data"). The home directory is +# "$HOME/.tendermint" by default, but could be changed via $TMHOME env variable +# or --home cmd flag. + +####################################################################### +### Main Base Config Options ### +####################################################################### + +# TCP or UNIX socket address of the ABCI application, +# or the name of an ABCI application compiled in with the Tendermint binary +proxy-app = "tcp://127.0.0.1:26658" + +# A custom human readable name for this node +moniker = "localhost" + +# Mode of Node: full | validator | seed +# * validator node +# - all reactors +# - with priv_validator_key.json, priv_validator_state.json +# * full node +# - all reactors +# - No priv_validator_key.json, priv_validator_state.json +# * seed node +# - only P2P, PEX Reactor +# - No priv_validator_key.json, priv_validator_state.json +mode = "validator" + +# Database backend: goleveldb | cleveldb | boltdb | rocksdb | badgerdb +# * goleveldb (github.com/syndtr/goleveldb - most popular implementation) +# - pure go +# - stable +# * cleveldb (uses levigo wrapper) +# - fast +# - requires gcc +# - use cleveldb build tag (go build -tags cleveldb) +# * boltdb (uses etcd's fork of bolt - github.com/etcd-io/bbolt) +# - EXPERIMENTAL +# - may be faster is some use-cases (random reads - indexer) +# - use boltdb build tag (go build -tags boltdb) +# * rocksdb (uses github.com/tecbot/gorocksdb) +# - EXPERIMENTAL +# - requires gcc +# - use rocksdb build tag (go build -tags rocksdb) +# * badgerdb (uses github.com/dgraph-io/badger) +# - EXPERIMENTAL +# - use badgerdb build tag (go build -tags badgerdb) +db-backend = "goleveldb" + +# Database directory +db-dir = "data" + +# Output level for logging, including package level options +log-level = "info" + +# Output format: 'plain' (colored text) or 'json' +log-format = "plain" + +##### additional base config options ##### + +# Path to the JSON file containing the initial validator set and other meta data +genesis-file = "config/genesis.json" + +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node-key-file = "config/node_key.json" + +# Mechanism to connect to the ABCI application: socket | grpc +abci = "socket" + +# If true, query the ABCI app on connecting to a new peer +# so the app can decide if we should keep the connection or not +filter-peers = false + + +####################################################### +### Priv Validator Configuration ### +####################################################### +[priv-validator] + +# Path to the JSON file containing the private key to use as a validator in the consensus protocol +key-file = "config/priv_validator_key.json" + +# Path to the JSON file containing the last sign state of a validator +state-file = "data/priv_validator_state.json" + +# TCP or UNIX socket address for Tendermint to listen on for +# connections from an external PrivValidator process +# when the listenAddr is prefixed with grpc instead of tcp it will use the gRPC Client +laddr = "" + +# Path to the client certificate generated while creating needed files for secure connection. +# If a remote validator address is provided but no certificate, the connection will be insecure +client-certificate-file = "" + +# Client key generated while creating certificates for secure connection +client-key-file = "" + +# Path to the Root Certificate Authority used to sign both client and server certificates +root-ca-file = "" + + +####################################################################### +### Advanced Configuration Options ### +####################################################################### + +####################################################### +### RPC Server Configuration Options ### +####################################################### +[rpc] + +# TCP or UNIX socket address for the RPC server to listen on +laddr = "tcp://127.0.0.1:26657" + +# A list of origins a cross-domain request can be executed from +# Default value '[]' disables cors support +# Use '["*"]' to allow any origin +cors-allowed-origins = [] + +# A list of methods the client is allowed to use with cross-domain requests +cors-allowed-methods = ["HEAD", "GET", "POST", ] + +# A list of non simple headers the client is allowed to use with cross-domain requests +cors-allowed-headers = ["Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time", ] + +# TCP or UNIX socket address for the gRPC server to listen on +# NOTE: This server only supports /broadcast_tx_commit +# Deprecated gRPC in the RPC layer of Tendermint will be deprecated in 0.36. +grpc-laddr = "" + +# Maximum number of simultaneous connections. +# Does not include RPC (HTTP&WebSocket) connections. See max-open-connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +# Deprecated gRPC in the RPC layer of Tendermint will be deprecated in 0.36. +grpc-max-open-connections = 900 + +# Activate unsafe RPC commands like /dial-seeds and /unsafe-flush-mempool +unsafe = false + +# Maximum number of simultaneous connections (including WebSocket). +# Does not include gRPC connections. See grpc-max-open-connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +max-open-connections = 900 + +# Maximum number of unique clientIDs that can /subscribe +# If you're using /broadcast_tx_commit, set to the estimated maximum number +# of broadcast_tx_commit calls per block. +max-subscription-clients = 100 + +# Maximum number of unique queries a given client can /subscribe to +# If you're using GRPC (or Local RPC client) and /broadcast_tx_commit, set to +# the estimated # maximum number of broadcast_tx_commit calls per block. +max-subscriptions-per-client = 5 + +# Experimental parameter to specify the maximum number of events a node will +# buffer, per subscription, before returning an error and closing the +# subscription. Must be set to at least 100, but higher values will accommodate +# higher event throughput rates (and will use more memory). +experimental-subscription-buffer-size = 200 + +# Experimental parameter to specify the maximum number of RPC responses that +# can be buffered per WebSocket client. If clients cannot read from the +# WebSocket endpoint fast enough, they will be disconnected, so increasing this +# parameter may reduce the chances of them being disconnected (but will cause +# the node to use more memory). +# +# Must be at least the same as "experimental-subscription-buffer-size", +# otherwise connections could be dropped unnecessarily. This value should +# ideally be somewhat higher than "experimental-subscription-buffer-size" to +# accommodate non-subscription-related RPC responses. +experimental-websocket-write-buffer-size = 200 + +# If a WebSocket client cannot read fast enough, at present we may +# silently drop events instead of generating an error or disconnecting the +# client. +# +# Enabling this experimental parameter will cause the WebSocket connection to +# be closed instead if it cannot read fast enough, allowing for greater +# predictability in subscription behavior. +experimental-close-on-slow-client = false + +# How long to wait for a tx to be committed during /broadcast_tx_commit. +# WARNING: Using a value larger than 10s will result in increasing the +# global HTTP write timeout, which applies to all connections and endpoints. +# See https://github.com/tendermint/tendermint/issues/3435 +timeout-broadcast-tx-commit = "10s" + +# Maximum size of request body, in bytes +max-body-bytes = 1000000 + +# Maximum size of request header, in bytes +max-header-bytes = 1048576 + +# The path to a file containing certificate that is used to create the HTTPS server. +# Might be either absolute path or path related to Tendermint's config directory. +# If the certificate is signed by a certificate authority, +# the certFile should be the concatenation of the server's certificate, any intermediates, +# and the CA's certificate. +# NOTE: both tls-cert-file and tls-key-file must be present for Tendermint to create HTTPS server. +# Otherwise, HTTP server is run. +tls-cert-file = "" + +# The path to a file containing matching private key that is used to create the HTTPS server. +# Might be either absolute path or path related to Tendermint's config directory. +# NOTE: both tls-cert-file and tls-key-file must be present for Tendermint to create HTTPS server. +# Otherwise, HTTP server is run. +tls-key-file = "" + +# pprof listen address (https://golang.org/pkg/net/http/pprof) +pprof-laddr = "" + +####################################################### +### P2P Configuration Options ### +####################################################### +[p2p] + +# Enable the legacy p2p layer. +use-legacy = false + +# Select the p2p internal queue +queue-type = "priority" + +# Address to listen for incoming connections +laddr = "tcp://0.0.0.0:26656" + +# Address to advertise to peers for them to dial +# If empty, will use the same port as the laddr, +# and will introspect on the listener or use UPnP +# to figure out the address. ip and port are required +# example: 159.89.10.97:26656 +external-address = "" + +# Comma separated list of seed nodes to connect to +# We only use these if we can’t connect to peers in the addrbook +# NOTE: not used by the new PEX reactor. Please use BootstrapPeers instead. +# TODO: Remove once p2p refactor is complete +# ref: https:#github.com/tendermint/tendermint/issues/5670 +seeds = "" + +# Comma separated list of peers to be added to the peer store +# on startup. Either BootstrapPeers or PersistentPeers are +# needed for peer discovery +bootstrap-peers = "" + +# Comma separated list of nodes to keep persistent connections to +persistent-peers = "" + +# UPNP port forwarding +upnp = false + +# Path to address book +# TODO: Remove once p2p refactor is complete in favor of peer store. +addr-book-file = "config/addrbook.json" + +# Set true for strict address routability rules +# Set false for private or local networks +addr-book-strict = true + +# Maximum number of inbound peers +# +# TODO: Remove once p2p refactor is complete in favor of MaxConnections. +# ref: https://github.com/tendermint/tendermint/issues/5670 +max-num-inbound-peers = 40 + +# Maximum number of outbound peers to connect to, excluding persistent peers +# +# TODO: Remove once p2p refactor is complete in favor of MaxConnections. +# ref: https://github.com/tendermint/tendermint/issues/5670 +max-num-outbound-peers = 10 + +# Maximum number of connections (inbound and outbound). +max-connections = 64 + +# Rate limits the number of incoming connection attempts per IP address. +max-incoming-connection-attempts = 100 + +# List of node IDs, to which a connection will be (re)established ignoring any existing limits +# TODO: Remove once p2p refactor is complete. +# ref: https://github.com/tendermint/tendermint/issues/5670 +unconditional-peer-ids = "" + +# Maximum pause when redialing a persistent peer (if zero, exponential backoff is used) +# TODO: Remove once p2p refactor is complete +# ref: https:#github.com/tendermint/tendermint/issues/5670 +persistent-peers-max-dial-period = "0s" + +# Time to wait before flushing messages out on the connection +# TODO: Remove once p2p refactor is complete +# ref: https:#github.com/tendermint/tendermint/issues/5670 +flush-throttle-timeout = "100ms" + +# Maximum size of a message packet payload, in bytes +# TODO: Remove once p2p refactor is complete +# ref: https:#github.com/tendermint/tendermint/issues/5670 +max-packet-msg-payload-size = 1400 + +# Rate at which packets can be sent, in bytes/second +# TODO: Remove once p2p refactor is complete +# ref: https:#github.com/tendermint/tendermint/issues/5670 +send-rate = 5120000 + +# Rate at which packets can be received, in bytes/second +# TODO: Remove once p2p refactor is complete +# ref: https:#github.com/tendermint/tendermint/issues/5670 +recv-rate = 5120000 + +# Set true to enable the peer-exchange reactor +pex = true + +# Comma separated list of peer IDs to keep private (will not be gossiped to other peers) +# Warning: IPs will be exposed at /net_info, for more information https://github.com/tendermint/tendermint/issues/3055 +private-peer-ids = "" + +# Toggle to disable guard against peers connecting from the same ip. +allow-duplicate-ip = false + +# Peer connection configuration. +handshake-timeout = "20s" +dial-timeout = "3s" + +####################################################### +### Mempool Configuration Option ### +####################################################### +[mempool] + +# Mempool version to use: +# 1) "v0" - The legacy non-prioritized mempool reactor. +# 2) "v1" (default) - The prioritized mempool reactor. +version = "v1" + +recheck = true +broadcast = true + +# Maximum number of transactions in the mempool +size = 5000 + +# Limit the total size of all txs in the mempool. +# This only accounts for raw transactions (e.g. given 1MB transactions and +# max-txs-bytes=5MB, mempool will only accept 5 transactions). +max-txs-bytes = 1073741824 + +# Size of the cache (used to filter transactions we saw earlier) in transactions +cache-size = 10000 + +# Do not remove invalid transactions from the cache (default: false) +# Set to true if it's not possible for any invalid transaction to become valid +# again in the future. +keep-invalid-txs-in-cache = false + +# Maximum size of a single transaction. +# NOTE: the max size of a tx transmitted over the network is {max-tx-bytes}. +max-tx-bytes = 1048576 + +# Maximum size of a batch of transactions to send to a peer +# Including space needed by encoding (one varint per transaction). +# XXX: Unused due to https://github.com/tendermint/tendermint/issues/5796 +max-batch-bytes = 0 + +# ttl-duration, if non-zero, defines the maximum amount of time a transaction +# can exist for in the mempool. +# +# Note, if ttl-num-blocks is also defined, a transaction will be removed if it +# has existed in the mempool at least ttl-num-blocks number of blocks or if it's +# insertion time into the mempool is beyond ttl-duration. +ttl-duration = "0s" + +# ttl-num-blocks, if non-zero, defines the maximum number of blocks a transaction +# can exist for in the mempool. +# +# Note, if ttl-duration is also defined, a transaction will be removed if it +# has existed in the mempool at least ttl-num-blocks number of blocks or if +# it's insertion time into the mempool is beyond ttl-duration. +ttl-num-blocks = 0 + +####################################################### +### State Sync Configuration Options ### +####################################################### +[statesync] +# State sync rapidly bootstraps a new node by discovering, fetching, and restoring a state machine +# snapshot from peers instead of fetching and replaying historical blocks. Requires some peers in +# the network to take and serve state machine snapshots. State sync is not attempted if the node +# has any local state (LastBlockHeight > 0). The node will have a truncated block history, +# starting from the height of the snapshot. +enable = false + +# State sync uses light client verification to verify state. This can be done either through the +# P2P layer or RPC layer. Set this to true to use the P2P layer. If false (default), RPC layer +# will be used. +use-p2p = false + +# If using RPC, at least two addresses need to be provided. They should be compatible with net.Dial, +# for example: "host.example.com:2125" +rpc-servers = "" + +# The hash and height of a trusted block. Must be within the trust-period. +trust-height = 0 +trust-hash = "" + +# The trust period should be set so that Tendermint can detect and gossip misbehavior before +# it is considered expired. For chains based on the Cosmos SDK, one day less than the unbonding +# period should suffice. +trust-period = "168h0m0s" + +# Time to spend discovering snapshots before initiating a restore. +discovery-time = "15s" + +# Temporary directory for state sync snapshot chunks, defaults to os.TempDir(). +# The synchronizer will create a new, randomly named directory within this directory +# and remove it when the sync is complete. +temp-dir = "" + +# The timeout duration before re-requesting a chunk, possibly from a different +# peer (default: 15 seconds). +chunk-request-timeout = "15s" + +# The number of concurrent chunk and block fetchers to run (default: 4). +fetchers = "4" + +####################################################### +### Block Sync Configuration Connections ### +####################################################### +[blocksync] + +# If this node is many blocks behind the tip of the chain, BlockSync +# allows them to catchup quickly by downloading blocks in parallel +# and verifying their commits +enable = true + +# Block Sync version to use: +# 1) "v0" (default) - the standard Block Sync implementation +# 2) "v2" - DEPRECATED, please use v0 +version = "v0" + +####################################################### +### Consensus Configuration Options ### +####################################################### +[consensus] + +wal-file = "data/cs.wal/wal" + +# How long we wait for a proposal block before prevoting nil +timeout-propose = "3s" +# How much timeout-propose increases with each round +timeout-propose-delta = "500ms" +# How long we wait after receiving +2/3 prevotes for “anything” (ie. not a single block or nil) +timeout-prevote = "1s" +# How much the timeout-prevote increases with each round +timeout-prevote-delta = "500ms" +# How long we wait after receiving +2/3 precommits for “anything” (ie. not a single block or nil) +timeout-precommit = "1s" +# How much the timeout-precommit increases with each round +timeout-precommit-delta = "500ms" +# How long we wait after committing a block, before starting on the new +# height (this gives us a chance to receive some more precommits, even +# though we already have +2/3). +timeout-commit = "1s" + +# How many blocks to look back to check existence of the node's consensus votes before joining consensus +# When non-zero, the node will panic upon restart +# if the same consensus key was used to sign {double-sign-check-height} last blocks. +# So, validators should stop the state machine, wait for some blocks, and then restart the state machine to avoid panic. +double-sign-check-height = 0 + +# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) +skip-timeout-commit = false + +# EmptyBlocks mode and possible interval between empty blocks +create-empty-blocks = true +create-empty-blocks-interval = "0s" + +# Reactor sleep duration parameters +peer-gossip-sleep-duration = "100ms" +peer-query-maj23-sleep-duration = "2s" + +####################################################### +### Transaction Indexer Configuration Options ### +####################################################### +[tx-index] + +# The backend database list to back the indexer. +# If list contains "null" or "", meaning no indexer service will be used. +# +# The application will set which txs to index. In some cases a node operator will be able +# to decide which txs to index based on configuration set in the application. +# +# Options: +# 1) "null" +# 2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). +# 3) "psql" - the indexer services backed by PostgreSQL. +# When "kv" or "psql" is chosen "tx.height" and "tx.hash" will always be indexed. +indexer = ["kv"] + +# The PostgreSQL connection configuration, the connection format: +# postgresql://:@:/? +psql-conn = "" + +####################################################### +### Instrumentation Configuration Options ### +####################################################### +[instrumentation] + +# When true, Prometheus metrics are served under /metrics on +# PrometheusListenAddr. +# Check out the documentation for the list of available metrics. +prometheus = false + +# Address to listen for Prometheus collector(s) connections +prometheus-listen-addr = ":26660" + +# Maximum number of simultaneous connections. +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +max-open-connections = 3 + +# Instrumentation namespace +namespace = "tendermint" diff --git a/scripts/confix/testdata/v36-config.toml b/scripts/confix/testdata/v36-config.toml new file mode 100644 index 000000000..e49b97d89 --- /dev/null +++ b/scripts/confix/testdata/v36-config.toml @@ -0,0 +1,479 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +# NOTE: Any path below can be absolute (e.g. "/var/myawesomeapp/data") or +# relative to the home directory (e.g. "data"). The home directory is +# "$HOME/.tendermint" by default, but could be changed via $TMHOME env variable +# or --home cmd flag. + +####################################################################### +### Main Base Config Options ### +####################################################################### + +# TCP or UNIX socket address of the ABCI application, +# or the name of an ABCI application compiled in with the Tendermint binary +proxy-app = "tcp://127.0.0.1:26658" + +# A custom human readable name for this node +moniker = "localhost" + +# Mode of Node: full | validator | seed +# * validator node +# - all reactors +# - with priv_validator_key.json, priv_validator_state.json +# * full node +# - all reactors +# - No priv_validator_key.json, priv_validator_state.json +# * seed node +# - only P2P, PEX Reactor +# - No priv_validator_key.json, priv_validator_state.json +mode = "validator" + +# Database backend: goleveldb | cleveldb | boltdb | rocksdb | badgerdb +# * goleveldb (github.com/syndtr/goleveldb - most popular implementation) +# - pure go +# - stable +# * cleveldb (uses levigo wrapper) +# - fast +# - requires gcc +# - use cleveldb build tag (go build -tags cleveldb) +# * boltdb (uses etcd's fork of bolt - github.com/etcd-io/bbolt) +# - EXPERIMENTAL +# - may be faster is some use-cases (random reads - indexer) +# - use boltdb build tag (go build -tags boltdb) +# * rocksdb (uses github.com/tecbot/gorocksdb) +# - EXPERIMENTAL +# - requires gcc +# - use rocksdb build tag (go build -tags rocksdb) +# * badgerdb (uses github.com/dgraph-io/badger) +# - EXPERIMENTAL +# - use badgerdb build tag (go build -tags badgerdb) +db-backend = "goleveldb" + +# Database directory +db-dir = "data" + +# Output level for logging, including package level options +log-level = "info" + +# Output format: 'plain' (colored text) or 'json' +log-format = "plain" + +##### additional base config options ##### + +# Path to the JSON file containing the initial validator set and other meta data +genesis-file = "config/genesis.json" + +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node-key-file = "config/node_key.json" + +# Mechanism to connect to the ABCI application: socket | grpc +abci = "socket" + +# If true, query the ABCI app on connecting to a new peer +# so the app can decide if we should keep the connection or not +filter-peers = false + + +####################################################### +### Priv Validator Configuration ### +####################################################### +[priv-validator] + +# Path to the JSON file containing the private key to use as a validator in the consensus protocol +key-file = "config/priv_validator_key.json" + +# Path to the JSON file containing the last sign state of a validator +state-file = "data/priv_validator_state.json" + +# TCP or UNIX socket address for Tendermint to listen on for +# connections from an external PrivValidator process +# when the listenAddr is prefixed with grpc instead of tcp it will use the gRPC Client +laddr = "" + +# Path to the client certificate generated while creating needed files for secure connection. +# If a remote validator address is provided but no certificate, the connection will be insecure +client-certificate-file = "" + +# Client key generated while creating certificates for secure connection +client-key-file = "" + +# Path to the Root Certificate Authority used to sign both client and server certificates +root-ca-file = "" + + +####################################################################### +### Advanced Configuration Options ### +####################################################################### + +####################################################### +### RPC Server Configuration Options ### +####################################################### +[rpc] + +# TCP or UNIX socket address for the RPC server to listen on +laddr = "tcp://127.0.0.1:26657" + +# A list of origins a cross-domain request can be executed from +# Default value '[]' disables cors support +# Use '["*"]' to allow any origin +cors-allowed-origins = [] + +# A list of methods the client is allowed to use with cross-domain requests +cors-allowed-methods = ["HEAD", "GET", "POST", ] + +# A list of non simple headers the client is allowed to use with cross-domain requests +cors-allowed-headers = ["Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time", ] + +# Activate unsafe RPC commands like /dial-seeds and /unsafe-flush-mempool +unsafe = false + +# Maximum number of simultaneous connections (including WebSocket). +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +max-open-connections = 900 + +# Maximum number of unique clientIDs that can /subscribe +# If you're using /broadcast_tx_commit, set to the estimated maximum number +# of broadcast_tx_commit calls per block. +max-subscription-clients = 100 + +# Maximum number of unique queries a given client can /subscribe to +# If you're using a Local RPC client and /broadcast_tx_commit, set this +# to the estimated maximum number of broadcast_tx_commit calls per block. +max-subscriptions-per-client = 5 + +# If true, disable the websocket interface to the RPC service. This has +# the effect of disabling the /subscribe, /unsubscribe, and /unsubscribe_all +# methods for event subscription. +# +# EXPERIMENTAL: This setting will be removed in Tendermint v0.37. +experimental-disable-websocket = false + +# The time window size for the event log. All events up to this long before +# the latest (up to EventLogMaxItems) will be available for subscribers to +# fetch via the /events method. If 0 (the default) the event log and the +# /events RPC method are disabled. +event-log-window-size = "0s" + +# The maxiumum number of events that may be retained by the event log. If +# this value is 0, no upper limit is set. Otherwise, items in excess of +# this number will be discarded from the event log. +# +# Warning: This setting is a safety valve. Setting it too low may cause +# subscribers to miss events. Try to choose a value higher than the +# maximum worst-case expected event load within the chosen window size in +# ordinary operation. +# +# For example, if the window size is 10 minutes and the node typically +# averages 1000 events per ten minutes, but with occasional known spikes of +# up to 2000, choose a value > 2000. +event-log-max-items = 0 + +# How long to wait for a tx to be committed during /broadcast_tx_commit. +# WARNING: Using a value larger than 10s will result in increasing the +# global HTTP write timeout, which applies to all connections and endpoints. +# See https://github.com/tendermint/tendermint/issues/3435 +timeout-broadcast-tx-commit = "10s" + +# Maximum size of request body, in bytes +max-body-bytes = 1000000 + +# Maximum size of request header, in bytes +max-header-bytes = 1048576 + +# The path to a file containing certificate that is used to create the HTTPS server. +# Might be either absolute path or path related to Tendermint's config directory. +# If the certificate is signed by a certificate authority, +# the certFile should be the concatenation of the server's certificate, any intermediates, +# and the CA's certificate. +# NOTE: both tls-cert-file and tls-key-file must be present for Tendermint to create HTTPS server. +# Otherwise, HTTP server is run. +tls-cert-file = "" + +# The path to a file containing matching private key that is used to create the HTTPS server. +# Might be either absolute path or path related to Tendermint's config directory. +# NOTE: both tls-cert-file and tls-key-file must be present for Tendermint to create HTTPS server. +# Otherwise, HTTP server is run. +tls-key-file = "" + +# pprof listen address (https://golang.org/pkg/net/http/pprof) +pprof-laddr = "" + +####################################################### +### P2P Configuration Options ### +####################################################### +[p2p] + +# Select the p2p internal queue +queue-type = "priority" + +# Address to listen for incoming connections +laddr = "tcp://0.0.0.0:26656" + +# Address to advertise to peers for them to dial +# If empty, will use the same port as the laddr, +# and will introspect on the listener or use UPnP +# to figure out the address. ip and port are required +# example: 159.89.10.97:26656 +external-address = "" + +# Comma separated list of seed nodes to connect to +# We only use these if we can’t connect to peers in the addrbook +# NOTE: not used by the new PEX reactor. Please use BootstrapPeers instead. +# TODO: Remove once p2p refactor is complete +# ref: https:#github.com/tendermint/tendermint/issues/5670 +seeds = "" + +# Comma separated list of peers to be added to the peer store +# on startup. Either BootstrapPeers or PersistentPeers are +# needed for peer discovery +bootstrap-peers = "" + +# Comma separated list of nodes to keep persistent connections to +persistent-peers = "" + +# UPNP port forwarding +upnp = false + +# Maximum number of connections (inbound and outbound). +max-connections = 64 + +# Rate limits the number of incoming connection attempts per IP address. +max-incoming-connection-attempts = 100 + +# Set true to enable the peer-exchange reactor +pex = true + +# Comma separated list of peer IDs to keep private (will not be gossiped to other peers) +# Warning: IPs will be exposed at /net_info, for more information https://github.com/tendermint/tendermint/issues/3055 +private-peer-ids = "" + +# Toggle to disable guard against peers connecting from the same ip. +allow-duplicate-ip = false + +# Peer connection configuration. +handshake-timeout = "20s" +dial-timeout = "3s" + +# Time to wait before flushing messages out on the connection +# TODO: Remove once MConnConnection is removed. +flush-throttle-timeout = "100ms" + +# Maximum size of a message packet payload, in bytes +# TODO: Remove once MConnConnection is removed. +max-packet-msg-payload-size = 1400 + +# Rate at which packets can be sent, in bytes/second +# TODO: Remove once MConnConnection is removed. +send-rate = 5120000 + +# Rate at which packets can be received, in bytes/second +# TODO: Remove once MConnConnection is removed. +recv-rate = 5120000 + + +####################################################### +### Mempool Configuration Option ### +####################################################### +[mempool] + +recheck = true +broadcast = true + +# Maximum number of transactions in the mempool +size = 5000 + +# Limit the total size of all txs in the mempool. +# This only accounts for raw transactions (e.g. given 1MB transactions and +# max-txs-bytes=5MB, mempool will only accept 5 transactions). +max-txs-bytes = 1073741824 + +# Size of the cache (used to filter transactions we saw earlier) in transactions +cache-size = 10000 + +# Do not remove invalid transactions from the cache (default: false) +# Set to true if it's not possible for any invalid transaction to become valid +# again in the future. +keep-invalid-txs-in-cache = false + +# Maximum size of a single transaction. +# NOTE: the max size of a tx transmitted over the network is {max-tx-bytes}. +max-tx-bytes = 1048576 + +# Maximum size of a batch of transactions to send to a peer +# Including space needed by encoding (one varint per transaction). +# XXX: Unused due to https://github.com/tendermint/tendermint/issues/5796 +max-batch-bytes = 0 + +# ttl-duration, if non-zero, defines the maximum amount of time a transaction +# can exist for in the mempool. +# +# Note, if ttl-num-blocks is also defined, a transaction will be removed if it +# has existed in the mempool at least ttl-num-blocks number of blocks or if it's +# insertion time into the mempool is beyond ttl-duration. +ttl-duration = "0s" + +# ttl-num-blocks, if non-zero, defines the maximum number of blocks a transaction +# can exist for in the mempool. +# +# Note, if ttl-duration is also defined, a transaction will be removed if it +# has existed in the mempool at least ttl-num-blocks number of blocks or if +# it's insertion time into the mempool is beyond ttl-duration. +ttl-num-blocks = 0 + +####################################################### +### State Sync Configuration Options ### +####################################################### +[statesync] +# State sync rapidly bootstraps a new node by discovering, fetching, and restoring a state machine +# snapshot from peers instead of fetching and replaying historical blocks. Requires some peers in +# the network to take and serve state machine snapshots. State sync is not attempted if the node +# has any local state (LastBlockHeight > 0). The node will have a truncated block history, +# starting from the height of the snapshot. +enable = false + +# State sync uses light client verification to verify state. This can be done either through the +# P2P layer or RPC layer. Set this to true to use the P2P layer. If false (default), RPC layer +# will be used. +use-p2p = false + +# If using RPC, at least two addresses need to be provided. They should be compatible with net.Dial, +# for example: "host.example.com:2125" +rpc-servers = "" + +# The hash and height of a trusted block. Must be within the trust-period. +trust-height = 0 +trust-hash = "" + +# The trust period should be set so that Tendermint can detect and gossip misbehavior before +# it is considered expired. For chains based on the Cosmos SDK, one day less than the unbonding +# period should suffice. +trust-period = "168h0m0s" + +# Time to spend discovering snapshots before initiating a restore. +discovery-time = "15s" + +# Temporary directory for state sync snapshot chunks, defaults to os.TempDir(). +# The synchronizer will create a new, randomly named directory within this directory +# and remove it when the sync is complete. +temp-dir = "" + +# The timeout duration before re-requesting a chunk, possibly from a different +# peer (default: 15 seconds). +chunk-request-timeout = "15s" + +# The number of concurrent chunk and block fetchers to run (default: 4). +fetchers = "4" + +####################################################### +### Consensus Configuration Options ### +####################################################### +[consensus] + +wal-file = "data/cs.wal/wal" + +# How many blocks to look back to check existence of the node's consensus votes before joining consensus +# When non-zero, the node will panic upon restart +# if the same consensus key was used to sign {double-sign-check-height} last blocks. +# So, validators should stop the state machine, wait for some blocks, and then restart the state machine to avoid panic. +double-sign-check-height = 0 + +# EmptyBlocks mode and possible interval between empty blocks +create-empty-blocks = true +create-empty-blocks-interval = "0s" + +# Reactor sleep duration parameters +peer-gossip-sleep-duration = "100ms" +peer-query-maj23-sleep-duration = "2s" + +### Unsafe Timeout Overrides ### + +# These fields provide temporary overrides for the Timeout consensus parameters. +# Use of these parameters is strongly discouraged. Using these parameters may have serious +# liveness implications for the validator and for the chain. +# +# These fields will be removed from the configuration file in the v0.37 release of Tendermint. +# For additional information, see ADR-74: +# https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-074-timeout-params.md + +# This field provides an unsafe override of the Propose timeout consensus parameter. +# This field configures how long the consensus engine will wait for a proposal block before prevoting nil. +# If this field is set to a value greater than 0, it will take effect. +# unsafe-propose-timeout-override = 0s + +# This field provides an unsafe override of the ProposeDelta timeout consensus parameter. +# This field configures how much the propose timeout increases with each round. +# If this field is set to a value greater than 0, it will take effect. +# unsafe-propose-timeout-delta-override = 0s + +# This field provides an unsafe override of the Vote timeout consensus parameter. +# This field configures how long the consensus engine will wait after +# receiving +2/3 votes in a round. +# If this field is set to a value greater than 0, it will take effect. +# unsafe-vote-timeout-override = 0s + +# This field provides an unsafe override of the VoteDelta timeout consensus parameter. +# This field configures how much the vote timeout increases with each round. +# If this field is set to a value greater than 0, it will take effect. +# unsafe-vote-timeout-delta-override = 0s + +# This field provides an unsafe override of the Commit timeout consensus parameter. +# This field configures how long the consensus engine will wait after receiving +# +2/3 precommits before beginning the next height. +# If this field is set to a value greater than 0, it will take effect. +# unsafe-commit-timeout-override = 0s + +# This field provides an unsafe override of the BypassCommitTimeout consensus parameter. +# This field configures if the consensus engine will wait for the full Commit timeout +# before proceeding to the next height. +# If this field is set to true, the consensus engine will proceed to the next height +# as soon as the node has gathered votes from all of the validators on the network. +# unsafe-bypass-commit-timeout-override = + +####################################################### +### Transaction Indexer Configuration Options ### +####################################################### +[tx-index] + +# The backend database list to back the indexer. +# If list contains "null" or "", meaning no indexer service will be used. +# +# The application will set which txs to index. In some cases a node operator will be able +# to decide which txs to index based on configuration set in the application. +# +# Options: +# 1) "null" (default) - no indexer services. +# 2) "kv" - a simple indexer backed by key-value storage (see DBBackend) +# 3) "psql" - the indexer services backed by PostgreSQL. +# When "kv" or "psql" is chosen "tx.height" and "tx.hash" will always be indexed. +indexer = ["null"] + +# The PostgreSQL connection configuration, the connection format: +# postgresql://:@:/? +psql-conn = "" + +####################################################### +### Instrumentation Configuration Options ### +####################################################### +[instrumentation] + +# When true, Prometheus metrics are served under /metrics on +# PrometheusListenAddr. +# Check out the documentation for the list of available metrics. +prometheus = false + +# Address to listen for Prometheus collector(s) connections +prometheus-listen-addr = ":26660" + +# Maximum number of simultaneous connections. +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +max-open-connections = 3 + +# Instrumentation namespace +namespace = "tendermint" diff --git a/scripts/keymigrate/migrate.go b/scripts/keymigrate/migrate.go index 2061223ad..ca2c528e2 100644 --- a/scripts/keymigrate/migrate.go +++ b/scripts/keymigrate/migrate.go @@ -15,8 +15,8 @@ import ( "math/rand" "runtime" "strconv" - "sync" + "github.com/creachadair/taskgroup" "github.com/google/orderedcode" dbm "github.com/tendermint/tm-db" ) @@ -27,7 +27,7 @@ type ( ) func getAllLegacyKeys(db dbm.DB) ([]keyID, error) { - out := []keyID{} + var out []keyID iter, err := db.Iterator(nil, nil) if err != nil { @@ -39,15 +39,12 @@ func getAllLegacyKeys(db dbm.DB) ([]keyID, error) { // make sure it's a key with a legacy format, and skip // all other keys, to make it safe to resume the migration. - if !keyIsLegacy(k) { + if !checkKeyType(k).isLegacy() { continue } - // there's inconsistency around tm-db's handling of - // key copies. - nk := make([]byte, len(k)) - copy(nk, k) - out = append(out, nk) + // Make an explicit copy, since not all tm-db backends do. + out = append(out, []byte(string(k))) } if err = iter.Error(); err != nil { @@ -61,66 +58,123 @@ func getAllLegacyKeys(db dbm.DB) ([]keyID, error) { return out, nil } -func makeKeyChan(keys []keyID) <-chan keyID { - out := make(chan keyID, len(keys)) - defer close(out) +// keyType is an enumeration for the structural type of a key. +type keyType int - for _, key := range keys { - out <- key - } +func (t keyType) isLegacy() bool { return t != nonLegacyKey } - return out +const ( + nonLegacyKey keyType = iota // non-legacy key (presumed already converted) + consensusParamsKey + abciResponsesKey + validatorsKey + stateStoreKey // state storage record + blockMetaKey // H: + blockPartKey // P: + commitKey // C: + seenCommitKey // SC: + blockHashKey // BH: + lightSizeKey // size + lightBlockKey // lb/ + evidenceCommittedKey // \x00 + evidencePendingKey // \x01 + txHeightKey // tx.height/... (special case) + abciEventKey // name/value/height/index + txHashKey // 32-byte transaction hash (unprefixed) +) + +var prefixes = []struct { + prefix []byte + ktype keyType +}{ + {[]byte("consensusParamsKey:"), consensusParamsKey}, + {[]byte("abciResponsesKey:"), abciResponsesKey}, + {[]byte("validatorsKey:"), validatorsKey}, + {[]byte("stateKey"), stateStoreKey}, + {[]byte("H:"), blockMetaKey}, + {[]byte("P:"), blockPartKey}, + {[]byte("C:"), commitKey}, + {[]byte("SC:"), seenCommitKey}, + {[]byte("BH:"), blockHashKey}, + {[]byte("size"), lightSizeKey}, + {[]byte("lb/"), lightBlockKey}, + {[]byte("\x00"), evidenceCommittedKey}, + {[]byte("\x01"), evidencePendingKey}, } -func keyIsLegacy(key keyID) bool { - for _, prefix := range []keyID{ - // core "store" - keyID("consensusParamsKey:"), - keyID("abciResponsesKey:"), - keyID("validatorsKey:"), - keyID("stateKey"), - keyID("H:"), - keyID("P:"), - keyID("C:"), - keyID("SC:"), - keyID("BH:"), - // light - keyID("size"), - keyID("lb/"), - // evidence - keyID([]byte{0x00}), - keyID([]byte{0x01}), - // tx index - keyID("tx.height/"), - keyID("tx.hash/"), - } { - if bytes.HasPrefix(key, prefix) { - return true +// checkKeyType classifies a candidate key based on its structure. +func checkKeyType(key keyID) keyType { + for _, p := range prefixes { + if bytes.HasPrefix(key, p.prefix) { + return p.ktype } } - // this means it's a tx index... - if bytes.Count(key, []byte("/")) >= 3 { - return true + // A legacy event key has the form: + // + // / / / + // + // Transaction hashes are stored as a raw binary hash with no prefix. + // + // Because a hash can contain any byte, it is possible (though unlikely) + // that a hash could have the correct form for an event key, in which case + // we would translate it incorrectly. To reduce the likelihood of an + // incorrect interpretation, we parse candidate event keys and check for + // some structural properties before making a decision. + // + // Note, though, that nothing prevents event names or values from containing + // additional "/" separators, so the parse has to be forgiving. + parts := bytes.Split(key, []byte("/")) + if len(parts) >= 4 { + // Special case for tx.height. + if len(parts) == 4 && bytes.Equal(parts[0], []byte("tx.height")) { + return txHeightKey + } + + // The name cannot be empty, but we don't know where the name ends and + // the value begins, so insist that there be something. + var n int + for _, part := range parts[:len(parts)-2] { + n += len(part) + } + // Check whether the last two fields could be .../height/index. + if n > 0 && isDecimal(parts[len(parts)-1]) && isDecimal(parts[len(parts)-2]) { + return abciEventKey + } } - return keyIsHash(key) + // If we get here, it's not an event key. Treat it as a hash if it is the + // right length. Note that it IS possible this could collide with the + // translation of some other key (though not a hash, since encoded hashes + // will be longer). The chance of that is small, but there is nothing we can + // do to detect it. + if len(key) == 32 { + return txHashKey + } + return nonLegacyKey } -func keyIsHash(key keyID) bool { - return len(key) == 32 && !bytes.Contains(key, []byte("/")) +// isDecimal reports whether buf is a non-empty sequence of Unicode decimal +// digits. +func isDecimal(buf []byte) bool { + for _, c := range buf { + if c < '0' || c > '9' { + return false + } + } + return len(buf) != 0 } -func migarateKey(key keyID) (keyID, error) { - switch { - case bytes.HasPrefix(key, keyID("H:")): +func migrateKey(key keyID) (keyID, error) { + switch checkKeyType(key) { + case blockMetaKey: val, err := strconv.Atoi(string(key[2:])) if err != nil { return nil, err } return orderedcode.Append(nil, int64(0), int64(val)) - case bytes.HasPrefix(key, keyID("P:")): + case blockPartKey: parts := bytes.Split(key[2:], []byte(":")) if len(parts) != 2 { return nil, fmt.Errorf("block parts key has %d rather than 2 components", @@ -137,55 +191,59 @@ func migarateKey(key keyID) (keyID, error) { } return orderedcode.Append(nil, int64(1), int64(valOne), int64(valTwo)) - case bytes.HasPrefix(key, keyID("C:")): + case commitKey: val, err := strconv.Atoi(string(key[2:])) if err != nil { return nil, err } return orderedcode.Append(nil, int64(2), int64(val)) - case bytes.HasPrefix(key, keyID("SC:")): + case seenCommitKey: val, err := strconv.Atoi(string(key[3:])) if err != nil { return nil, err } return orderedcode.Append(nil, int64(3), int64(val)) - case bytes.HasPrefix(key, keyID("BH:")): - val, err := strconv.Atoi(string(key[3:])) + case blockHashKey: + hash := string(key[3:]) + if len(hash)%2 == 1 { + hash = "0" + hash + } + val, err := hex.DecodeString(hash) if err != nil { return nil, err } - return orderedcode.Append(nil, int64(4), int64(val)) - case bytes.HasPrefix(key, keyID("validatorsKey:")): + return orderedcode.Append(nil, int64(4), string(val)) + case validatorsKey: val, err := strconv.Atoi(string(key[14:])) if err != nil { return nil, err } return orderedcode.Append(nil, int64(5), int64(val)) - case bytes.HasPrefix(key, keyID("consensusParamsKey:")): + case consensusParamsKey: val, err := strconv.Atoi(string(key[19:])) if err != nil { return nil, err } return orderedcode.Append(nil, int64(6), int64(val)) - case bytes.HasPrefix(key, keyID("abciResponsesKey:")): + case abciResponsesKey: val, err := strconv.Atoi(string(key[17:])) if err != nil { return nil, err } return orderedcode.Append(nil, int64(7), int64(val)) - case bytes.HasPrefix(key, keyID("stateKey")): + case stateStoreKey: return orderedcode.Append(nil, int64(8)) - case bytes.HasPrefix(key, []byte{0x00}): // committed evidence + case evidenceCommittedKey: return convertEvidence(key, 9) - case bytes.HasPrefix(key, []byte{0x01}): // pending evidence + case evidencePendingKey: return convertEvidence(key, 10) - case bytes.HasPrefix(key, keyID("lb/")): + case lightBlockKey: if len(key) < 24 { return nil, fmt.Errorf("light block evidence %q in invalid format", string(key)) } @@ -196,9 +254,9 @@ func migarateKey(key keyID) (keyID, error) { } return orderedcode.Append(nil, int64(11), int64(val)) - case bytes.HasPrefix(key, keyID("size")): + case lightSizeKey: return orderedcode.Append(nil, int64(12)) - case bytes.HasPrefix(key, keyID("tx.height")): + case txHeightKey: parts := bytes.Split(key, []byte("/")) if len(parts) != 4 { return nil, fmt.Errorf("key has %d parts rather than 4", len(parts)) @@ -221,7 +279,7 @@ func migarateKey(key keyID) (keyID, error) { } return orderedcode.Append(nil, elems...) - case bytes.Count(key, []byte("/")) >= 3: // tx indexer + case abciEventKey: parts := bytes.Split(key, []byte("/")) elems := make([]interface{}, 0, 4) @@ -257,7 +315,7 @@ func migarateKey(key keyID) (keyID, error) { elems = append(elems, string(appKey), int64(val), int64(val2)) } return orderedcode.Append(nil, elems...) - case keyIsHash(key): + case txHashKey: return orderedcode.Append(nil, "tx.hash", string(key)) default: return nil, fmt.Errorf("key %q is in the wrong format", string(key)) @@ -349,53 +407,23 @@ func Migrate(ctx context.Context, db dbm.DB) error { return err } - numWorkers := runtime.NumCPU() - wg := &sync.WaitGroup{} + var errs []string + g, start := taskgroup.New(func(err error) error { + errs = append(errs, err.Error()) + return err + }).Limit(runtime.NumCPU()) - errs := make(chan error, numWorkers) - - keyCh := makeKeyChan(keys) - - // run migrations. - for i := 0; i < numWorkers; i++ { - wg.Add(1) - go func() { - defer wg.Done() - for key := range keyCh { - err := replaceKey(db, key, migarateKey) - if err != nil { - errs <- err - } - - if ctx.Err() != nil { - return - } + for _, key := range keys { + key := key + start(func() error { + if err := ctx.Err(); err != nil { + return err } - }() + return replaceKey(db, key, migrateKey) + }) } - - // collect and process the errors. - errStrs := []string{} - signal := make(chan struct{}) - go func() { - defer close(signal) - for err := range errs { - if err == nil { - continue - } - errStrs = append(errStrs, err.Error()) - } - }() - - // Wait for everything to be done. - wg.Wait() - close(errs) - <-signal - - // check the error results - if len(errs) != 0 { - return fmt.Errorf("encountered errors during migration: %v", errStrs) + if g.Wait() != nil { + return fmt.Errorf("encountered errors during migration: %q", errs) } - return nil } diff --git a/scripts/keymigrate/migrate_test.go b/scripts/keymigrate/migrate_test.go index 21e9592fb..b2727a5df 100644 --- a/scripts/keymigrate/migrate_test.go +++ b/scripts/keymigrate/migrate_test.go @@ -27,7 +27,7 @@ func getLegacyPrefixKeys(val int) map[string][]byte { "BlockPartTwo": []byte(fmt.Sprintf("P:%d:%d", val+2, val+val)), "BlockCommit": []byte(fmt.Sprintf("C:%d", val)), "SeenCommit": []byte(fmt.Sprintf("SC:%d", val)), - "BlockHeight": []byte(fmt.Sprintf("BH:%d", val)), + "BlockHeight": []byte(fmt.Sprintf("BH:%x", val)), "Validators": []byte(fmt.Sprintf("validatorsKey:%d", val)), "ConsensusParams": []byte(fmt.Sprintf("consensusParamsKey:%d", val)), "ABCIResponse": []byte(fmt.Sprintf("abciResponsesKey:%d", val)), @@ -107,19 +107,19 @@ func TestMigration(t *testing.T) { t.Run("Legacy", func(t *testing.T) { for kind, le := range legacyPrefixes { - require.True(t, keyIsLegacy(le), kind) + require.True(t, checkKeyType(le).isLegacy(), kind) } }) t.Run("New", func(t *testing.T) { for kind, ne := range newPrefixes { - require.False(t, keyIsLegacy(ne), kind) + require.False(t, checkKeyType(ne).isLegacy(), kind) } }) t.Run("Conversion", func(t *testing.T) { for kind, le := range legacyPrefixes { - nk, err := migarateKey(le) + nk, err := migrateKey(le) require.NoError(t, err, kind) - require.False(t, keyIsLegacy(nk), kind) + require.False(t, checkKeyType(nk).isLegacy(), kind) } }) t.Run("Hashes", func(t *testing.T) { @@ -129,8 +129,12 @@ func TestMigration(t *testing.T) { } }) t.Run("ContrivedLegacyKeyDetection", func(t *testing.T) { - require.True(t, keyIsLegacy([]byte("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"))) - require.False(t, keyIsLegacy([]byte("xxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxx"))) + // length 32: should appear to be a hash + require.Equal(t, txHashKey, checkKeyType([]byte("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"))) + + // length ≠ 32: should not appear to be a hash + require.Equal(t, nonLegacyKey, checkKeyType([]byte("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx--"))) + require.Equal(t, nonLegacyKey, checkKeyType([]byte("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"))) }) }) }) @@ -159,7 +163,7 @@ func TestMigration(t *testing.T) { "UserKey3": []byte("foo/bar/baz/1.2/4"), } for kind, key := range table { - out, err := migarateKey(key) + out, err := migrateKey(key) require.Error(t, err, kind) require.Nil(t, out, kind) } @@ -177,7 +181,7 @@ func TestMigration(t *testing.T) { return nil, errors.New("hi") })) }) - t.Run("KeyDisapears", func(t *testing.T) { + t.Run("KeyDisappears", func(t *testing.T) { db := dbm.NewMemDB() key := keyID("hi") require.NoError(t, db.Set(key, []byte("world"))) @@ -204,7 +208,7 @@ func TestMigration(t *testing.T) { require.Equal(t, size, len(keys)) legacyKeys := 0 for _, k := range keys { - if keyIsLegacy(k) { + if checkKeyType(k).isLegacy() { legacyKeys++ } } @@ -212,20 +216,9 @@ func TestMigration(t *testing.T) { }) t.Run("KeyIdempotency", func(t *testing.T) { for _, key := range getNewPrefixKeys(t, 84) { - require.False(t, keyIsLegacy(key)) + require.False(t, checkKeyType(key).isLegacy()) } }) - t.Run("ChannelConversion", func(t *testing.T) { - ch := makeKeyChan([]keyID{ - makeKey(t, "abc", int64(2), int64(42)), - makeKey(t, int64(42)), - }) - count := 0 - for range ch { - count++ - } - require.Equal(t, 2, count) - }) t.Run("Migrate", func(t *testing.T) { _, db := getLegacyDatabase(t) diff --git a/scripts/protocgen.sh b/scripts/protocgen.sh deleted file mode 100755 index 8e121448b..000000000 --- a/scripts/protocgen.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# By default, this script runs against the latest commit to the master branch -# in the Tendermint spec repository. To use this script with a different version -# of the spec repository, run it with the $VERS environment variable set to the -# desired branch name or commit hash from the spec repo. - -: ${VERS:=master} - -echo "fetching proto files" - -# Get shortened ref of commit -REF=$(curl -H "Accept: application/vnd.github.v3.sha" -qL \ - "https://api.github.com/repos/tendermint/spec/commits/${VERS}" \ - | cut -c -7) - -readonly OUTDIR="tendermint-spec-${REF}" -curl -qL "https://api.github.com/repos/tendermint/spec/tarball/${REF}" | tar -xzf - ${OUTDIR}/ - -cp -r ${OUTDIR}/proto/tendermint/* ./proto/tendermint -cp -r ${OUTDIR}/third_party/** ./third_party - -MODNAME="$(go list -m)" -find ./proto/tendermint -name '*.proto' -not -path "./proto/tendermint/abci/types.proto" \ - -exec sh ./scripts/protopackage.sh {} "$MODNAME" ';' - -# For historical compatibility, the abci file needs to get a slightly different import name -# so that it can be moved into the ./abci/types directory. -sh ./scripts/protopackage.sh ./proto/tendermint/abci/types.proto $MODNAME "abci/types" - -buf generate --path proto/tendermint --template ./${OUTDIR}/buf.gen.yaml --config ./${OUTDIR}/buf.yaml - -mv ./proto/tendermint/abci/types.pb.go ./abci/types - -echo "proto files have been compiled" - -echo "removing copied files" - -find ${OUTDIR}/proto/tendermint/ -name *.proto \ - | sed "s/$OUTDIR\/\(.*\)/\1/g" \ - | xargs -I {} rm {} - -rm -rf ${OUTDIR} diff --git a/scripts/protopackage.sh b/scripts/protopackage.sh deleted file mode 100755 index a69e758ca..000000000 --- a/scripts/protopackage.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/sh -set -eo pipefail - -# This script appends the "option go_package" proto option to the file located at $FNAME. -# This option specifies what the package will be named when imported by other packages. -# This option is not directly included in the proto files to allow the files to more easily -# be hosted in github.com/tendermint/spec and shared between other repos. -# If the option is already specified in the file, it will be replaced using the -# arguments passed to this script. - -FNAME="${1:?missing required .proto filename}" -MODNAME=$(echo $2| sed 's/\//\\\//g') -PACKAGE="$(dirname $FNAME | sed 's/^\.\/\(.*\)/\1/g' | sed 's/\//\\\//g')" -if [[ ! -z "$3" ]]; then - PACKAGE="$(echo $3 | sed 's/\//\\\//g')" -fi - - -if ! grep -q 'option\s\+go_package\s\+=\s\+.*;' $FNAME; then - sed -i "s/\(package tendermint.*\)/\1\n\noption go_package = \"$MODNAME\/$PACKAGE\";/g" $FNAME -else - sed -i "s/option\s\+go_package\s\+=\s\+.*;/option go_package = \"$MODNAME\/$PACKAGE\";/g" $FNAME -fi diff --git a/scripts/scmigrate/migrate.go b/scripts/scmigrate/migrate.go new file mode 100644 index 000000000..c8e1d9490 --- /dev/null +++ b/scripts/scmigrate/migrate.go @@ -0,0 +1,197 @@ +// Package scmigrate implements a migration for SeenCommit data +// between 0.34 and 0.35 +// +// The Migrate implementation is idempotent and finds all seen commit +// records and deletes all *except* the record corresponding to the +// highest height. +package scmigrate + +import ( + "bytes" + "context" + "errors" + "fmt" + "sort" + + "github.com/gogo/protobuf/proto" + "github.com/google/orderedcode" + dbm "github.com/tendermint/tm-db" + + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "github.com/tendermint/tendermint/types" +) + +type toMigrate struct { + key []byte + commit *types.Commit +} + +const prefixSeenCommit = int64(3) + +func makeKeyFromPrefix(ids ...int64) []byte { + vals := make([]interface{}, len(ids)) + for idx := range ids { + vals[idx] = ids[idx] + } + + key, err := orderedcode.Append(nil, vals...) + if err != nil { + panic(err) + } + return key +} + +func makeToMigrate(val []byte) (*types.Commit, error) { + if len(val) == 0 { + return nil, errors.New("empty value") + } + + var pbc = new(tmproto.Commit) + + if err := proto.Unmarshal(val, pbc); err != nil { + return nil, fmt.Errorf("error reading block seen commit: %w", err) + } + + commit, err := types.CommitFromProto(pbc) + if commit == nil { + // theoretically we should error for all errors, but + // there's no reason to keep junk data in the + // database, and it makes testing easier. + if err != nil { + return nil, fmt.Errorf("error from proto commit: %w", err) + } + return nil, fmt.Errorf("missing commit") + } + + return commit, nil +} + +func sortMigrations(scData []toMigrate) { + // put this in it's own function just to make it testable + sort.SliceStable(scData, func(i, j int) bool { + return scData[i].commit.Height > scData[j].commit.Height + }) +} + +func getAllSeenCommits(ctx context.Context, db dbm.DB) ([]toMigrate, error) { + scKeyPrefix := makeKeyFromPrefix(prefixSeenCommit) + iter, err := db.Iterator( + scKeyPrefix, + makeKeyFromPrefix(prefixSeenCommit+1), + ) + if err != nil { + return nil, err + } + + scData := []toMigrate{} + for ; iter.Valid(); iter.Next() { + if err := ctx.Err(); err != nil { + return nil, err + } + + k := iter.Key() + nk := make([]byte, len(k)) + copy(nk, k) + + if !bytes.HasPrefix(nk, scKeyPrefix) { + break + } + commit, err := makeToMigrate(iter.Value()) + if err != nil { + return nil, err + } + + scData = append(scData, toMigrate{ + key: nk, + commit: commit, + }) + } + if err := iter.Error(); err != nil { + return nil, err + } + if err := iter.Close(); err != nil { + return nil, err + } + return scData, nil +} + +func renameRecord(db dbm.DB, keep toMigrate) error { + wantKey := makeKeyFromPrefix(prefixSeenCommit) + if bytes.Equal(keep.key, wantKey) { + return nil // we already did this conversion + } + + // This record's key has already been converted to the "new" format, we just + // now need to trim off the tail. + val, err := db.Get(keep.key) + if err != nil { + return err + } + + batch := db.NewBatch() + if err := batch.Delete(keep.key); err != nil { + return err + } + if err := batch.Set(wantKey, val); err != nil { + return err + } + werr := batch.Write() + cerr := batch.Close() + if werr != nil { + return werr + } + return cerr +} + +func deleteRecords(db dbm.DB, scData []toMigrate) error { + // delete all the remaining stale values in a single batch + batch := db.NewBatch() + + for _, mg := range scData { + if err := batch.Delete(mg.key); err != nil { + return err + } + } + + if err := batch.WriteSync(); err != nil { + return err + } + + if err := batch.Close(); err != nil { + return err + } + return nil +} + +func Migrate(ctx context.Context, db dbm.DB) error { + scData, err := getAllSeenCommits(ctx, db) + if err != nil { + return fmt.Errorf("sourcing tasks to migrate: %w", err) + } else if len(scData) == 0 { + return nil // nothing to do + } + + // Sort commits in decreasing order of height. + sortMigrations(scData) + + // Keep and rename the newest seen commit, delete the rest. + // In TM < v0.35 we kept a last-seen commit for each height; in v0.35 we + // retain only the latest. + keep, remove := scData[0], scData[1:] + + if err := renameRecord(db, keep); err != nil { + return fmt.Errorf("renaming seen commit record: %w", err) + } + + if len(remove) == 0 { + return nil + } + + // Remove any older seen commits. Prior to v0.35, we kept these records for + // all heights, but v0.35 keeps only the latest. + if err := deleteRecords(db, remove); err != nil { + return fmt.Errorf("writing data: %w", err) + } + + return nil +} diff --git a/scripts/scmigrate/migrate_test.go b/scripts/scmigrate/migrate_test.go new file mode 100644 index 000000000..900a15f85 --- /dev/null +++ b/scripts/scmigrate/migrate_test.go @@ -0,0 +1,174 @@ +package scmigrate + +import ( + "bytes" + "context" + "math/rand" + "testing" + + "github.com/gogo/protobuf/proto" + dbm "github.com/tendermint/tm-db" + + "github.com/tendermint/tendermint/types" +) + +func appendRandomMigrations(in []toMigrate, num int) []toMigrate { + if in == nil { + in = []toMigrate{} + } + + for i := 0; i < num; i++ { + height := rand.Int63() + if height <= 0 { + continue + } + in = append(in, toMigrate{commit: &types.Commit{Height: height}}) + } + return in +} + +func assertWellOrderedMigrations(t *testing.T, testData []toMigrate) { + t.Run("ValuesDescend", func(t *testing.T) { + for idx := range testData { + height := testData[idx].commit.Height + if idx == 0 { + continue + } + prev := testData[idx-1].commit.Height + if prev < height { + t.Fatal("height decreased in sort order") + } + } + }) + t.Run("EarliestIsZero", func(t *testing.T) { + earliestHeight := testData[len(testData)-1].commit.Height + if earliestHeight != 0 { + t.Fatalf("the earliest height is not 0: %d", earliestHeight) + } + }) +} + +func getLatestHeight(data []toMigrate) int64 { + var out int64 + + for _, d := range data { + if d.commit.Height >= out { + out = d.commit.Height + } + } + + return out +} + +func insertTestData(t *testing.T, db dbm.DB, data []toMigrate) { + t.Helper() + + batch := db.NewBatch() + + for idx, val := range data { + payload, err := proto.Marshal(val.commit.ToProto()) + if err != nil { + t.Fatal(err) + } + + if err := batch.Set(makeKeyFromPrefix(prefixSeenCommit, int64(idx)), payload); err != nil { + t.Fatal(err) + } + } + if err := batch.WriteSync(); err != nil { + t.Fatal(err) + } + if err := batch.Close(); err != nil { + t.Fatal(err) + } +} + +func TestMigrations(t *testing.T) { + t.Run("Sort", func(t *testing.T) { + t.Run("HandCraftedData", func(t *testing.T) { + testData := []toMigrate{ + {commit: &types.Commit{Height: 100}}, + {commit: &types.Commit{Height: 0}}, + {commit: &types.Commit{Height: 8}}, + {commit: &types.Commit{Height: 1}}, + } + + sortMigrations(testData) + assertWellOrderedMigrations(t, testData) + }) + t.Run("RandomGeneratedData", func(t *testing.T) { + testData := []toMigrate{{commit: &types.Commit{Height: 0}}} + + testData = appendRandomMigrations(testData, 10000) + + sortMigrations(testData) + assertWellOrderedMigrations(t, testData) + }) + }) + t.Run("InvalidMigrations", func(t *testing.T) { + if _, err := makeToMigrate(nil); err == nil { + t.Fatal("should error for nil migrations") + } + if _, err := makeToMigrate([]byte{}); err == nil { + t.Fatal("should error for empty migrations") + } + if _, err := makeToMigrate([]byte("invalid")); err == nil { + t.Fatal("should error for empty migrations") + } + }) + + t.Run("GetSeenCommits", func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + db := dbm.NewMemDB() + data := appendRandomMigrations([]toMigrate{}, 100) + insertTestData(t, db, data) + commits, err := getAllSeenCommits(ctx, db) + if err != nil { + t.Fatal(err) + } + if len(commits) != len(data) { + t.Log("inputs", len(data)) + t.Log("commits", len(commits)) + t.Fatal("migrations not found in database") + } + }) + t.Run("Integration", func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + db := dbm.NewMemDB() + data := appendRandomMigrations([]toMigrate{}, 1000) + insertTestData(t, db, data) + + latestHeight := getLatestHeight(data) + for _, test := range []string{"Migration", "Idempotency"} { + // run the test twice to make sure that it's + // safe to rerun + t.Run(test, func(t *testing.T) { + if err := Migrate(ctx, db); err != nil { + t.Fatalf("Migration failed: %v", err) + } + + post, err := getAllSeenCommits(ctx, db) + if err != nil { + t.Fatalf("Fetching seen commits: %v", err) + } + + if len(post) != 1 { + t.Fatalf("Wrong number of commits: got %d, wanted 1", len(post)) + } + + wantKey := makeKeyFromPrefix(prefixSeenCommit) + if !bytes.Equal(post[0].key, wantKey) { + t.Errorf("Seen commit key: got %x, want %x", post[0].key, wantKey) + } + if got := post[0].commit.Height; got != latestHeight { + t.Fatalf("Wrong commit height after migration: got %d, wanted %d", got, latestHeight) + } + }) + } + }) + +} diff --git a/spec/abci++/README.md b/spec/abci++/README.md index f720dae4e..38feba9d7 100644 --- a/spec/abci++/README.md +++ b/spec/abci++/README.md @@ -25,8 +25,7 @@ This allows Tendermint to run with applications written in many programming lang This specification is split as follows: -- [Basic concepts and definitions](./abci++_basic_concepts_002_draft.md) - definitions and descriptions - of concepts that are needed to understand other parts of this sepcification. +- [Overview and basic concepts](./abci++_basic_concepts_002_draft.md) - interface's overview and concepts needed to understand other parts of this specification. - [Methods](./abci++_methods_002_draft.md) - complete details on all ABCI++ methods and message types. - [Requirements for the Application](./abci++_app_requirements_002_draft.md) - formal requirements diff --git a/spec/abci++/abci++_app_requirements_002_draft.md b/spec/abci++/abci++_app_requirements_002_draft.md index 1ba523d03..620b1cd5e 100644 --- a/spec/abci++/abci++_app_requirements_002_draft.md +++ b/spec/abci++/abci++_app_requirements_002_draft.md @@ -46,33 +46,43 @@ Full execution of blocks at `PrepareProposal` time stands on Tendermint's critic Requirement 3 ensures the Application will set a value for _TimeoutPropose_ such that the time it takes to fully execute blocks in `PrepareProposal` does not interfere with Tendermint's propose timer. -* Requirement 4 [`PrepareProposal`, `ProcessProposal`, coherence]: For any two correct processes $p$ and $q$, +* Requirement 4 [`PrepareProposal`, tx-size] When $p$'s Application calls `ResponsePrepareProposal`, the + total size in bytes of the transactions returned does not exceed `RequestPrepareProposal.max_tx_bytes`. + +Busy blockchains might seek to maximize the amount of transactions included in each block. Under those conditions, +Tendermint might choose to increase the transactions passed to the Application via `RequestPrepareProposal.txs` +beyond the `RequestPrepareProposal.max_tx_bytes` limit. The idea is that, if the Application drops some of +those transactions, it can still return a transaction list whose byte size is as close to +`RequestPrepareProposal.max_tx_bytes` as possible. Thus, Requirement 4 ensures that the size in bytes of the +transaction list returned by the application will never cause the resulting block to go beyond its byte limit. + +* Requirement 5 [`PrepareProposal`, `ProcessProposal`, coherence]: For any two correct processes $p$ and $q$, if $q$'s Tendermint calls `RequestProcessProposal` on $v'_p$, $q$'s Application returns Accept in `ResponseProcessProposal`. -Requirement 4 makes sure that blocks proposed by correct processes _always_ pass the correct receiving process's +Requirement 5 makes sure that blocks proposed by correct processes _always_ pass the correct receiving process's `ProcessProposal` check. On the other hand, if there is a deterministic bug in `PrepareProposal` or `ProcessProposal` (or in both), strictly speaking, this makes all processes that hit the bug byzantine. This is a problem in practice, as very often validators are running the Application from the same codebase, so potentially _all_ would likely hit the bug at the same time. This would result in most (or all) processes prevoting `nil`, with the -serious consequences on Tendermint's liveness that this entails. Due to its criticality, Requirement 4 is a +serious consequences on Tendermint's liveness that this entails. Due to its criticality, Requirement 5 is a target for extensive testing and automated verification. -* Requirement 5 [`ProcessProposal`, determinism-1]: `ProcessProposal` is a (deterministic) function of the current +* Requirement 6 [`ProcessProposal`, determinism-1]: `ProcessProposal` is a (deterministic) function of the current state and the block that is about to be applied. In other words, for any correct process $p$, and any arbitrary block $v'$, if $p$'s Tendermint calls `RequestProcessProposal` on $v'$ at height $h$, then $p$'s Application's acceptance or rejection **exclusively** depends on $v'$ and $s_{p,h-1}$. -* Requirement 6 [`ProcessProposal`, determinism-2]: For any two correct processes $p$ and $q$, and any arbitrary block $v'$, +* Requirement 7 [`ProcessProposal`, determinism-2]: For any two correct processes $p$ and $q$, and any arbitrary block $v'$, if $p$'s (resp. $q$'s) Tendermint calls `RequestProcessProposal` on $v'$ at height $h$, then $p$'s Application accepts $v'$ if and only if $q$'s Application accepts $v'$. - Note that this requirement follows from Requirement 5 and the Agreement property of consensus. + Note that this requirement follows from Requirement 6 and the Agreement property of consensus. -Requirements 5 and 6 ensure that all correct processes will react in the same way to a proposed block, even +Requirements 6 and 7 ensure that all correct processes will react in the same way to a proposed block, even if the proposer is Byzantine. However, `ProcessProposal` may contain a bug that renders the acceptance or rejection of the block non-deterministic, and therefore prevents processes hitting -the bug from fulfilling Requirements 5 or 6 (effectively making those processes Byzantine). +the bug from fulfilling Requirements 6 or 7 (effectively making those processes Byzantine). In such a scenario, Tendermint's liveness cannot be guaranteed. Again, this is a problem in practice if most validators are running the same software, as they are likely to hit the bug at the same point. There is currently no clear solution to help with this situation, so @@ -85,43 +95,43 @@ is about to broadcast a non-`nil` precommit message, a correct process can only Let $e^r_p$ be the vote extension that the Application of a correct process $p$ returns via `ResponseExtendVote` in round $r$, height $h$. Let $w^r_p$ be the proposed block that $p$'s Tendermint passes to the Application via `RequestExtendVote` in round $r$, height $h$. -* Requirement 7 [`ExtendVote`, `VerifyVoteExtension`, coherence]: For any two correct processes $p$ and $q$, if $q$ receives $e^r_p$ +* Requirement 8 [`ExtendVote`, `VerifyVoteExtension`, coherence]: For any two correct processes $p$ and $q$, if $q$ receives $e^r_p$ from $p$ in height $h$, $q$'s Application returns Accept in `ResponseVerifyVoteExtension`. -Requirement 7 constrains the creation and handling of vote extensions in a similar way as Requirement 4 +Requirement 8 constrains the creation and handling of vote extensions in a similar way as Requirement 5 contrains the creation and handling of proposed blocks. -Requirement 7 ensures that extensions created by correct processes _always_ pass the `VerifyVoteExtension` +Requirement 8 ensures that extensions created by correct processes _always_ pass the `VerifyVoteExtension` checks performed by correct processes receiving those extensions. However, if there is a (deterministic) bug in `ExtendVote` or `VerifyVoteExtension` (or in both), -we will face the same liveness issues as described for Requirement 4, as Precommit messages with invalid vote +we will face the same liveness issues as described for Requirement 5, as Precommit messages with invalid vote extensions will be discarded. -* Requirement 8 [`VerifyVoteExtension`, determinism-1]: `VerifyVoteExtension` is a (deterministic) function of +* Requirement 9 [`VerifyVoteExtension`, determinism-1]: `VerifyVoteExtension` is a (deterministic) function of the current state, the vote extension received, and the prepared proposal that the extension refers to. In other words, for any correct process $p$, and any arbitrary vote extension $e$, and any arbitrary block $w$, if $p$'s (resp. $q$'s) Tendermint calls `RequestVerifyVoteExtension` on $e$ and $w$ at height $h$, then $p$'s Application's acceptance or rejection **exclusively** depends on $e$, $w$ and $s_{p,h-1}$. -* Requirement 9 [`VerifyVoteExtension`, determinism-2]: For any two correct processes $p$ and $q$, +* Requirement 10 [`VerifyVoteExtension`, determinism-2]: For any two correct processes $p$ and $q$, and any arbitrary vote extension $e$, and any arbitrary block $w$, if $p$'s (resp. $q$'s) Tendermint calls `RequestVerifyVoteExtension` on $e$ and $w$ at height $h$, then $p$'s Application accepts $e$ if and only if $q$'s Application accepts $e$. - Note that this requirement follows from Requirement 8 and the Agreement property of consensus. + Note that this requirement follows from Requirement 9 and the Agreement property of consensus. -Requirements 8 and 9 ensure that the validation of vote extensions will be deterministic at all +Requirements 9 and 10 ensure that the validation of vote extensions will be deterministic at all correct processes. -Requirements 8 and 9 protect against arbitrary vote extension data from Byzantine processes -similarly to Requirements 5 and 6 and proposed blocks. -Requirements 8 and 9 can be violated by a bug inducing non-determinism in +Requirements 9 and 10 protect against arbitrary vote extension data from Byzantine processes +similarly to Requirements 6 and 7 and proposed blocks. +Requirements 9 and 10 can be violated by a bug inducing non-determinism in `VerifyVoteExtension`. In this case liveness can be compromised. Extra care should be put in the implementation of `ExtendVote` and `VerifyVoteExtension` and, as a general rule, `VerifyVoteExtension` _should_ always accept the vote extension. -* Requirement 10 [_all_, no-side-effects]: $p$'s calls to `RequestPrepareProposal`, +* Requirement 11 [_all_, no-side-effects]: $p$'s calls to `RequestPrepareProposal`, `RequestProcessProposal`, `RequestExtendVote`, and `RequestVerifyVoteExtension` at height $h$ do not modify $s_{p,h-1}$. -* Requirement 11 [`ExtendVote`, `FinalizeBlock`, non-dependency]: for any correct process $p$, +* Requirement 12 [`ExtendVote`, `FinalizeBlock`, non-dependency]: for any correct process $p$, and any vote extension $e$ that $p$ received at height $h$, the computation of $s_{p,h}$ does not depend on $e$. @@ -133,16 +143,13 @@ Additionally, * in same-block execution mode, $p$'s `PrepareProposal` creates a set of transaction results $T_{p,h}$ if $p$ was the proposer of $v_{p,h}$, otherwise `FinalizeBlock` creates $T_{p,h}$. ->**TODO** I have left out all the "events" as they don't have any impact in safety or liveness ->(same for consensus params, and validator set) - -* Requirement 12 [`FinalizeBlock`, determinism-1]: For any correct process $p$, +* Requirement 13 [`FinalizeBlock`, determinism-1]: For any correct process $p$, $s_{p,h}$ exclusively depends on $s_{p,h-1}$ and $v_{p,h}$. -* Requirement 13 [`FinalizeBlock`, determinism-2]: For any correct process $p$, +* Requirement 14 [`FinalizeBlock`, determinism-2]: For any correct process $p$, the contents of $T_{p,h}$ exclusively depend on $s_{p,h-1}$ and $v_{p,h}$. -Note that Requirements 12 and 13, combined with Agreement property of consensus ensure +Note that Requirements 13 and 14, combined with Agreement property of consensus ensure the Application state evolves consistently at all correct processes. Finally, notice that neither `PrepareProposal` nor `ExtendVote` have determinism-related diff --git a/spec/abci++/abci++_basic_concepts_002_draft.md b/spec/abci++/abci++_basic_concepts_002_draft.md index d2426f31c..a1ad038a5 100644 --- a/spec/abci++/abci++_basic_concepts_002_draft.md +++ b/spec/abci++/abci++_basic_concepts_002_draft.md @@ -1,51 +1,259 @@ --- order: 1 -title: Basic concepts and definitions +title: Overview and basic concepts --- -# Basic concepts and definitions +## Outline +- [ABCI++ vs. ABCI](#abci-vs-abci) +- [Methods overview](#methods-overview) + - [Consensus methods](#consensus-methods) + - [Mempool methods](#mempool-methods) + - [Info methods](#info-methods) + - [State-sync methods](#state-sync-methods) +- [Next-block execution vs. same-block execution](#next-block-execution-vs-same-block-execution) + - [Tendermint timeouts](#tendermint-timeouts-in-same-block-execution) +- [Determinism](#determinism) +- [Errors](#errors) +- [Events](#events) +- [Evidence](#evidence) -## Connections +# Overview and basic concepts -ABCI++ applications can run either within the _same_ process as the Tendermint -state-machine replication engine, or as a _separate_ process from the state-machine -replication engine. When run within the same process, Tendermint will call the ABCI++ -application methods directly as Go method calls. +## ABCI++ vs. ABCI +[↑ Back to Outline](#outline) -When Tendermint and the ABCI++ application are run as separate processes, Tendermint -opens four connections to the application for ABCI++ methods. The connections each -handle a subset of the ABCI++ method calls. These subsets are defined as follows: +With ABCI, the application can only act at one phase in consensus, immediately after a block has been finalized. This restriction on the application prevents numerous features for the application, including many scalability improvements that are now better understood than when ABCI was first written. For example, many of the scalability proposals can be boiled down to "Make the miner / block proposers / validators do work, so the network does not have to". This includes optimizations such as tx-level signature aggregation, state transition proofs, etc. Furthermore, many new security properties cannot be achieved in the current paradigm, as the application cannot enforce validators to do more than just finalize txs. This includes features such as threshold cryptography, and guaranteed IBC connection attempts. -### **Consensus** connection +ABCI++ overcomes these limitations by allowing the application to intervene at three key places of the block execution. The new interface allows block proposers to perform application-dependent work in a block through the `PrepareProposal` method; validators to perform application-dependent work in a proposed block through the `ProcessProposal` method; and applications to require their validators do more than just validate blocks, e.g., validator guaranteed IBC connection attempts, through the `ExtendVote` and `VerifyVoteExtension` methods. Furthermore, ABCI++ renames {`BeginBlock`, [`DeliverTx`], `EndBlock`} to `FinalizeBlock`, as a simplified way to deliver a decided block to the Application. -* Driven by a consensus protocol and is responsible for block execution. -* Handles the `InitChain`, `PrepareProposal`, `ProcessProposal`, `ExtendVote`, - `VerifyVoteExtension`, and `FinalizeBlock` method calls. +## Methods overview +[↑ Back to Outline](#outline) -### **Mempool** connection +Methods can be classified into four categories: consensus, mempool, info, and state-sync. -* For validating new transactions, before they're shared or included in a block. -* Handles the `CheckTx` calls. +### Consensus/block execution methods -### **Info** connection +The first time a new blockchain is started, Tendermint calls +`InitChain`. From then on, method `FinalizeBlock` is executed at the end of each +block, resulting in an updated Application state. +During consensus execution of a block height, before method `FinalizeBlock` is +called, methods `PrepareProposal`, `ProcessProposal`, `ExtendVote`, and +`VerifyVoteExtension` may be called several times. +See [Tendermint's expected behavior](abci++_tmint_expected_behavior_002_draft.md) +for details on the possible call sequences of these methods. -* For initialization and for queries from the user. -* Handles the `Info` and `Query` calls. +* [**InitChain:**](./abci++_methods_002_draft.md#initchain) This method initializes the blockchain. Tendermint calls it once upon genesis. -### **Snapshot** connection +* [**PrepareProposal:**](./abci++_methods_002_draft.md#prepareproposal) It allows the block proposer to perform application-dependent work in a block before using it as its proposal. This enables, for instance, batch optimizations to a block, which has been empirically demonstrated to be a key component for scaling. Method `PrepareProposal` is called every time Tendermint is about to send +a proposal message, but no previous proposal has been locked at Tendermint level. +Tendermint gathers outstanding transactions from the mempool, generates a block header, and uses +them to create a block to propose. Then, it calls `RequestPrepareProposal` +with the newly created proposal, called _raw proposal_. The Application can +make changes to the raw proposal, such as modifying transactions, and returns +the (potentially) modified proposal, called _prepared proposal_ in the +`Response*` call. The logic modifying the raw proposal can be non-deterministic. -* For serving and restoring [state sync snapshots](../abci/apps.md#state-sync). -* Handles the `ListSnapshots`, `LoadSnapshotChunk`, `OfferSnapshot`, and `ApplySnapshotChunk` calls. +* [**ProcessProposal:**](./abci++_methods_002_draft.md#processproposal) It allows a validator to perform application-dependent work in a proposed block. This enables features such as allowing validators to reject a block according to whether the state machine deems it valid, and changing the block execution pipeline. Tendermint calls it when it receives a proposal and it is not locked on a block. The Application cannot +modify the proposal at this point but can reject it if it realizes it is invalid. +If that is the case, Tendermint will prevote `nil` on the proposal, which has +strong liveness implications for Tendermint. As a general rule, the Application +SHOULD accept a prepared proposal passed via `ProcessProposal`, even if a part of +the proposal is invalid (e.g., an invalid transaction); the Application can +ignore the invalid part of the prepared proposal at block execution time. -Additionally, there is a `Flush` method that is called on every connection, -and an `Echo` method that is just for debugging. +* [**ExtendVote:**](./abci++_methods_002_draft.md#extendvote) It allows applications to force their validators to do more than just validate within consensus. `ExtendVote` allows applications to include non-deterministic data, opaque to Tendermint, to precommit messages (the final round of voting). +The data, called _vote extension_, will also be made available to the +application in the next height, along with the vote it is extending, in the rounds +where the local process is the proposer. +If the Application does not have vote extension information to provide, it returns a 0-length byte array as its vote extension. +Tendermint calls `ExtendVote` when is about to send a non-`nil` precommit message. ->**TODO** Figure out what to do with this. +* [**VerifyVoteExtension:**](./abci++_methods_002_draft.md#verifyvoteextension) It allows validators to validate the vote extension data attached to a precommit message. If the validation fails, the precommit message will be deemed invalid and ignored +by Tendermint. This has a negative impact on Tendermint's liveness, i.e., if vote extensions repeatedly cannot be verified by correct validators, Tendermint may not be able to finalize a block even if sufficiently many (+2/3) of the validators send precommit votes for that block. Thus, `VerifyVoteExtension` should be used with special care. +As a general rule, an Application that detects an invalid vote extension SHOULD +accept it in `ResponseVerifyVoteExtension` and ignore it in its own logic. Tendermint calls it when +a process receives a precommit message with a (possibly empty) vote extension. + +* [**FinalizeBlock:**](./abci++_methods_002_draft.md#finalizeblock) It delivers a decided block to the Application. The Application must execute the transactions in the block in order and update its state accordingly. Cryptographic commitments to the block and transaction results, via the corresponding +parameters in `ResponseFinalizeBlock`, are included in the header of the next block. Tendermint calls it when a new block is decided. + +### Mempool methods + +* [**CheckTx:**](./abci++_methods_002_draft.md#checktx) This method allows the Application to validate transactions against its current state, e.g., checking signatures and account balances. If a transaction passes the validation, then tendermint adds it to its local mempool, discarding it otherwise. Tendermint calls it when it receives a new transaction either coming from an external user or another node. Furthermore, Tendermint can be configured to re-call `CheckTx` on any decided transaction (after `FinalizeBlock`). + +### Info methods + +* [**Info:**](./abci++_methods_002_draft.md#info) Used to sync Tendermint with the Application during a handshake that happens on startup. + +* [**Query:**](./abci++_methods_002_draft.md#query) Clients can use this method to query the Application for information about the application state. + +### State-sync methods + +State sync allows new nodes to rapidly bootstrap by discovering, fetching, and applying +state machine snapshots instead of replaying historical blocks. For more details, see the +[state sync section](../p2p/messages/state-sync.md). + +New nodes will discover and request snapshots from other nodes in the P2P network. +A Tendermint node that receives a request for snapshots from a peer will call +`ListSnapshots` on its Application. The Application returns the list of locally avaiable snapshots. +Note that the list does not contain the actual snapshot but metadata about it: height at which the snapshot was taken, application-specific verification data and more (see [snapshot data type](./abci++_methods_002_draft.md#snapshot) for more details). After receiving a list of available snapshots from a peer, the new node can offer any of the snapshots in the list to its local Application via the `OfferSnapshot` method. The Application can check at this point the validity of the snapshot metadata. + +Snapshots may be quite large and are thus broken into smaller "chunks" that can be +assembled into the whole snapshot. Once the Application accepts a snapshot and +begins restoring it, Tendermint will fetch snapshot "chunks" from existing nodes. +The node providing "chunks" will fetch them from its local Application using +the `LoadSnapshotChunk` method. + +As the new node receives "chunks" it will apply them sequentially to the local +application with `ApplySnapshotChunk`. When all chunks have been applied, the +Application's `AppHash` is retrieved via an `Info` query. +To ensure that the sync proceeded correctly, Tendermint compares the local Application's `AppHash` to the `AppHash` stored on the blockchain (verified via +[light client verification](../light-client/verification/README.md)). + +In summary: + +* [**ListSnapshots:**](./abci++_methods_002_draft.md#listsnapshots) Used by nodes to discover available snapshots on peers. + +* [**LoadSnapshotChunk:**](./abci++_methods_002_draft.md#loadsnapshotchunk) Used by Tendermint to retrieve snapshot chunks from the application to send to peers. + +* [**OfferSnapshot:**](./abci++_methods_002_draft.md#offersnapshot) When a node receives a snapshot from a peer, Tendermint uses this method to offer the snapshot to the Application. + +* [**ApplySnapshotChunk:**](./abci++_methods_002_draft.md#applysnapshotchunk) Used by Tendermint to hand snapshot chunks to the Application. + +### Other methods + +Additionally, there is a [**Flush**](./abci++_methods_002_draft.md#flush) method that is called on every connection, +and an [**Echo**](./abci++_methods_002_draft.md#echo) method that is just for debugging. More details on managing state across connections can be found in the section on [ABCI Applications](../abci/apps.md). +## Next-block execution vs. same-block execution +[↑ Back to Outline](#outline) + +In the original ABCI protocol, the only moment when the Application had access to a +block was after it was decided. This led to a block execution model, called _next-block +execution_, where some fields hashed in a block header refer to the execution of the +previous block, namely: + +* the Merkle root of the Application's state +* the transaction results +* the consensus parameter updates +* the validator updates + +With ABCI++, an Application may decide to keep using the next-block execution model, by doing all its processing in `FinalizeBlock`; +however the new methods introduced, `PrepareProposal` and `ProcessProposal` allow +for a new execution model, called _same-block execution_. An Application implementing +this execution model, upon receiving a raw proposal via `RequestPrepareProposal` +and potentially modifying its transaction list, +fully executes the resulting prepared proposal as though it was the decided block. +The results of the block execution are used as follows: + +* The block execution may generate a set of events. The Application should store these events and return them back to Tendermint during the `FinalizeBlock` call if the block is finally decided. +* The Merkle root resulting from executing the prepared proposal is provided in + `ResponsePrepareProposal` and thus refers to the **current block**. Tendermint + will use it in the prepared proposal's header. +* likewise, the transaction results from executing the prepared proposal are + provided in `ResponsePrepareProposal` and refer to the transactions in the + **current block**. Tendermint will use them to calculate the results hash + in the prepared proposal's header. +* The consensus parameter updates and validator updates are also provided in + `ResponsePrepareProposal` and reflect the result of the prepared proposal's + execution. They come into force in height H+1 (as opposed to the H+2 rule + in next-block execution model). + +If the Application decides to keep the next-block execution model, it will not +provide any data in `ResponsePrepareProposal`, other than an optionally modified +transaction list. + +In the long term, the execution model will be set in a new boolean parameter +*same_block* in `ConsensusParams`. +It **must not** be changed once the blockchain has started unless the Application +developers _really_ know what they are doing. +However, modifying `ConsensusParams` structure cannot be done lightly if we are to +preserve blockchain compatibility. Therefore we need an interim solution until +soft upgrades are specified and implemented in Tendermint. This somewhat _unsafe_ +solution consists in Tendermint assuming same-block execution if the Application +fills the above mentioned fields in `ResponsePrepareProposal`. + +### Tendermint timeouts in same-block execution + +The new same-block execution mode requires the Application to fully execute the +prepared block at `PrepareProposal` time. This execution is synchronous, so +Tendermint cannot make progress until the Application returns from `PrepareProposal`. +This stands on Tendermint's critical path: if the Application takes a long time +executing the block, the default value of _TimeoutPropose_ might not be sufficient +to accommodate the long block execution time and non-proposer processes might time +out and prevote `nil`, thus starting a further round unnecessarily. + +The Application is the best suited to provide a value for _TimeoutPropose_ so +that the block execution time upon `PrepareProposal` fits well in the propose +timeout interval. + +Currently, the Application can override the value of _TimeoutPropose_ via the +`config.toml` file. In the future, `ConsensusParams` will have an extra field +with the current _TimeoutPropose_ value so that the Application can adapt it at every height. + +## Determinism +[↑ Back to Outline](#outline) + +ABCI++ applications must implement deterministic finite-state machines to be +securely replicated by the Tendermint consensus engine. This means block execution +over the Consensus Connection must be strictly deterministic: given the same +ordered set of transactions, all nodes will compute identical responses, for all +successive `FinalizeBlock` calls. This is critical because the +responses are included in the header of the next block, either via a Merkle root +or directly, so all nodes must agree on exactly what they are. + +For this reason, it is recommended that application state is not exposed to any +external user or process except via the ABCI connections to a consensus engine +like Tendermint Core. The Application must only change its state based on input +from block execution (`FinalizeBlock` calls), and not through +any other kind of request. This is the only way to ensure all nodes see the same +transactions and compute the same results. + +Some Applications may choose to execute the blocks that are about to be proposed +(via `PrepareProposal`), or those that the Application is asked to validate +(via `ProcessProposal`). However, the state changes caused by processing those +proposed blocks must never replace the previous state until `FinalizeBlock` confirms +the block decided. + +Additionally, vote extensions or the validation thereof (via `ExtendVote` or +`VerifyVoteExtension`) must _never_ have side effects on the current state. +They can only be used when their data is provided in a `RequestPrepareProposal` call. + +If there is some non-determinism in the state machine, consensus will eventually +fail as nodes disagree over the correct values for the block header. The +non-determinism must be fixed and the nodes restarted. + +Sources of non-determinism in applications may include: + +* Hardware failures + * Cosmic rays, overheating, etc. +* Node-dependent state + * Random numbers + * Time +* Underspecification + * Library version changes + * Race conditions + * Floating point numbers + * JSON or protobuf serialization + * Iterating through hash-tables/maps/dictionaries +* External Sources + * Filesystem + * Network calls (eg. some external REST API service) + +See [#56](https://github.com/tendermint/abci/issues/56) for original discussion. + +Note that some methods (`Query, CheckTx, FinalizeBlock`) return +explicitly non-deterministic data in the form of `Info` and `Log` fields. The `Log` is +intended for the literal output from the Application's logger, while the +`Info` is any additional info that should be returned. These are the only fields +that are not included in block header computations, so we don't need agreement +on them. All other fields in the `Response*` must be strictly deterministic. + ## Errors +[↑ Back to Outline](#outline) The `Query`, and `CheckTx` methods include a `Code` field in their `Response*`. The `Code` field is also included in type `TxResult`, used by @@ -75,8 +283,6 @@ The handling of non-zero response codes by Tendermint is described below ### `CheckTx` -The `CheckTx` ABCI++ method controls what transactions are considered for inclusion -in a block. When Tendermint receives a `ResponseCheckTx` with a non-zero `Code`, the associated transaction will not be added to Tendermint's mempool or it will be removed if it is already included. @@ -91,11 +297,11 @@ Tendermint consensus. ### `Query` -The `Query` ABCI++ method queries the Application for information about application state. When Tendermint receives a `ResponseQuery` with a non-zero `Code`, this code is returned directly to the client that initiated the query. ## Events +[↑ Back to Outline](#outline) Method `CheckTx` includes an `Events` field in its `Response*`. Method `FinalizeBlock` includes an `Events` field at the top level in its @@ -171,11 +377,12 @@ Example: } ``` -## EvidenceType +## Evidence +[↑ Back to Outline](#outline) Tendermint's security model relies on the use of "evidence". Evidence is proof of -malicious behaviour by a network participant. It is the responsibility of Tendermint -to detect such malicious behaviour. When malicious behavior is detected, Tendermint +malicious behavior by a network participant. It is the responsibility of Tendermint +to detect such malicious behavior. When malicious behavior is detected, Tendermint will gossip evidence of the behavior to other nodes and commit the evidence to the chain once it is verified by all validators. This evidence will then be passed on to the Application through ABCI++. It is the responsibility of the @@ -195,208 +402,3 @@ There are two forms of evidence: Duplicate Vote and Light Client Attack. More information can be found in either [data structures](../core/data_structures.md) or [accountability](../light-client/accountability/) -## Vote Extensions - -According to the Tendermint algorithm, a proposed block needs at least a predefined -number of precommit votes in order to be decided. Tendermint gathers all the valid -precommit votes for the decided block that it receives before the block is decided, -and then includes these votes in the proposed block for the next height whenever -the local process is the proposer of the round. - -When Tendermint's consensus is about to send a non-`nil` precommit message, it calls -method `ExtendVote`, which gives the Application the opportunity to include -non-deterministic data, opaque to Tendermint, that will be attached to the precommit -message. The data, called _vote extension_, will also be made available to the -application in the next height, along with the vote it is extending, in the rounds -where the local process is the proposer. - -The vote extension data is split into two parts, one signed by Tendermint as part -of the vote data structure, and the other (optionally) signed by the Application. -The Application may also choose not to include any vote extension. -When another process receives a precommit message with a vote extension, it calls -method `VerifyVoteExtension` so that the Application can validate the data received. -If the validation fails, the precommit message will be deemed invalid and ignored -by Tendermint. This has negative impact on Tendermint's liveness, i.e., if repeatedly vote extensions by correct validators cannot be verified by correct validators, Tendermint may not be able to finalize a block even if sufficiently many (+2/3) of the validators send precommit votes for that block. Thus, `VerifyVoteExtension` should only be used with special care. -As a general rule, an Application that detects an invalid vote extension SHOULD -accept it in `ResponseVerifyVoteExtension` and ignore it in its own logic. - -## Determinism - -ABCI++ applications must implement deterministic finite-state machines to be -securely replicated by the Tendermint consensus engine. This means block execution -over the Consensus Connection must be strictly deterministic: given the same -ordered set of requests, all nodes will compute identical responses, for all -successive `FinalizeBlock` calls. This is critical, because the -responses are included in the header of the next block, either via a Merkle root -or directly, so all nodes must agree on exactly what they are. - -For this reason, it is recommended that applications not be exposed to any -external user or process except via the ABCI connections to a consensus engine -like Tendermint Core. The Application must only change its state based on input -from block execution (`FinalizeBlock` calls), and not through -any other kind of request. This is the only way to ensure all nodes see the same -transactions and compute the same results. - -Some Applications may choose to execute the blocks that are about to be proposed -(via `PrepareProposal`), or those that the Application is asked to validate -(via `Processproposal`). However the state changes caused by processing those -proposed blocks must never replace the previous state until `FinalizeBlock` confirms -the block decided. - -Additionally, vote extensions or the validation thereof (via `ExtendVote` or -`VerifyVoteExtension`) must _never_ have side effects on the current state. -They can only be used when their data is included in a block. - -If there is some non-determinism in the state machine, consensus will eventually -fail as nodes disagree over the correct values for the block header. The -non-determinism must be fixed and the nodes restarted. - -Sources of non-determinism in applications may include: - -* Hardware failures - * Cosmic rays, overheating, etc. -* Node-dependent state - * Random numbers - * Time -* Underspecification - * Library version changes - * Race conditions - * Floating point numbers - * JSON or protobuf serialization - * Iterating through hash-tables/maps/dictionaries -* External Sources - * Filesystem - * Network calls (eg. some external REST API service) - -See [#56](https://github.com/tendermint/abci/issues/56) for original discussion. - -Note that some methods (`Query, CheckTx, FinalizeBlock`) return -explicitly non-deterministic data in the form of `Info` and `Log` fields. The `Log` is -intended for the literal output from the Application's logger, while the -`Info` is any additional info that should be returned. These are the only fields -that are not included in block header computations, so we don't need agreement -on them. All other fields in the `Response*` must be strictly deterministic. - -## Block Execution - -The first time a new blockchain is started, Tendermint calls -`InitChain`. From then on, method `FinalizeBlock` is executed at the end of each -block, resulting in an updated Application state. -During consensus execution of a block height, before method `FinalizeBlock` is -called, methods `PrepareProposal`, `ProcessProposal`, `ExtendVote`, and -`VerifyVoteExtension` may be called a number of times. -See [Tendermint's expected behavior](abci++_tmint_expected_behavior_002_draft.md) -for details on the possible call sequences of these methods. - -Method `PrepareProposal` is called every time Tendermint is about to send -a proposal message, but no previous proposal has been locked at Tendermint level. -Tendermint gathers outstanding transactions from the mempool -(see [PrepareProposal](#PrepareProposal)), generates a block header and uses -them to create a block to propose. Then, it calls `RequestPrepareProposal` -with the newly created proposal, called _raw proposal_. The Application can -make changes to the raw proposal, such as modifying transactions, and returns -the (potentially) modified proposal, called _prepared proposal_ in the -`Response*` call. The logic modifying the raw proposal can be non-deterministic. - -When Tendermint receives a prepared proposal it uses method `ProcessProposal` -to inform the Application of the proposal just received. The Application cannot -modify the proposal at this point but can reject it if it realises it is invalid. -If that is the case, Tendermint will prevote `nil` on the proposal, which has -strong liveness implications for Tendermint. As a general rule, the Application -SHOULD accept a prepared proposal passed via `ProcessProposal`, even if a part of -the proposal is invalid (e.g., an invalid transaction); the Application can later -ignore the invalid part of the prepared proposal at block execution time. - -Cryptographic commitments to the block and transaction results, via the corresponding -parameters in `FinalizeBlockResponse` are included in the header of the next block. - -## Next-block execution and same-block execution - -With ABCI++ predecessor, ABCI, the only moment when the Application had access to a -block was when it was decided. This led to a block execution model, called _next-block -execution_, where some fields hashed in a block header refer to the execution of the -previous block, namely: - -* the merkle root of the Application's state -* the transaction results -* the consensus parameter updates -* the validator updates - -With ABCI++, an Application may decide to keep using the next-block execution model; -however the new methods introduced, `PrepareProposal` and `ProcessProposal` allow -for a new execution model, called _same-block execution_. An Application implementing -this execution model, upon receiving a raw proposal via `RequestPrepareProposal` -and potentially modifying its transaction list, -fully executes the resulting prepared proposal as though it was the decided block. -The results of the block execution are used as follows: - -* the Application keeps the events generated and provides them if `FinalizeBlock` - is finally called on this prepared proposal. -* the merkle root resulting from executing the prepared proposal is provided in - `ResponsePrepareProposal` and thus refers to the **current block**. Tendermint - will use it in the prepared proposal's header. -* likewise, the transaction results from executing the prepared proposal are - provided in `ResponsePrepareProposal` and refer to the transactions in the - **current block**. Tendermint will use them to calculate the results hash - in the prepared proposal's header. -* the consensus parameter updates and validator updates are also provided in - `ResponsePrepareProposal` and reflect the result of the prepared proposal's - execution. They come into force in height H+1 (as opposed to the H+2 rule - in next-block execution model). - -If the Application decides to keep the next-block execution model, it will not -provide any data in `ResponsePrepareProposal`, other than an optionally modified -transaction list. - -In the long term, the execution model will be set in a new boolean parameter -*same_block* in `ConsensusParams`. -It should **not** be changed once the blockchain has started, unless the Application -developers _really_ know what they are doing. -However, modifying `ConsensusParams` structure cannot be done lightly if we are to -preserve blockchain compatibility. Therefore we need an interim solution until -soft upgrades are specified and implemented in Tendermint. This somewhat _unsafe_ -solution consists in Tendermint assuming same-block execution if the Application -fills the above mentioned fields in `ResponsePrepareProposal`. - -## Tendermint timeouts in same-block execution - -The new same-block execution mode requires the Application to fully execute the -prepared block at `PrepareProposal` time. This execution is synchronous, so -Tendermint cannot make progress until the Application returns from `PrepareProposal`. -This stands on Tendermint's critical path: if the Application takes a long time -executing the block, the default value of _TimeoutPropose_ might not be sufficient -to accomodate the long block execution time and non-proposer processes might time -out and prevote `nil`, thus starting a further round unnecessarily. - -The Application is the best suited to provide a value for _TimeoutPropose_ so -that the block execution time upon `PrepareProposal` fits well in the propose -timeout interval. - -Currently, the Application can override the value of _TimeoutPropose_ via the -`config.toml` file. In the future, `ConsensusParams` may have an extra field -with the current _TimeoutPropose_ value so that the Application has the possibility -to adapt it at every height. - -## State Sync - -State sync allows new nodes to rapidly bootstrap by discovering, fetching, and applying -state machine snapshots instead of replaying historical blocks. For more details, see the -[state sync section](../p2p/messages/state-sync.md). - -New nodes will discover and request snapshots from other nodes in the P2P network. -A Tendermint node that receives a request for snapshots from a peer will call -`ListSnapshots` on its Application to retrieve any local state snapshots. After receiving - snapshots from peers, the new node will offer each snapshot received from a peer -to its local Application via the `OfferSnapshot` method. - -Snapshots may be quite large and are thus broken into smaller "chunks" that can be -assembled into the whole snapshot. Once the Application accepts a snapshot and -begins restoring it, Tendermint will fetch snapshot "chunks" from existing nodes. -The node providing "chunks" will fetch them from its local Application using -the `LoadSnapshotChunk` method. - -As the new node receives "chunks" it will apply them sequentially to the local -application with `ApplySnapshotChunk`. When all chunks have been applied, the -Application's `AppHash` is retrieved via an `Info` query. The `AppHash` is then -compared to the blockchain's `AppHash` which is verified via -[light client verification](../light-client/verification/README.md). diff --git a/spec/abci++/abci++_methods_002_draft.md b/spec/abci++/abci++_methods_002_draft.md index 5f811a03f..d1782bbdc 100644 --- a/spec/abci++/abci++_methods_002_draft.md +++ b/spec/abci++/abci++_methods_002_draft.md @@ -287,44 +287,39 @@ title: Methods | Name | Type | Description | Field Number | |-------------------------|---------------------------------------------|------------------------------------------------------------------------------------------------------------------|--------------| - | hash | bytes | The block header's hash of the block to propose. Present for convenience (can be derived from the block header). | 1 | - | header | [Header](../core/data_structures.md#header) | The header of the block to propose. | 2 | - | txs | repeated bytes | Preliminary list of transactions that have been picked as part of the block to propose. | 3 | - | local_last_commit | [ExtendedCommitInfo](#extendedcommitinfo) | Info about the last commit, obtained locally from Tendermint's data structures. | 4 | - | byzantine_validators | repeated [Evidence](#evidence) | List of evidence of validators that acted maliciously. | 5 | - | max_tx_bytes | int64 | Currently configured maximum size in bytes taken by the modified transactions. | 6 | + | max_tx_bytes | int64 | Currently configured maximum size in bytes taken by the modified transactions. | 1 | + | txs | repeated bytes | Preliminary list of transactions that have been picked as part of the block to propose. | 2 | + | local_last_commit | [ExtendedCommitInfo](#extendedcommitinfo) | Info about the last commit, obtained locally from Tendermint's data structures. | 3 | + | byzantine_validators | repeated [Misbehavior](#misbehavior) | List of information about validators that acted incorrectly. | 4 | + | height | int64 | The height of the block that will be proposed. | 5 | + | time | [google.protobuf.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp) | Timestamp of the block that that will be proposed. | 6 | + | next_validators_hash | bytes | Merkle root of the next validator set. | 7 | + | proposer_address | bytes | [Address](../core/data_structures.md#address) of the validator that is creating the proposal. | 8 | * **Response**: | Name | Type | Description | Field Number | |-------------------------|--------------------------------------------------|---------------------------------------------------------------------------------------------|--------------| - | modified_tx | bool | The Application sets it to true to denote it made changes to transactions | 1 | | tx_records | repeated [TxRecord](#txrecord) | Possibly modified list of transactions that have been picked as part of the proposed block. | 2 | | app_hash | bytes | The Merkle root hash of the application state. | 3 | | tx_results | repeated [ExecTxResult](#txresult) | List of structures containing the data resulting from executing the transactions | 4 | | validator_updates | repeated [ValidatorUpdate](#validatorupdate) | Changes to validator set (set voting power to 0 to remove). | 5 | | consensus_param_updates | [ConsensusParams](#consensusparams) | Changes to consensus-critical gas, size, and other parameters. | 6 | - | app_signed_updates | repeated bytes | Optional changes to the *app_signed* part of vote extensions. | 7 | * **Usage**: - * The first five parameters of `RequestPrepareProposal` are the same as `RequestProcessProposal` + * The first six parameters of `RequestPrepareProposal` are the same as `RequestProcessProposal` and `RequestFinalizeBlock`. - * The header contains the height, timestamp, and more - it exactly matches the - Tendermint block header. - * `RequestPrepareProposal` contains a preliminary set of transactions `txs` that Tendermint considers to be a good block proposal, called _raw block_. The Application can modify this set via `ResponsePrepareProposal.tx_records` (see [TxRecord](#txrecord)). - * In this case, the Application should set `ResponsePrepareProposal.modified_tx` to true. - * The Application _can_ reorder, remove or add transactions to the raw block. Let `tx` be a transaction in `txs`: + * The height and time values match the values from the header of the proposed block. + * `RequestPrepareProposal` contains a preliminary set of transactions `txs` that Tendermint considers to be a good block proposal, called _raw proposal_. The Application can modify this set via `ResponsePrepareProposal.tx_records` (see [TxRecord](#txrecord)). + * The Application _can_ reorder, remove or add transactions to the raw proposal. Let `tx` be a transaction in `txs`: * If the Application considers that `tx` should not be proposed in this block, e.g., there are other transactions with higher priority, then it should not include it in `tx_records`. In this case, Tendermint won't remove `tx` from the mempool. The Application should be extra-careful, as abusing this feature may cause transactions to stay forever in the mempool. - * If the Application considers that a `tx` should not be included in the proposal and removed from the mempool, then the Application should include it in `tx_records` and _mark_ it as "REMOVE". In this case, Tendermint will remove `tx` from the mempool. - * If the Application wants to add a new transaction, then the Application should include it in `tx_records` and _mark_ it as "ADD". In this case, Tendermint will add it to the mempool. + * If the Application considers that a `tx` should not be included in the proposal and removed from the mempool, then the Application should include it in `tx_records` and _mark_ it as `REMOVED`. In this case, Tendermint will remove `tx` from the mempool. + * If the Application wants to add a new transaction, then the Application should include it in `tx_records` and _mark_ it as `ADD`. In this case, Tendermint will add it to the mempool. * The Application should be aware that removing and adding transactions may compromise _traceability_. > Consider the following example: the Application transforms a client-submitted transaction `t1` into a second transaction `t2`, i.e., the Application asks Tendermint to remove `t1` and add `t2` to the mempool. If a client wants to eventually check what happened to `t1`, it will discover that `t_1` is not in the mempool or in a committed block, getting the wrong idea that `t_1` did not make it into a block. Note that `t_2` _will be_ in a committed block, but unless the Application tracks this information, no component will be aware of it. Thus, if the Application wants traceability, it is its responsability to support it. For instance, the Application could attach to a transformed transaction a list with the hashes of the transactions it derives from. - * If the Application modifies the set of transactions, the modified transactions MUST NOT exceed the configured maximum size `RequestPrepareProposal.max_tx_bytes`. - * If the Application does not modify the preliminary set of transactions `txs`, then it sets `ResponsePrepareProposal.modified_tx` to false. In this case, Tendermint will ignore the contents of `ResponsePrepareProposal.tx_records`. - * If the Application modifies the *app_signed* part of vote extensions via `ResponsePrepareProposal.app_signed_updates`, - the new total size of those extensions cannot exceed their initial size. - * The Application may choose to not modify the *app_signed* part of vote extensions by leaving parameter - `ResponsePrepareProposal.app_signed_updates` empty. + * Tendermint MAY include a list of transactions in `RequestPrepareProposal.txs` whose total size in bytes exceeds `RequestPrepareProposal.max_tx_bytes`. + Therefore, if the size of `RequestPrepareProposal.txs` is greater than `RequestPrepareProposal.max_tx_bytes`, the Application MUST make sure that the + `RequestPrepareProposal.max_tx_bytes` limit is respected by those transaction records returned in `ResponsePrepareProposal.tx_records` that are marked as `UNMODIFIED` or `ADDED`. * In same-block execution mode, the Application must provide values for `ResponsePrepareProposal.app_hash`, `ResponsePrepareProposal.tx_results`, `ResponsePrepareProposal.validator_updates`, and `ResponsePrepareProposal.consensus_param_updates`, as a result of fully executing the block. @@ -353,12 +348,10 @@ title: Methods * As a sanity check, Tendermint will check the returned parameters for validity if the Application modified them. In particular, `ResponsePrepareProposal.tx_records` will be deemed invalid if * There is a duplicate transaction in the list. - * A new or modified transaction is marked as "TXUNMODIFIED" or "TXREMOVED". - * An unmodified transaction is marked as "TXADDED". - * A transaction is marked as "TXUNKNOWN". - * If Tendermint's sanity checks on the parameters of `ResponsePrepareProposal` fails, then it will drop the proposal - and proceed to the next round (thus simulating a network loss/delay of the proposal). - * **TODO**: [From discussion with William] Another possibility here is to panic. What do folks think we should do here? + * A new or modified transaction is marked as `UNMODIFIED` or `REMOVED`. + * An unmodified transaction is marked as `ADDED`. + * A transaction is marked as `UNKNOWN`. + * If Tendermint fails to validate the `ResponsePrepareProposal`, Tendermint will assume the application is faulty and crash. * The implementation of `PrepareProposal` can be non-deterministic. #### When does Tendermint call it? @@ -377,7 +370,7 @@ and _p_'s _validValue_ is `nil`: * _p_'s Tendermint creates a block header. 2. _p_'s Tendermint calls `RequestPrepareProposal` with the newly generated block. The call is synchronous: Tendermint's execution will block until the Application returns from the call. -3. The Application checks the block (header, transactions, commit info, evidences). Besides, +3. The Application checks the block (hashes, transactions, commit info, misbehavior). Besides, * in same-block execution mode, the Application can (and should) provide `ResponsePrepareProposal.app_hash`, `ResponsePrepareProposal.validator_updates`, or `ResponsePrepareProposal.consensus_param_updates`. @@ -385,7 +378,7 @@ and _p_'s _validValue_ is `nil`: `ResponsePrepareProposal.validator_updates`, and `ResponsePrepareProposal.consensus_param_updates`. * in both modes, the Application can manipulate transactions * leave transactions untouched - `TxAction = UNMODIFIED` - * add new transactions (not previously in the mempool) - `TxAction = ADDED` + * add new transactions directly to the proposal - `TxAction = ADDED` * remove transactions (invalid) from the proposal and from the mempool - `TxAction = REMOVED` * remove transactions from the proposal but not from the mempool (effectively _delaying_ them) - the Application removes the transaction from the list @@ -406,56 +399,48 @@ Note that, if _p_ has a non-`nil` _validValue_, Tendermint will use it as propos | Name | Type | Description | Field Number | |----------------------|---------------------------------------------|----------------------------------------------------------------------------------------------------------------|--------------| - | hash | bytes | The block header's hash of the proposed block. Present for convenience (can be derived from the block header). | 1 | - | header | [Header](../core/data_structures.md#header) | The proposed block's header. | 2 | - | txs | repeated bytes | List of transactions that have been picked as part of the proposed block. | 3 | - | proposed_last_commit | [CommitInfo](#commitinfo) | Info about the last commit, obtained from the information in the proposed block. | 4 | - | byzantine_validators | repeated [Evidence](#evidence) | List of evidence of validators that acted maliciously. | 5 | + | txs | repeated bytes | List of transactions that have been picked as part of the proposed block. | 1 | + | proposed_last_commit | [CommitInfo](#commitinfo) | Info about the last commit, obtained from the information in the proposed block. | 2 | + | byzantine_validators | repeated [Misbehavior](#misbehavior) | List of information about validators that acted incorrectly. | 3 | + | hash | bytes | The block header's hash of the proposed block. | 4 | + | height | int64 | The height of the proposed block. | 5 | + | time | [google.protobuf.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp) | Timestamp included in the proposed block. | 6 | + | next_validators_hash | bytes | Merkle root of the next validator set. | 7 | + | proposer_address | bytes | [Address](../core/data_structures.md#address) of the validator that created the proposal. | 8 | * **Response**: | Name | Type | Description | Field Number | |-------------------------|--------------------------------------------------|-----------------------------------------------------------------------------------|--------------| - | accept | bool | If false, the received block failed verification. | 1 | + | status | [ProposalStatus](#proposalstatus) | `enum` that signals if the application finds the proposal valid. | 1 | | app_hash | bytes | The Merkle root hash of the application state. | 2 | | tx_results | repeated [ExecTxResult](#txresult) | List of structures containing the data resulting from executing the transactions. | 3 | | validator_updates | repeated [ValidatorUpdate](#validatorupdate) | Changes to validator set (set voting power to 0 to remove). | 4 | | consensus_param_updates | [ConsensusParams](#consensusparams) | Changes to consensus-critical gas, size, and other parameters. | 5 | * **Usage**: - * Contains a full proposed block. - * The parameters and types of `RequestProcessProposal` are the same as `RequestPrepareProposal` - and `RequestFinalizeBlock`. + * Contains fields from the proposed block. * The Application may fully execute the block as though it was handling `RequestFinalizeBlock`. - However, any resulting state changes must be kept as _canditade state_, + However, any resulting state changes must be kept as _candidate state_, and the Application should be ready to backtrack/discard it in case the decided block is different. - * The header exactly matches the Tendermint header of the proposed block. - * In next-block execution mode, the header hashes _AppHash_, _LastResultHash_, _ValidatorHash_, - and _ConsensusHash_ refer to the **last committed block** (data was provided by the last call to - `ResponseFinalizeBlock`). - * In same-block execution mode, the header hashes _AppHash_, _LastResultHash_, _ValidatorHash_, - and _ConsensusHash_ refer to the **same** block being passed in the `Request*` call to this - method (data was provided by the call to `ResponsePrepareProposal` at the current height that - resulted in the block being passed in the `Request*` call to this method) - * If `ResponseProcessProposal.accept` is _false_, Tendermint assumes the proposal received + * The height and timestamp values match the values from the header of the proposed block. + * If `ResponseProcessProposal.status` is `REJECT`, Tendermint assumes the proposal received is not valid. * In same-block execution mode, the Application is required to fully execute the block and provide values for parameters `ResponseProcessProposal.app_hash`, `ResponseProcessProposal.tx_results`, `ResponseProcessProposal.validator_updates`, and `ResponseProcessProposal.consensus_param_updates`, so that Tendermint can then verify the hashes in the block's header are correct. - If the hashes mismatch, Tendermint will reject the block even if `ResponseProcessProposal.accept` - was set to _true_. + If the hashes mismatch, Tendermint will reject the block even if `ResponseProcessProposal.status` + was set to `ACCEPT`. * In next-block execution mode, the Application should *not* provide values for parameters `ResponseProcessProposal.app_hash`, `ResponseProcessProposal.tx_results`, `ResponseProcessProposal.validator_updates`, and `ResponseProcessProposal.consensus_param_updates`. * The implementation of `ProcessProposal` MUST be deterministic. Moreover, the value of - `ResponseProcessProposal.accept` MUST **exclusively** depend on the parameters passed in + `ResponseProcessProposal.status` MUST **exclusively** depend on the parameters passed in the call to `RequestProcessProposal`, and the last committed Application state (see [Requirements](abci++_app_requirements_002_draft.md) section). - * Moreover, application implementors SHOULD always set `ResponseProcessProposal.accept` to _true_, - unless they _really_ know what the potential liveness implications of returning _false_ are. - ->**TODO**: should `ResponseProcessProposal.accept` be of type `Result` rather than `bool`? (so we are able to extend the possible values in the future?) + * Moreover, application implementors SHOULD always set `ResponseProcessProposal.status` to `ACCEPT`, + unless they _really_ know what the potential liveness implications of returning `REJECT` are. #### When does Tendermint call it? @@ -538,33 +523,38 @@ a [CanonicalVoteExtension](#canonicalvoteextension) field in the `precommit nil` | hash | bytes | The header hash of the propsed block that the vote extension refers to. | 1 | | validator_address | bytes | [Address](../core/data_structures.md#address) of the validator that signed the extension | 2 | | height | int64 | Height of the block (for sanity check). | 3 | - | vote_extension | bytes | Optional information signed by Tendermint. | 4 | + | vote_extension | bytes | Application-specific information signed by Tendermint. Can have 0 length | 4 | * **Response**: - | Name | Type | Description | Field Number | - |--------|------|-------------------------------------------------------|--------------| - | accept | bool | If false, Application is rejecting the vote extension | 1 | + | Name | Type | Description | Field Number | + |--------|-------------------------------|----------------------------------------------------------------|--------------| + | status | [VerifyStatus](#verifystatus) | `enum` signaling if the application accepts the vote extension | 1 | * **Usage**: - * If `ResponseVerifyVoteExtension.accept` is _false_, Tendermint will reject the whole received vote. + * `RequestVerifyVoteExtension.vote_extension` can be an empty byte array. The Application's interpretation of it should be + that the Application running at the process that sent the vote chose not to extend it. + Tendermint will always call `RequestVerifyVoteExtension`, even for 0 length vote extensions. + * If `ResponseVerifyVoteExtension.status` is `REJECT`, Tendermint will reject the whole received vote. See the [Requirements](abci++_app_requirements_002_draft.md) section to understand the potential liveness implications of this. * The implementation of `VerifyVoteExtension` MUST be deterministic. Moreover, the value of - `ResponseVerifyVoteExtension.accept` MUST **exclusively** depend on the parameters passed in + `ResponseVerifyVoteExtension.status` MUST **exclusively** depend on the parameters passed in the call to `RequestVerifyVoteExtension`, and the last committed Application state (see [Requirements](abci++_app_requirements_002_draft.md) section). - * Moreover, application implementors SHOULD always set `ResponseVerifyVoteExtension.accept` to _true_, - unless they _really_ know what the potential liveness implications of returning _false_ are. + * Moreover, application implementers SHOULD always set `ResponseVerifyVoteExtension.status` to `ACCEPT`, + unless they _really_ know what the potential liveness implications of returning `REJECT` are. #### When does Tendermint call it? When a validator _p_ is in Tendermint consensus round _r_, height _h_, state _prevote_ (**TODO** discuss: I think I must remove the state from this condition, but not sure), and _p_ receives a Precommit message for round _r_, height _h_ from _q_: -1. _p_'s Tendermint calls `RequestVerifyVoteExtension`. -2. The Application returns _accept_ or _reject_ via `ResponseVerifyVoteExtension.accept`. -3. If the Application returns +1. If the Precommit message does not contain a vote extension with a valid signature, Tendermint discards the message as invalid. + * a 0-length vote extension is valid as long as its accompanying signature is also valid. +2. Else, _p_'s Tendermint calls `RequestVerifyVoteExtension`. +3. The Application returns _accept_ or _reject_ via `ResponseVerifyVoteExtension.status`. +4. If the Application returns * _accept_, _p_'s Tendermint will keep the received vote, together with its corresponding vote extension in its internal data structures. It will be used to populate the [ExtendedCommitInfo](#extendedcommitinfo) structure in calls to `RequestPrepareProposal`, in rounds of height _h + 1_ where _p_ is the proposer. @@ -578,17 +568,20 @@ from this condition, but not sure), and _p_ receives a Precommit message for rou | Name | Type | Description | Field Number | |----------------------|---------------------------------------------|------------------------------------------------------------------------------------------|--------------| - | hash | bytes | The block header's hash. Present for convenience (can be derived from the block header). | 1 | - | header | [Header](../core/data_structures.md#header) | The block header. | 2 | - | txs | repeated bytes | List of transactions committed as part of the block. | 3 | - | decided_last_commit | [CommitInfo](#commitinfo) | Info about the last commit, obtained from the block that was just decided. | 4 | - | byzantine_validators | repeated [Evidence](#evidence) | List of evidence of validators that acted maliciously. | 5 | + | txs | repeated bytes | List of transactions committed as part of the block. | 1 | + | decided_last_commit | [CommitInfo](#commitinfo) | Info about the last commit, obtained from the block that was just decided. | 2 | + | byzantine_validators | repeated [Misbehavior](#misbehavior) | List of information about validators that acted incorrectly. | 3 | + | hash | bytes | The block header's hash. Present for convenience (can be derived from the block header). | 4 | + | height | int64 | The height of the finalized block. | 5 | + | time | [google.protobuf.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp) | Timestamp included in the finalized block. | 6 | + | next_validators_hash | bytes | Merkle root of the next validator set. | 7 | + | proposer_address | bytes | [Address](../core/data_structures.md#address) of the validator that created the proposal.| 8 | * **Response**: | Name | Type | Description | Field Number | |-------------------------|-------------------------------------------------------------|----------------------------------------------------------------------------------|--------------| - | events | repeated [Event](abci++_basic_concepts_002_draft.md#events) | Type & Key-Value events for indexing | 1 | + | events | repeated [Event](abci++_basic_concepts_002_draft.md#events) | Type & Key-Value events for indexing | 1 | | tx_results | repeated [ExecTxResult](#txresult) | List of structures containing the data resulting from executing the transactions | 2 | | validator_updates | repeated [ValidatorUpdate](#validatorupdate) | Changes to validator set (set voting power to 0 to remove). | 3 | | consensus_param_updates | [ConsensusParams](#consensusparams) | Changes to consensus-critical gas, size, and other parameters. | 4 | @@ -596,10 +589,10 @@ from this condition, but not sure), and _p_ receives a Precommit message for rou | retain_height | int64 | Blocks below this height may be removed. Defaults to `0` (retain all). | 6 | * **Usage**: - * Contains a newly decided block. + * Contains the fields of the newly decided block. * This method is equivalent to the call sequence `BeginBlock`, [`DeliverTx`], `EndBlock`, `Commit` in the previous version of ABCI. - * The header exactly matches the Tendermint header of the proposed block. + * The height and timestamp values match the values from the header of the proposed block. * The Application can use `RequestFinalizeBlock.decided_last_commit` and `RequestFinalizeBlock.byzantine_validators` to determine rewards and punishments for the validators. * The application must execute the transactions in full, in the order they appear in `RequestFinalizeBlock.txs`, @@ -701,23 +694,23 @@ Most of the data structures used in ABCI are shared [common data structures](../ * Validator identified by PubKey * Used to tell Tendermint to update the validator set -### Evidence +### Misbehavior * **Fields**: | Name | Type | Description | Field Number | |--------------------|--------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------|--------------| - | type | [EvidenceType](#evidencetype) | Type of the evidence. An enum of possible evidence's. | 1 | + | type | [MisbehaviorType](#misbehaviortype) | Type of the misbehavior. An enum of possible misbehaviors. | 1 | | validator | [Validator](#validator) | The offending validator | 2 | | height | int64 | Height when the offense occurred | 3 | | time | [google.protobuf.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp) | Time of the block that was committed at the height that the offense occurred | 4 | | total_voting_power | int64 | Total voting power of the validator set at height `Height` | 5 | -#### EvidenceType +#### MisbehaviorType * **Fields** - EvidenceType is an enum with the listed fields: + MisbehaviorType is an enum with the listed fields: | Name | Field Number | |---------------------|--------------| @@ -832,26 +825,27 @@ Most of the data structures used in ABCI are shared [common data structures](../ | info | string | Additional information. **May be non-deterministic.** | 4 | | gas_wanted | int64 | Amount of gas requested for transaction. | 5 | | gas_used | int64 | Amount of gas consumed by transaction. | 6 | - | events | repeated [Event](abci++_basic_concepts_002_draft.md#events) | Type & Key-Value events for indexing transactions (e.g. by account). | 7 | + | events | repeated [Event](abci++_basic_concepts_002_draft.md#events) | Type & Key-Value events for indexing transactions (e.g. by account). | 7 | | codespace | string | Namespace for the `code`. | 8 | ### TxAction -```protobuf - enum TxAction { - TXUNKNOWN = 0; // Unknown action - TXUNMODIFIED = 1; // The Application did not modify this transaction. - TXADDED = 2; // The Application added this transaction. - TXREMOVED = 3; // The Application wants this transaction removed from the proposal and the mempool. - } +```proto +enum TxAction { + UNKNOWN = 0; // Unknown action + UNMODIFIED = 1; // The Application did not modify this transaction. + ADDED = 2; // The Application added this transaction. + REMOVED = 3; // The Application wants this transaction removed from the proposal and the mempool. +} ``` * **Usage**: - * If `Action` is TXUNKNOWN, a problem happened in the Application. Tendermint will ignore this transaction. **TODO** should we panic? - * If `Action` is TXUNMODIFIED, Tendermint includes the transaction in the proposal. Nothing to do on the mempool. - * If `Action` is TXADDED, Tendermint includes the transaction in the proposal. The transaction is also added to the mempool and gossipped. - * If `Action` is TXREMOVED, Tendermint excludes the transaction from the proposal. The transaction is also removed from the mempool if it exists, + * If `Action` is `UNKNOWN`, a problem happened in the Application. Tendermint will assume the application is faulty and crash. + * If `Action` is `UNMODIFIED`, Tendermint includes the transaction in the proposal. Nothing to do on the mempool. + * If `Action` is `ADDED`, Tendermint includes the transaction in the proposal. The transaction is _not_ added to the mempool. + * If `Action` is `REMOVED`, Tendermint excludes the transaction from the proposal. The transaction is also removed from the mempool if it exists, similar to `CheckTx` returning _false_. + ### TxRecord * **Fields**: @@ -861,6 +855,39 @@ Most of the data structures used in ABCI are shared [common data structures](../ | action | [TxAction](#txaction) | What should Tendermint do with this transaction? | 1 | | tx | bytes | Transaction contents | 2 | +### ProposalStatus + +```proto +enum ProposalStatus { + UNKNOWN = 0; // Unknown status. Returning this from the application is always an error. + ACCEPT = 1; // Status that signals that the application finds the proposal valid. + REJECT = 2; // Status that signals that the application finds the proposal invalid. +} +``` + +* **Usage**: + * Used within the [ProcessProposal](#processproposal) response. + * If `Status` is `UNKNOWN`, a problem happened in the Application. Tendermint will assume the application is faulty and crash. + * If `Status` is `ACCEPT`, Tendermint accepts the proposal and will issue a Prevote message for it. + * If `Status` is `REJECT`, Tendermint rejects the proposal and will issue a Prevote for `nil` instead. + +### VerifyStatus + +```proto +enum VerifyStatus { + UNKNOWN = 0; // Unknown status. Returning this from the application is always an error. + ACCEPT = 1; // Status that signals that the application finds the vote extension valid. + REJECT = 2; // Status that signals that the application finds the vote extension invalid. +} +``` + +* **Usage**: + * Used within the [VerifyVoteExtension](#verifyvoteextension) response. + * If `Status` is `UNKNOWN`, a problem happened in the Application. Tendermint will assume the application is faulty and crash. + * If `Status` is `ACCEPT`, Tendermint will accept the vote as valid. + * If `Status` is `REJECT`, Tendermint will reject the vote as invalid. + + ### CanonicalVoteExtension >**TODO**: This protobuf message definition is not part of the ABCI++ interface, but rather belongs to the diff --git a/spec/abci++/abci++_tmint_expected_behavior_002_draft.md b/spec/abci++/abci++_tmint_expected_behavior_002_draft.md index 18669a479..778689450 100644 --- a/spec/abci++/abci++_tmint_expected_behavior_002_draft.md +++ b/spec/abci++/abci++_tmint_expected_behavior_002_draft.md @@ -202,11 +202,17 @@ to undergo any changes in their implementation. As for the new methods: -* `PrepareProposal` should set `ResponsePrepareProposal.modified_tx` to _false_ and return. -* `ProcessProposal` should set `ResponseProcessProposal.accept` to _true_ and return. -* `ExtendVote` should set `ResponseExtendVote.extension` to an empty byte array and return. -* `VerifyVoteExtension` should set `ResponseVerifyVoteExtension.accept` to _true_ if the extension is an empty byte array +* `PrepareProposal` must create a list of [TxRecord](./abci++_methods_002_draft.md#txrecord) each containing a + transaction passed in `RequestPrepareProposal.txs`, in the same other. The field `action` must be set to `UNMODIFIED` + for all [TxRecord](./abci++_methods_002_draft.md#txrecord) elements in the list. + The Application must check whether the size of all transactions exceeds the byte limit + (`RequestPrepareProposal.max_tx_bytes`). If so, the Application must remove transactions at the end of the list + until the total byte size is at or below the limit. +* `ProcessProposal` must set `ResponseProcessProposal.accept` to _true_ and return. +* `ExtendVote` is to set `ResponseExtendVote.extension` to an empty byte array and return. +* `VerifyVoteExtension` must set `ResponseVerifyVoteExtension.accept` to _true_ if the extension is an empty byte array and _false_ otherwise, then return. -* `FinalizeBlock` should coalesce the implementation of methods `BeginBlock`, `DeliverTx`, `EndBlock`, and `Commit`. - The logic extracted from `DeliverTx` should be wrappped by a loop that will execute as many times as - transactions exist in `RequestFinalizeBlock.tx`. +* `FinalizeBlock` is to coalesce the implementation of methods `BeginBlock`, `DeliverTx`, `EndBlock`, and `Commit`. + Legacy applications looking to reuse old code that implemented `DeliverTx` should wrap the legacy + `DeliverTx` logic in a loop that executes one transaction iteration per + transaction in `RequestFinalizeBlock.tx`. diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 2a027d1d0..5d9d59b71 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -13,35 +13,11 @@ replication engine. When run within the same process, Tendermint will call the A application methods directly as Go method calls. When Tendermint and the ABCI application are run as separate processes, Tendermint -opens four connections to the application for ABCI methods. The connections each -handle a subset of the ABCI method calls. These subsets are defined as follows: +opens maintains a connection over either a native socket protocol or +gRPC. -#### **Consensus** connection - -* Driven by a consensus protocol and is responsible for block execution. -* Handles the `InitChain`, `BeginBlock`, `DeliverTx`, `EndBlock`, and `Commit` method -calls. - -#### **Mempool** connection - -* For validating new transactions, before they're shared or included in a block. -* Handles the `CheckTx` calls. - -#### **Info** connection - -* For initialization and for queries from the user. -* Handles the `Info` and `Query` calls. - -#### **Snapshot** connection - -* For serving and restoring [state sync snapshots](apps.md#state-sync). -* Handles the `ListSnapshots`, `LoadSnapshotChunk`, `OfferSnapshot`, and `ApplySnapshotChunk` calls. - -Additionally, there is a `Flush` method that is called on every connection, -and an `Echo` method that is just for debugging. - -More details on managing state across connections can be found in the section on -[ABCI Applications](apps.md). +More details on managing state across connections can be found in the +section on [ABCI Applications](apps.md). ## Errors @@ -54,13 +30,17 @@ These methods also return a `Codespace` string to Tendermint. This field is used to disambiguate `Code` values returned by different domains of the application. The `Codespace` is a namespace for the `Code`. -The `Echo`, `Info`, `InitChain`, `BeginBlock`, `EndBlock`, `Commit` methods -do not return errors. An error in any of these methods represents a critical -issue that Tendermint has no reasonable way to handle. If there is an error in one -of these methods, the application must crash to ensure that the error is safely -handled by an operator. +The handling of non-zero response codes by Tendermint is described +below. -The handling of non-zero response codes by Tendermint is described below +Applications should always terminate if they encounter an issue in a +method where continuing would corrupt their own state, or for which +tendermint should not continue. + +In the Go implementation these methods take a context and may return +an error. The context exists so that applications can terminate +gracefully during shutdown, and the error return value makes it +possible for applications to singal transient errors to Tendermint. ### CheckTx diff --git a/spec/consensus/evidence.md b/spec/consensus/evidence.md index 84d51a01b..edf9e53ff 100644 --- a/spec/consensus/evidence.md +++ b/spec/consensus/evidence.md @@ -1,7 +1,7 @@ # Evidence Evidence is an important component of Tendermint's security model. Whilst the core -consensus protocol provides correctness gaurantees for state machine replication +consensus protocol provides correctness guarantees for state machine replication that can tolerate less than 1/3 failures, the evidence system looks to detect and gossip byzantine faults whose combined power is greater than or equal to 1/3. It is worth noting that the evidence system is designed purely to detect possible attacks, gossip them, diff --git a/spec/consensus/proposer-based-timestamp/pbts-sysmodel_002_draft.md b/spec/consensus/proposer-based-timestamp/pbts-sysmodel_002_draft.md index 832d11c9a..d6fcb54b6 100644 --- a/spec/consensus/proposer-based-timestamp/pbts-sysmodel_002_draft.md +++ b/spec/consensus/proposer-based-timestamp/pbts-sysmodel_002_draft.md @@ -1,16 +1,31 @@ # PBTS: System Model and Properties +## Outline + + - [System model](#system-model) + - [Synchronized clocks](#synchronized-clocks) + - [Message delays](#message-delays) + - [Problem Statement](#problem-statement) + - [Protocol Analysis - Timely Proposals](#protocol-analysis---timely-proposals) + - [Timely Proof-of-Locks](#timely-proof-of-locks) + - [Derived Proof-of-Locks](#derived-proof-of-locks) + - [Temporal Analysis](#temporal-analysis) + - [Safety](#safety) + - [Liveness](#liveness) + ## System Model #### **[PBTS-CLOCK-NEWTON.0]** -There is a reference Newtonian real-time `t` (UTC). +There is a reference Newtonian real-time `t`. No process has direct access to this reference time, used only for specification purposes. +The reference real-time is assumed to be aligned with the Coordinated Universal Time (UTC). ### Synchronized clocks -Processes are assumed to be equipped with synchronized clocks. +Processes are assumed to be equipped with synchronized clocks, +aligned with the Coordinated Universal Time (UTC). This requires processes to periodically synchronize their local clocks with an external and trusted source of the time (e.g. NTP servers). @@ -27,43 +42,35 @@ and drifts of local clocks from real time. #### **[PBTS-CLOCK-PRECISION.0]** There exists a system parameter `PRECISION`, such that -for any two processes `p` and `q`, with local clocks `C_p` and `C_q`, -that read their local clocks at the same real-time `t`, we have: +for any two processes `p` and `q`, with local clocks `C_p` and `C_q`: -- If `p` and `q` are equipped with synchronized clocks, then `|C_p(t) - C_q(t)| < PRECISION` +- If `p` and `q` are equipped with synchronized clocks, + then for any real-time `t` we have `|C_p(t) - C_q(t)| <= PRECISION`. `PRECISION` thus bounds the difference on the times simultaneously read by processes from their local clocks, so that their clocks can be considered synchronized. #### Accuracy -The [first draft][sysmodel_v1] of this specification included a second clock-related parameter, `ACCURACY`, -that relates the values read by processes from their synchronized clocks with real time: +A second relevant clock parameter is accuracy, which binds the values read by +processes from their clocks to real time. -- If `p` is a process is equipped with a synchronized clock, then at real time - `t` it reads from its clock time `C_p(t)` with `|C_p(t) - t| < ACCURACY` +##### **[PBTS-CLOCK-ACCURACY.0]** -The adoption of `ACCURACY` as the upper bound on the difference between clock -readings and real time, however, renders the `PRECISION` parameter redundant. -In fact, if we assume that clocks readings are at most `ACCURACY` from real -time, we would therefore be assuming that they cannot be more than `2 * ACCURACY` -apart from each other, thus establishing a worst-case upper bound for `PRECISION`. - -The approach we take is to assume that processes clocks are periodically -synchronized with an external source of time, thus improving their accuracy. -This allows us to adopt a relaxed version of the above `ACCURACY` definition: - -##### **[PBTS-CLOCK-FAIR.0]** +For the sake of completeness, we define a parameter `ACCURACY` such that: - At real time `t` there is at least one correct process `p` which clock marks - `C_p(t)` with `|C_p(t) - t| < ACCURACY` + `C_p(t)` with `|C_p(t) - t| <= ACCURACY`. -Then, through [PBTS-CLOCK-PRECISION] we can extend this relation of clock times -with real time to every correct process, which will have a clock with accuracy -bound by `ACCURACY + PRECISION`. -But, for the sake of simpler specification we can assume that the `PRECISION`, -which is a worst-case parameter that applies to all correct processes, -includes the best `ACCURACY` achieved by any of them. +As a consequence, applying the definition of `PRECISION`, we have: + +- At real time `t` the synchronized clock of any correct process `p` marks + `C_p(t)` with `|C_p(t) - t| <= ACCURACY + PRECISION`. + +The reason for not adopting `ACCURACY` as a system parameter is the assumption +that `PRECISION >> ACCURACY`. +This allows us to consider, for practical purposes, that the `PRECISION` system +parameter embodies the `ACCURACY` model parameter. ### Message Delays @@ -79,172 +86,264 @@ defining a lower bound, a *minimum time* that a correct process assigns to propo While *minimum delay* for delivering a proposal to a destination allows defining an upper bound, the *maximum time* assigned to a proposal. -#### **[PBTS-MSG-D.0]** +#### **[PBTS-MSG-DELAY.0]** -There exists a system parameter `MSGDELAY` for end-to-end delays of messages carrying proposals, -such for any two correct processes `p` and `q`, and any real time `t`: +There exists a system parameter `MSGDELAY` for end-to-end delays of proposal messages, +such for any two correct processes `p` and `q`: -- If `p` sends a message `m` carrying a proposal at time `ts`, -then if `q` receives the message and learns the proposal, -`q` does that at time `t` such that `ts <= t <= ts + MSGDELAY`. +- If `p` sends a proposal message `m` at real time `t` and `q` receives `m` at + real time `t'`, then `t <= t' <= t + MSGDELAY`. -While we don't want to impose particular restrictions regarding the format of `m`, -we need to assume that their size is upper bounded. -In practice, using messages with a fixed-size to carry proposals allows -for a more accurate estimation of `MSGDELAY`, and therefore is advised. +Notice that, as a system parameter, `MSGDELAY` should be observed for any +proposal message broadcast by correct processes: it is a *worst-case* parameter. +As message delays depends on the message size, the above requirement implicitly +indicates that the size of proposal messages is either fixed or upper bounded. ## Problem Statement In this section we define the properties of Tendermint consensus -(cf. the [arXiv paper][arXiv]) in this new system model. +(cf. the [arXiv paper][arXiv]) in this system model. -#### **[PBTS-PROPOSE.0]** +### **[PBTS-PROPOSE.0]** -A proposer proposes a consensus value `v` with an associated proposal time `v.time`. +A proposer proposes a consensus value `v` that includes a proposal time +`v.time`. + +> We then restrict the allowed decisions along the following lines: #### **[PBTS-INV-AGREEMENT.0]** -[Agreement] No two correct processes decide on different values `v`. (This implies that no two correct processes decide on different proposal times `v.time`.) +- [Agreement] No two correct processes decide on different values `v`. + +This implies that no two correct processes decide on different proposal times +`v.time`. #### **[PBTS-INV-VALID.0]** -[Validity] If a correct process decides on value `v`, -then `v` satisfies a predefined `valid` predicate. +- [Validity] If a correct process decides on value `v`, then `v` satisfies a + predefined `valid` predicate. + +With respect to PBTS, the `valid` predicate requires proposal times to be +[monotonic](./pbts-algorithm_002_draft.md#time-monotonicity) over heights of +consensus: + +##### **[PBTS-INV-MONOTONICITY.0]** + +- If a correct process decides on value `v` at the height `h` of consensus, + thus setting `decision[h] = v`, then `v.time > decision[h'].time` for all + previous heights `h' < h`. + +The monotonicity of proposal times, and external validity in general, +implicitly assumes that heights of consensus are executed in order. #### **[PBTS-INV-TIMELY.0]** -[Time-Validity] If a correct process decides on value `v`, -then the associated proposal time `v.time` satisfies a predefined `timely` predicate. +- [Time-Validity] If a correct process decides on value `v`, then the proposal + time `v.time` was considered `timely` by at least one correct process. -> Both [Validity] and [Time-Validity] must be observed even if up to `2f` validators are faulty. +PBTS introduces a `timely` predicate that restricts the allowed decisions based +on the proposal time `v.time` associated with a proposed value `v`. +As a synchronous predicate, the time at which it is evaluated impacts on +whether a process accepts or reject a proposal time. +For this reason, the Time-Validity property refers to the previous evaluation +of the `timely` predicate, detailed in the following section. -### Timely proposals +## Protocol Analysis - Timely proposals + +For PBTS, a `proposal` is a tuple `(v, v.time, v.round)`, where: + +- `v` is the proposed value; +- `v.time` is the associated proposal time; +- `v.round` is the round at which `v` was first proposed. + +We include the proposal round `v.round` in the proposal definition because a +value `v` and its associated proposal time `v.time` can be proposed in multiple +rounds, but the evaluation of the `timely` predicate is only relevant at round +`v.round`. + +> Considering the algorithm in the [arXiv paper][arXiv], a new proposal is +> produced by the `getValue()` method, invoked by the proposer `p` of round +> `round_p` when starting its proposing round with a nil `validValue_p`. +> The first round at which a value `v` is proposed is then the round at which +> the proposal for `v` was produced, and broadcast in a `PROPOSAL` message with +> `vr = -1`. + +#### **[PBTS-PROPOSAL-RECEPTION.0]** The `timely` predicate is evaluated when a process receives a proposal. -Let `now_p` be time a process `p` reads from its local clock when `p` receives a proposal. -Let `v` be the proposed value and `v.time` the proposal time. -The proposal is considered `timely` by `p` if: +More precisely, let `p` be a correct process: -#### **[PBTS-RECEPTION-STEP.1]** +- `proposalReceptionTime(p,r)` is the time `p` reads from its local clock when + `p` is at round `r` and receives the proposal of round `r`. -1. `now_p >= v.time - PRECISION` and -1. `now_p <= v.time + MSGDELAY + PRECISION` +#### **[PBTS-TIMELY.0]** + +The proposal `(v, v.time, v.round)` is considered `timely` by a correct process +`p` if: + +1. `proposalReceptionTime(p,v.round)` is set, and +1. `proposalReceptionTime(p,v.round) >= v.time - PRECISION`, and +1. `proposalReceptionTime(p,v.round) <= v.time + MSGDELAY + PRECISION`. + +A correct process at round `v.round` only sends a `PREVOTE` for `v` if the +associated proposal time `v.time` is considered `timely`. + +> Considering the algorithm in the [arXiv paper][arXiv], the `timely` predicate +> is evaluated by a process `p` when it receives a valid `PROPOSAL` message +> from the proposer of the current round `round_p` with `vr = -1`. ### Timely Proof-of-Locks -We denote by `POL(v,r)` a *Proof-of-Lock* of value `v` at the round `r` of consensus. -`POL(v,r)` consists of a set of `PREVOTE` messages of round `r` for the value `v` -from processes whose cumulative voting power is at least `2f + 1`. +A *Proof-of-Lock* is a set of `PREVOTE` messages of round of consensus for the +same value from processes whose cumulative voting power is at least `2f + 1`. +We denote as `POL(v,r)` a proof-of-lock of value `v` at round `r`. -#### **[PBTS-TIMELY-POL.1]** +For PBTS, we are particularly interested in the `POL(v,v.round)` produced in +the round `v.round` at which a value `v` was first proposed. +We call it a *timely* proof-of-lock for `v` because it can only be observed +if at least one correct process considered it `timely`: + +#### **[PBTS-TIMELY-POL.0]** If -- there is a valid `POL(v,r*)` for height `h`, and -- `r*` is the lowest-numbered round `r` of height `h` for which there is a valid `POL(v,r)`, and -- `POL(v,r*)` contains a `PREVOTE` message from at least one correct process, +- there is a valid `POL(v,r)` with `r = v.round`, and +- `POL(v,v.round)` contains a `PREVOTE` message from at least one correct process, -Then, where `p` is a such correct process: +Then, let `p` is a such correct process: -- `p` received a `PROPOSE` message of round `r*` and height `h`, and -- the `PROPOSE` message contained a proposal for value `v` with proposal time `v.time`, and -- a correct process `p` considered the proposal `timely`. +- `p` received a `PROPOSAL` message of round `v.round`, and +- the `PROPOSAL` message contained a proposal `(v, v.time, v.round)`, and +- `p` was in round `v.round` and evaluated the proposal time `v.time` as `timely`. -The round `r*` above defined will be, in most cases, -the round in which `v` was originally proposed, and when `v.time` was assigned, -using a `PROPOSE` message with `POLRound = -1`. -In any case, at least one correct process must consider the proposal `timely` at round `r*` -to enable a valid `POL(v,r*)` to be observed. +The existence of a such correct process `p` is guaranteed provided that the +voting power of Byzantine processes is bounded by `2f`. ### Derived Proof-of-Locks -#### **[PBTS-DERIVED-POL.1]** +The existence of `POL(v,r)` is a requirement for the decision of `v` at round +`r` of consensus. + +At the same time, the Time-Validity property establishes that if `v` is decided +then a timely proof-of-lock `POL(v,v.round)` must have been produced. + +So, we need to demonstrate here that any valid `POL(v,r)` is either a timely +proof-of-lock or it is derived from a timely proof-of-lock: + +#### **[PBTS-DERIVED-POL.0]** If -- there is a valid `POL(v,r)` for height `h`, and +- there is a valid `POL(v,r)`, and - `POL(v,r)` contains a `PREVOTE` message from at least one correct process, Then -- there is a valid `POL(v,r*)` for height `h`, with `r* <= r`, and -- `POL(v,r*)` contains a `PREVOTE` message from at least one correct process, and -- a correct process considered the proposal for `v` `timely` at round `r*`. +- there is a valid `POL(v,v.round)` with `v.round <= r` which is a timely proof-of-lock. -The above relation derives from a recursion on the round number `r`. -It is trivially observed when `r = r*`, the base of the recursion, -when a timely `POL(v,r*)` is obtained. -We need to ensure that, once a timely `POL(v,r*)` is obtained, -it is possible to obtain a valid `POL(v,r)` with `r > r*`, -without the need of satisfying the `timely` predicate (again) in round `r`. -In fact, since rounds are started in order, it is not likely that -a proposal time `v.time`, assigned at round `r*`, -will still be considered `timely` when the round `r > r*` is in progress. +The above relation is trivially observed when `r = v.round`, as `POL(v,r)` must +be a timely proof-of-lock. +Notice that we cannot have `r < v.round`, as `v.round` is defined as the first +round at which `v` was proposed. -In other words, the algorithm should ensure that once a `POL(v,r*)` attests -that the proposal for `v` is `timely`, -further valid `POL(v,r)` with `r > r*` can be obtained, -even though processes do not consider the proposal for `v` `timely` any longer. +For `r > v.round` we need to demonstrate that if there is a valid `POL(v,r)`, +then a timely `POL(v,v.round)` was previously obtained. +We observe that a condition for observing a `POL(v,r)` is that the proposer of +round `r` has broadcast a `PROPOSAL` message for `v`. +As `r > v.round`, we can affirm that `v` was not produced in round `r`. +Instead, by the protocol operation, `v` was a *valid value* for the proposer of +round `r`, which means that if the proposer has observed a `POL(v,vr)` with `vr +< r`. +The above operation considers a *correct* proposer, but since a `POL(v,r)` was +produced (by hypothesis) we can affirm that at least one correct process (also) +observed a `POL(v,vr)`. -> This can be achieved if the proposer of round `r' > r*` proposes `v` in a `PROPOSE` message -with `POLRound = r*`, and at least one correct processes is aware of a `POL(v,r*)`. -> From this point, if a valid `POL(v,r')` is achieved, it can replace the adopted `POL(v,r*)`. +> Considering the algorithm in the [arXiv paper][arXiv], `v` was proposed by +> the proposer `p` of round `round_p` because its `validValue_p` variable was +> set to `v`. +> The `PROPOSAL` message broadcast by the proposer, in this case, had `vr > -1`, +> and it could only be accepted by processes that also observed a `POL(v,vr)`. -### SAFETY +Thus, if there is a `POL(v,r)` with `r > v.round`, then there is a valid +`POL(v,vr)` with `v.round <= vr < r`. +If `vr = v.round` then `POL(vr,v)` is a timely proof-of-lock and we are done. +Otherwise, there is another valid `POL(v,vr')` with `v.round <= vr' < vr`, +and the above reasoning can be recursively applied until we get `vr' = v.round` +and observe a timely proof-of-lock. -The safety of the algorithm requires a *timely* proof-of-lock for a decided value, -either directly evaluated by a correct process, -or indirectly received through a derived proof-of-lock. +## Temporal analysis -#### **[PBTS-CONSENSUS-TIME-VALID.0]** +In this section we present invariants that need be observed for ensuring that +PBTS is both safe and live. + +In addition to the variables and system parameters already defined, we use +`beginRound(p,r)` as the value of process `p`'s local clock +when it starts round `r` of consensus. + +### Safety + +The safety of PBTS requires that if a value `v` is decided, then at least one +correct process `p` considered the associated proposal time `v.time` timely. +Following the definition of [timely proposals](#pbts-timely0) and +proof-of-locks, we require this condition to be asserted at a specific round of +consensus, defined as `v.round`: + +#### **[PBTS-SAFETY.0]** If -- there is a valid commit `C` for height `k` and round `r`, and +- there is a valid commit `C` for a value `v` - `C` contains a `PRECOMMIT` message from at least one correct process -Then, where `p` is one such correct process: +then there is a correct process `p` (not necessarily the same above considered) such that: -- since `p` is correct, `p` received a valid `POL(v,r)`, and -- `POL(v,r)` contains a `PREVOTE` message from at least one correct process, and -- `POL(v,r)` is derived from a timely `POL(v,r*)` with `r* <= r`, and -- `POL(v,r*)` contains a `PREVOTE` message from at least one correct process, and -- a correct process considered a proposal for `v` `timely` at round `r*`. +- `beginRound(p,v.round) <= proposalReceptionTime(p,v.round) <= beginRound(p,v.round+1)` and +- `proposalReceptionTime (p,v.round) - MSGDELAY - PRECISION <= v.time <= proposalReceptionTime(p,v.round) + PRECISION` -### LIVENESS +That is, a correct process `p` started round `v.round` and, while still at +round `v.round`, received a `PROPOSAL` message from round `v.round` proposing +`v`. +Moreover, the reception time of the original proposal for `v`, according with +`p`'s local clock, enabled `p` to consider the proposal time `v.time` as +`timely`. +This is the requirement established by PBTS for issuing a `PREVOTE` for the +proposal `(v, v.time, v.round)`, so for the eventual decision of `v`. -In terms of liveness, we need to ensure that a proposal broadcast by a correct process -will be considered `timely` by any correct process that is ready to accept that proposal. -So, if: +### Liveness -- the proposer `p` of a round `r` is correct, -- there is no `POL(v',r')` for any value `v'` and any round `r' < r`, -- `p` proposes a valid value `v` and sets `v.time` to the time it reads from its local clock, +The liveness of PBTS relies on correct processes accepting proposal times +assigned by correct proposers. +We thus present a set of conditions for assigning a proposal time `v.time` so +that every correct process should be able to issue a `PREVOTE` for `v`. -Then let `q` be a correct process that receives `p`'s proposal, we have: +#### **[PBTS-LIVENESS.0]** -- `q` receives `p`'s proposal after its clock reads `v.time - PRECISION`, and -- if `q` is at or joins round `r` while `p`'s proposal is being transmitted, -then `q` receives `p`'s proposal before its clock reads `v.time + MSGDELAY + PRECISION` +If -> Note that, before `GST`, we cannot ensure that every correct process receives `p`'s proposals, nor that it does it while ready to accept a round `r` proposal. +- the proposer of a round `r` of consensus is correct +- and it proposes a value `v` for the first time, with associated proposal time `v.time` -A correct process `q` as above defined must then consider `p`'s proposal `timely`. -It will then broadcast a `PREVOTE` message for `v` at round `r`, -thus enabling, from the Time-Validity point of view, `v` to be eventually decided. +then the proposal `(v, v.time, r)` is accepted by every correct process provided that: -#### Under-estimated `MSGDELAY`s +- `min{p is correct : beginRound(p,r)} <= v.time <= max{p is correct : beginRound(p,r)}` and +- `max{p is correct : beginRound(p,r)} <= v.time + MSGDELAY + PRECISION <= min{p is correct : beginRound(p,r+1)}` -The liveness assumptions of PBTS are conditioned by a conservative and clever -choice of the timing parameters, specially of `MSGDELAY`. -In fact, if the transmission delay for a message carrying a proposal is wrongly -estimated, correct processes may never consider a valid proposal as `timely`. +The first condition establishes a range of safe proposal times `v.time` for round `r`. +This condition is trivially observed if a correct proposer `p` sets `v.time` to the time it +reads from its clock when starting round `r` and proposing `v`. +A `PROPOSAL` message sent by `p` at local time `v.time` should not be received +by any correct process before its local clock reads `v.time - PRECISION`, so +that condition 2 of [PBTS-TIMELY.0] is observed. -To circumvent this liveness issue, which could result from a misconfiguration, -we assume that the `MSGDELAY` parameter can be increased as rounds do not -succeed on deciding a value, possibly because no proposal is considered -`timely` by enough processes. -The precise behavior for this workaround is under [discussion](https://github.com/tendermint/spec/issues/371). +The second condition establishes that every correct process should start round +`v.round` at a local time that allows `v.time` to still be considered timely, +according to condition 3. of [PBTS-TIMELY.0]. +In addition, it requires correct processes to stay long enough in round +`v.round` so that they can receive the `PROPOSAL` message of round `v.round`. +It assumed here that the proposer of `v` broadcasts a `PROPOSAL` message at +time `v.time`, according to its local clock, so that every correct process +should receive this message by time `v.time + MSGDELAY + PRECISION`, according +to their local clocks. Back to [main document][main]. diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index 0aca40519..dde3ec354 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -466,7 +466,7 @@ func SumTruncated(bz []byte) []byte { | vote | [google.protobuf.Duration](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Duration)| Parameter that, along with vote_delta, configures the timeout for the prevote and precommit step of the consensus algorithm. | 3 | | vote_delta | [google.protobuf.Duration](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Duration)| Parameter that, along with vote, configures the timeout for the prevote and precommit step of the consensus algorithm. | 4 | | commit | [google.protobuf.Duration](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Duration) | Parameter that configures how long Tendermint will wait after receiving a quorum of precommits before beginning consensus for the next height.| 5 | -| enable_commit_timeout_bypass | bool | Parameter that, if enabled, configures the node to proceed immediately to the next height once the node has received all precommits for a block, forgoing the commit timeout. | 6 | +| bypass_commit_timeout | bool | Parameter that, if enabled, configures the node to proceed immediately to the next height once the node has received all precommits for a block, forgoing the commit timeout. | 6 | ## Proof diff --git a/spec/core/genesis.md b/spec/core/genesis.md index 1dc019e77..5bf6156fd 100644 --- a/spec/core/genesis.md +++ b/spec/core/genesis.md @@ -4,31 +4,32 @@ The genesis file is the starting point of a chain. An application will populate ## Genesis Fields -- `genesis_time`: The genesis time is the time the blockchain started or will start. If nodes are started before this time they will sit idle until the time specified. -- `chain_id`: The chainid is the chain identifier. Every chain should have a unique identifier. When conducting a fork based upgrade, we recommend changing the chainid to avoid network or consensus errors. -- `initial_height`: This field is the starting height of the blockchain. When conducting a chain restart to avoid restarting at height 1, the network is able to start at a specified height. +- `genesis_time`: The time the blockchain started or will start. If nodes are started before this time they will sit idle until the time specified. +- `chain_id`: The chain identifier. Every chain should have a unique identifier. When conducting a fork based upgrade, we recommend changing the chainid to avoid network or consensus errors. +- `initial_height`: The starting height of the blockchain. When conducting a chain restart to avoid restarting at height 1, the network is able to start at a specified height. - `consensus_params` - `block` - `max_bytes`: The max amount of bytes a block can be. - `max_gas`: The maximum amount of gas that a block can have. - - `time_iota_ms`: This parameter has no value anymore in Tendermint-core. - -- `evidence` - - `max_age_num_blocks`: After this preset amount of blocks has passed a single piece of evidence is considered invalid - - `max_age_duration`: After this preset amount of time has passed a single piece of evidence is considered invalid. - - `max_bytes`: The max amount of bytes of all evidence included in a block. - -> Note: For evidence to be considered invalid, evidence must be older than both `max_age_num_blocks` and `max_age_duration` - -- `validator` - - `pub_key_types`: Defines which curves are to be accepted as a valid validator consensus key. Tendermint supports ed25519, sr25519 and secp256k1. - -- `version` - - `app_version`: The version of the application. This is set by the application and is used to identify which version of the app a user should be using in order to operate a node. - + - `evidence` + - `max_age_num_blocks`: After this preset amount of blocks has passed a single piece of evidence is considered invalid. + - `max_age_duration`: After this preset amount of time has passed a single piece of evidence is considered invalid. + - `max_bytes`: The max amount of bytes of all evidence included in a block. + - `validator` + - `pub_key_types`: Defines which curves are to be accepted as a valid validator consensus key. Tendermint supports ed25519, sr25519 and secp256k1. + - `version` + - `app_version`: The version of the application. This is set by the application and is used to identify which version of the app a user should be using in order to operate a node. + - `synchrony` + - `message_delay`: A bound on how long a proposal message may take to reach all validators on a network and still be considered valid. + - `precision`: A bound on how skewed the proposer's clock may be from any validator on the network while still producing valid proposals. + - `timeout` + - `propose`: How long the Tendermint consensus engine will wait for a proposal block before prevoting nil. + - `propose_delta`: How much the propose timeout increase with each round. + - `vote`: How long the consensus engine will wait after receiving +2/3 votes in a round. + - `vote_delta`: How much the vote timeout increases with each round. + - `commit`: How long the consensus engine will wait after receiving +2/3 precommits before beginning the next height. + - `bypass_commit_timeout`: Configures if the consensus engine will wait for the full commit timeout before proceeding to the next height. If this field is set to true, the conesnsus engine will proceed to the next height as soon as the node has gathered votes from all of the validators on the network. - `validators` - This is an array of validators. This validator set is used as the starting validator set of the chain. This field can be empty, if the application sets the validator set in `InitChain`. - - `app_hash`: The applications state root hash. This field does not need to be populated at the start of the chain, the application may provide the needed information via `Initchain`. - - `app_state`: This section is filled in by the application and is unknown to Tendermint. diff --git a/spec/p2p/messages/pex.md b/spec/p2p/messages/pex.md index ea5986f0d..e02393d52 100644 --- a/spec/p2p/messages/pex.md +++ b/spec/p2p/messages/pex.md @@ -26,7 +26,7 @@ PexResponse is an list of net addresses provided to a peer to dial. | Name | Type | Description | Field Number | |-------|------------------------------------|------------------------------------------|--------------| -| addresses | repeated [PexAddress](#PexAddress) | List of peer addresses available to dial | 1 | +| addresses | repeated [PexAddress](#pexaddress) | List of peer addresses available to dial | 1 | ### PexAddress @@ -41,7 +41,7 @@ into a `NodeAddress`. See [ParseNodeAddress](https://github.com/tendermint/tende Message is a [`oneof` protobuf type](https://developers.google.com/protocol-buffers/docs/proto#oneof). The one of consists of two messages. -| Name | Type | Description | Field Number | -|--------------|---------------------------|------------------------------------------------------|--------------| -| pex_request | [PexRequest](#PexRequest) | Empty request asking for a list of addresses to dial | 3 | -| pex_response | [PexResponse](#PexResponse) | List of addresses to dial | 4 | +| Name | Type | Description | Field Number | +|--------------|-----------------------------|------------------------------------------------------|--------------| +| pex_request | [PexRequest](#pexrequest) | Empty request asking for a list of addresses to dial | 3 | +| pex_response | [PexResponse](#pexresponse) | List of addresses to dial | 4 | diff --git a/test/app/test.sh b/test/app/test.sh index 6896ee5f8..dba8a9c43 100755 --- a/test/app/test.sh +++ b/test/app/test.sh @@ -1,5 +1,5 @@ -#! /bin/bash -set -ex +#!/bin/bash +set -exo pipefail #- kvstore over socket, curl @@ -8,9 +8,19 @@ set -ex export PATH="$GOBIN:$PATH" export TMHOME=$HOME/.tendermint_app -function kvstore_over_socket(){ - rm -rf $TMHOME +function init_validator() { + rm -rf -- "$TMHOME" tendermint init validator + + # The default configuration sets a null indexer, but these tests require + # indexing to be enabled. Rewrite the config file to set the "kv" indexer + # before starting up the node. + sed -i'' -e '/indexer = \["null"\]/c\ +indexer = ["kv"]' "$TMHOME/config/config.toml" +} + +function kvstore_over_socket() { + init_validator echo "Starting kvstore_over_socket" abci-cli kvstore > /dev/null & pid_kvstore=$! @@ -25,9 +35,8 @@ function kvstore_over_socket(){ } # start tendermint first -function kvstore_over_socket_reorder(){ - rm -rf $TMHOME - tendermint init validator +function kvstore_over_socket_reorder() { + init_validator echo "Starting kvstore_over_socket_reorder (ie. start tendermint first)" tendermint start --mode validator > tendermint.log & pid_tendermint=$! @@ -42,7 +51,7 @@ function kvstore_over_socket_reorder(){ kill -9 $pid_kvstore $pid_tendermint } -case "$1" in +case "$1" in "kvstore_over_socket") kvstore_over_socket ;; diff --git a/test/docker/config-template.toml b/test/docker/config-template.toml index a90eb7bd5..6ce39c9f8 100644 --- a/test/docker/config-template.toml +++ b/test/docker/config-template.toml @@ -1,2 +1,5 @@ [rpc] laddr = "tcp://0.0.0.0:26657" + +[tx-index] +indexer = ["kv"] diff --git a/test/e2e/README.md b/test/e2e/README.md index 00bce5ad8..70510b6fa 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -11,7 +11,7 @@ This creates and runs a testnet named `ci` under `networks/ci/`. ## Conceptual Overview -End-to-end testnets are used to test Tendermint functionality as a user would use it, by spinning up a set of nodes with various configurations and making sure the nodes and network behave correctly. The background for the E2E test suite is outlined in [RFC-001](https://github.com/tendermint/tendermint/blob/master/docs/rfc/rfc-001-end-to-end-testing.md). +End-to-end testnets are used to test Tendermint functionality as a user would use it, by spinning up a set of nodes with various configurations and making sure the nodes and network behave correctly. The background for the E2E test suite is outlined in [RFC-001](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-066-e2e-testing.md). The end-to-end tests can be thought of in this manner: @@ -180,4 +180,4 @@ tendermint start ./build/node ./node.socket.toml ``` -Check `node/config.go` to see how the settings of the test application can be tweaked. \ No newline at end of file +Check `node/config.go` to see how the settings of the test application can be tweaked. diff --git a/test/e2e/app/app.go b/test/e2e/app/app.go index e0c080f87..64ccd2e28 100644 --- a/test/e2e/app/app.go +++ b/test/e2e/app/app.go @@ -2,21 +2,31 @@ package app import ( "bytes" + "context" "encoding/base64" + "encoding/binary" "errors" "fmt" + "math/rand" "path/filepath" "sort" "strconv" + "strings" "sync" "github.com/tendermint/tendermint/abci/example/code" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/version" ) +const ( + voteExtensionKey string = "extensionSum" + voteExtensionMaxVal int64 = 128 +) + // Application is an ABCI application for use by end-to-end tests. It is a // simple key/value store for strings, storing data in memory and persisting // to disk as JSON, taking state sync snapshots if requested. @@ -90,8 +100,13 @@ func NewApplication(cfg *Config) (*Application, error) { if err != nil { return nil, err } + logger, err := log.NewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo) + if err != nil { + return nil, err + } + return &Application{ - logger: log.MustNewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo), + logger: logger, state: state, snapshots: snapshots, cfg: cfg, @@ -99,20 +114,20 @@ func NewApplication(cfg *Config) (*Application, error) { } // Info implements ABCI. -func (app *Application) Info(req abci.RequestInfo) abci.ResponseInfo { +func (app *Application) Info(_ context.Context, req *abci.RequestInfo) (*abci.ResponseInfo, error) { app.mu.Lock() defer app.mu.Unlock() - return abci.ResponseInfo{ + return &abci.ResponseInfo{ Version: version.ABCIVersion, AppVersion: 1, LastBlockHeight: int64(app.state.Height), LastBlockAppHash: app.state.Hash, - } + }, nil } // Info implements ABCI. -func (app *Application) InitChain(req abci.RequestInitChain) abci.ResponseInitChain { +func (app *Application) InitChain(_ context.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { app.mu.Lock() defer app.mu.Unlock() @@ -124,7 +139,7 @@ func (app *Application) InitChain(req abci.RequestInitChain) abci.ResponseInitCh panic(err) } } - resp := abci.ResponseInitChain{ + resp := &abci.ResponseInitChain{ AppHash: app.state.Hash, ConsensusParams: &types.ConsensusParams{ Version: &types.VersionParams{ @@ -135,26 +150,26 @@ func (app *Application) InitChain(req abci.RequestInitChain) abci.ResponseInitCh if resp.Validators, err = app.validatorUpdates(0); err != nil { panic(err) } - return resp + return resp, nil } // CheckTx implements ABCI. -func (app *Application) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { +func (app *Application) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) { app.mu.Lock() defer app.mu.Unlock() _, _, err := parseTx(req.Tx) if err != nil { - return abci.ResponseCheckTx{ + return &abci.ResponseCheckTx{ Code: code.CodeTypeEncodingError, Log: err.Error(), - } + }, nil } - return abci.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1} + return &abci.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1}, nil } // FinalizeBlock implements ABCI. -func (app *Application) FinalizeBlock(req abci.RequestFinalizeBlock) abci.ResponseFinalizeBlock { +func (app *Application) FinalizeBlock(_ context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { var txs = make([]*abci.ExecTxResult, len(req.Txs)) app.mu.Lock() @@ -170,12 +185,12 @@ func (app *Application) FinalizeBlock(req abci.RequestFinalizeBlock) abci.Respon txs[i] = &abci.ExecTxResult{Code: code.CodeTypeOK} } - valUpdates, err := app.validatorUpdates(uint64(req.Header.Height)) + valUpdates, err := app.validatorUpdates(uint64(req.Height)) if err != nil { panic(err) } - return abci.ResponseFinalizeBlock{ + return &abci.ResponseFinalizeBlock{ TxResults: txs, ValidatorUpdates: valUpdates, Events: []abci.Event{ @@ -188,16 +203,16 @@ func (app *Application) FinalizeBlock(req abci.RequestFinalizeBlock) abci.Respon }, { Key: "height", - Value: strconv.Itoa(int(req.Header.Height)), + Value: strconv.Itoa(int(req.Height)), }, }, }, }, - } + }, nil } // Commit implements ABCI. -func (app *Application) Commit() abci.ResponseCommit { +func (app *Application) Commit(_ context.Context) (*abci.ResponseCommit, error) { app.mu.Lock() defer app.mu.Unlock() @@ -210,36 +225,36 @@ func (app *Application) Commit() abci.ResponseCommit { if err != nil { panic(err) } - app.logger.Info("Created state sync snapshot", "height", snapshot.Height) + app.logger.Info("created state sync snapshot", "height", snapshot.Height) err = app.snapshots.Prune(maxSnapshotCount) if err != nil { - app.logger.Error("Failed to prune snapshots", "err", err) + app.logger.Error("failed to prune snapshots", "err", err) } } retainHeight := int64(0) if app.cfg.RetainBlocks > 0 { retainHeight = int64(height - app.cfg.RetainBlocks + 1) } - return abci.ResponseCommit{ + return &abci.ResponseCommit{ Data: hash, RetainHeight: retainHeight, - } + }, nil } // Query implements ABCI. -func (app *Application) Query(req abci.RequestQuery) abci.ResponseQuery { +func (app *Application) Query(_ context.Context, req *abci.RequestQuery) (*abci.ResponseQuery, error) { app.mu.Lock() defer app.mu.Unlock() - return abci.ResponseQuery{ + return &abci.ResponseQuery{ Height: int64(app.state.Height), Key: req.Data, Value: []byte(app.state.Get(string(req.Data))), - } + }, nil } // ListSnapshots implements ABCI. -func (app *Application) ListSnapshots(req abci.RequestListSnapshots) abci.ResponseListSnapshots { +func (app *Application) ListSnapshots(_ context.Context, req *abci.RequestListSnapshots) (*abci.ResponseListSnapshots, error) { app.mu.Lock() defer app.mu.Unlock() @@ -247,11 +262,11 @@ func (app *Application) ListSnapshots(req abci.RequestListSnapshots) abci.Respon if err != nil { panic(err) } - return abci.ResponseListSnapshots{Snapshots: snapshots} + return &abci.ResponseListSnapshots{Snapshots: snapshots}, nil } // LoadSnapshotChunk implements ABCI. -func (app *Application) LoadSnapshotChunk(req abci.RequestLoadSnapshotChunk) abci.ResponseLoadSnapshotChunk { +func (app *Application) LoadSnapshotChunk(_ context.Context, req *abci.RequestLoadSnapshotChunk) (*abci.ResponseLoadSnapshotChunk, error) { app.mu.Lock() defer app.mu.Unlock() @@ -259,11 +274,11 @@ func (app *Application) LoadSnapshotChunk(req abci.RequestLoadSnapshotChunk) abc if err != nil { panic(err) } - return abci.ResponseLoadSnapshotChunk{Chunk: chunk} + return &abci.ResponseLoadSnapshotChunk{Chunk: chunk}, nil } // OfferSnapshot implements ABCI. -func (app *Application) OfferSnapshot(req abci.RequestOfferSnapshot) abci.ResponseOfferSnapshot { +func (app *Application) OfferSnapshot(_ context.Context, req *abci.RequestOfferSnapshot) (*abci.ResponseOfferSnapshot, error) { app.mu.Lock() defer app.mu.Unlock() @@ -272,11 +287,11 @@ func (app *Application) OfferSnapshot(req abci.RequestOfferSnapshot) abci.Respon } app.restoreSnapshot = req.Snapshot app.restoreChunks = [][]byte{} - return abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ACCEPT} + return &abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ACCEPT}, nil } // ApplySnapshotChunk implements ABCI. -func (app *Application) ApplySnapshotChunk(req abci.RequestApplySnapshotChunk) abci.ResponseApplySnapshotChunk { +func (app *Application) ApplySnapshotChunk(_ context.Context, req *abci.RequestApplySnapshotChunk) (*abci.ResponseApplySnapshotChunk, error) { app.mu.Lock() defer app.mu.Unlock() @@ -296,23 +311,176 @@ func (app *Application) ApplySnapshotChunk(req abci.RequestApplySnapshotChunk) a app.restoreSnapshot = nil app.restoreChunks = nil } - return abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT} + return &abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil } -func (app *Application) PrepareProposal(req abci.RequestPrepareProposal) abci.ResponsePrepareProposal { - return abci.ResponsePrepareProposal{BlockData: req.BlockData} +// PrepareProposal will take the given transactions and attempt to prepare a +// proposal from them when it's our turn to do so. In the process, vote +// extensions from the previous round of consensus, if present, will be used to +// construct a special transaction whose value is the sum of all of the vote +// extensions from the previous round. +// +// NB: Assumes that the supplied transactions do not exceed `req.MaxTxBytes`. +// If adding a special vote extension-generated transaction would cause the +// total number of transaction bytes to exceed `req.MaxTxBytes`, we will not +// append our special vote extension transaction. +func (app *Application) PrepareProposal(_ context.Context, req *abci.RequestPrepareProposal) (*abci.ResponsePrepareProposal, error) { + var sum int64 + var extCount int + for _, vote := range req.LocalLastCommit.Votes { + if !vote.SignedLastBlock || len(vote.VoteExtension) == 0 { + continue + } + extValue, err := parseVoteExtension(vote.VoteExtension) + // This should have been verified in VerifyVoteExtension + if err != nil { + panic(fmt.Errorf("failed to parse vote extension in PrepareProposal: %w", err)) + } + valAddr := crypto.Address(vote.Validator.Address) + app.logger.Info("got vote extension value in PrepareProposal", "valAddr", valAddr, "value", extValue) + sum += extValue + extCount++ + } + // We only generate our special transaction if we have vote extensions + if extCount > 0 { + var totalBytes int64 + extTxPrefix := fmt.Sprintf("%s=", voteExtensionKey) + extTx := []byte(fmt.Sprintf("%s%d", extTxPrefix, sum)) + app.logger.Info("preparing proposal with custom transaction from vote extensions", "tx", extTx) + // Our generated transaction takes precedence over any supplied + // transaction that attempts to modify the "extensionSum" value. + txRecords := make([]*abci.TxRecord, len(req.Txs)+1) + for i, tx := range req.Txs { + if strings.HasPrefix(string(tx), extTxPrefix) { + txRecords[i] = &abci.TxRecord{ + Action: abci.TxRecord_REMOVED, + Tx: tx, + } + } else { + txRecords[i] = &abci.TxRecord{ + Action: abci.TxRecord_UNMODIFIED, + Tx: tx, + } + totalBytes += int64(len(tx)) + } + } + if totalBytes+int64(len(extTx)) < req.MaxTxBytes { + txRecords[len(req.Txs)] = &abci.TxRecord{ + Action: abci.TxRecord_ADDED, + Tx: extTx, + } + } else { + app.logger.Info( + "too many txs to include special vote extension-generated tx", + "totalBytes", totalBytes, + "MaxTxBytes", req.MaxTxBytes, + "extTx", extTx, + "extTxLen", len(extTx), + ) + } + return &abci.ResponsePrepareProposal{ + TxRecords: txRecords, + }, nil + } + // None of the transactions are modified by this application. + trs := make([]*abci.TxRecord, 0, len(req.Txs)) + var totalBytes int64 + for _, tx := range req.Txs { + totalBytes += int64(len(tx)) + if totalBytes > req.MaxTxBytes { + break + } + trs = append(trs, &abci.TxRecord{ + Action: abci.TxRecord_UNMODIFIED, + Tx: tx, + }) + } + return &abci.ResponsePrepareProposal{TxRecords: trs}, nil } // ProcessProposal implements part of the Application interface. // It accepts any proposal that does not contain a malformed transaction. -func (app *Application) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal { +func (app *Application) ProcessProposal(_ context.Context, req *abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error) { for _, tx := range req.Txs { - _, _, err := parseTx(tx) + k, v, err := parseTx(tx) if err != nil { - return abci.ResponseProcessProposal{Accept: false} + app.logger.Error("malformed transaction in ProcessProposal", "tx", tx, "err", err) + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, nil + } + // Additional check for vote extension-related txs + if k == voteExtensionKey { + _, err := strconv.Atoi(v) + if err != nil { + app.logger.Error("malformed vote extension transaction", k, v, "err", err) + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, nil + } } } - return abci.ResponseProcessProposal{Accept: true} + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil +} + +// ExtendVote will produce vote extensions in the form of random numbers to +// demonstrate vote extension nondeterminism. +// +// In the next block, if there are any vote extensions from the previous block, +// a new transaction will be proposed that updates a special value in the +// key/value store ("extensionSum") with the sum of all of the numbers collected +// from the vote extensions. +func (app *Application) ExtendVote(_ context.Context, req *abci.RequestExtendVote) (*abci.ResponseExtendVote, error) { + // We ignore any requests for vote extensions that don't match our expected + // next height. + if req.Height != int64(app.state.Height)+1 { + app.logger.Error( + "got unexpected height in ExtendVote request", + "expectedHeight", app.state.Height+1, + "requestHeight", req.Height, + ) + return &abci.ResponseExtendVote{}, nil + } + ext := make([]byte, binary.MaxVarintLen64) + // We don't care that these values are generated by a weak random number + // generator. It's just for test purposes. + // nolint:gosec // G404: Use of weak random number generator + num := rand.Int63n(voteExtensionMaxVal) + extLen := binary.PutVarint(ext, num) + app.logger.Info("generated vote extension", "num", num, "ext", fmt.Sprintf("%x", ext[:extLen]), "state.Height", app.state.Height) + return &abci.ResponseExtendVote{ + VoteExtension: ext[:extLen], + }, nil +} + +// VerifyVoteExtension simply validates vote extensions from other validators +// without doing anything about them. In this case, it just makes sure that the +// vote extension is a well-formed integer value. +func (app *Application) VerifyVoteExtension(_ context.Context, req *abci.RequestVerifyVoteExtension) (*abci.ResponseVerifyVoteExtension, error) { + // We allow vote extensions to be optional + if len(req.VoteExtension) == 0 { + return &abci.ResponseVerifyVoteExtension{ + Status: abci.ResponseVerifyVoteExtension_ACCEPT, + }, nil + } + if req.Height != int64(app.state.Height)+1 { + app.logger.Error( + "got unexpected height in VerifyVoteExtension request", + "expectedHeight", app.state.Height, + "requestHeight", req.Height, + ) + return &abci.ResponseVerifyVoteExtension{ + Status: abci.ResponseVerifyVoteExtension_REJECT, + }, nil + } + + num, err := parseVoteExtension(req.VoteExtension) + if err != nil { + app.logger.Error("failed to verify vote extension", "req", req, "err", err) + return &abci.ResponseVerifyVoteExtension{ + Status: abci.ResponseVerifyVoteExtension_REJECT, + }, nil + } + app.logger.Info("verified vote extension value", "req", req, "num", num) + return &abci.ResponseVerifyVoteExtension{ + Status: abci.ResponseVerifyVoteExtension_ACCEPT, + }, nil } func (app *Application) Rollback() error { @@ -360,3 +528,19 @@ func parseTx(tx []byte) (string, string, error) { } return string(parts[0]), string(parts[1]), nil } + +// parseVoteExtension attempts to parse the given extension data into a positive +// integer value. +func parseVoteExtension(ext []byte) (int64, error) { + num, errVal := binary.Varint(ext) + if errVal == 0 { + return 0, errors.New("vote extension is too small to parse") + } + if errVal < 0 { + return 0, errors.New("vote extension value is too large") + } + if num >= voteExtensionMaxVal { + return 0, fmt.Errorf("vote extension value must be smaller than %d (was %d)", voteExtensionMaxVal, num) + } + return num, nil +} diff --git a/test/e2e/app/snapshots.go b/test/e2e/app/snapshots.go index 65edbc3a5..61e34bd07 100644 --- a/test/e2e/app/snapshots.go +++ b/test/e2e/app/snapshots.go @@ -92,7 +92,7 @@ func (s *SnapshotStore) Create(state *State) (abci.Snapshot, error) { snapshot := abci.Snapshot{ Height: state.Height, Format: 1, - Hash: hashItems(state.Values), + Hash: hashItems(state.Values, state.Height), Chunks: byteChunks(bz), } err = os.WriteFile(filepath.Join(s.dir, fmt.Sprintf("%v.json", state.Height)), bz, 0644) diff --git a/test/e2e/app/state.go b/test/e2e/app/state.go index e82a22539..17d8cd75f 100644 --- a/test/e2e/app/state.go +++ b/test/e2e/app/state.go @@ -3,6 +3,7 @@ package app import ( "crypto/sha256" + "encoding/binary" "encoding/json" "errors" "fmt" @@ -38,7 +39,7 @@ func NewState(dir string, persistInterval uint64) (*State, error) { previousFile: filepath.Join(dir, prevStateFileName), persistInterval: persistInterval, } - state.Hash = hashItems(state.Values) + state.Hash = hashItems(state.Values, state.Height) err := state.load() switch { case errors.Is(err, os.ErrNotExist): @@ -114,7 +115,7 @@ func (s *State) Import(height uint64, jsonBytes []byte) error { } s.Height = height s.Values = values - s.Hash = hashItems(values) + s.Hash = hashItems(values, height) return s.save() } @@ -140,7 +141,6 @@ func (s *State) Set(key, value string) { func (s *State) Commit() (uint64, []byte, error) { s.Lock() defer s.Unlock() - s.Hash = hashItems(s.Values) switch { case s.Height > 0: s.Height++ @@ -149,6 +149,7 @@ func (s *State) Commit() (uint64, []byte, error) { default: s.Height = 1 } + s.Hash = hashItems(s.Values, s.Height) if s.persistInterval > 0 && s.Height%s.persistInterval == 0 { err := s.save() if err != nil { @@ -171,7 +172,7 @@ func (s *State) Rollback() error { } // hashItems hashes a set of key/value items. -func hashItems(items map[string]string) []byte { +func hashItems(items map[string]string, height uint64) []byte { keys := make([]string, 0, len(items)) for key := range items { keys = append(keys, key) @@ -179,6 +180,9 @@ func hashItems(items map[string]string) []byte { sort.Strings(keys) hasher := sha256.New() + var b [8]byte + binary.BigEndian.PutUint64(b[:], height) + _, _ = hasher.Write(b[:]) for _, key := range keys { _, _ = hasher.Write([]byte(key)) _, _ = hasher.Write([]byte{0}) diff --git a/test/e2e/generator/main.go b/test/e2e/generator/main.go index 10b809279..bec78d89c 100644 --- a/test/e2e/generator/main.go +++ b/test/e2e/generator/main.go @@ -2,7 +2,9 @@ package main import ( + "context" "fmt" + stdlog "log" "math/rand" "os" "path/filepath" @@ -17,21 +19,35 @@ const ( randomSeed int64 = 4827085738 ) -var logger = log.MustNewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo) - func main() { - NewCLI().Run() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + cli, err := NewCLI() + if err != nil { + stdlog.Fatal(err) + } + + cli.Run(ctx) } // CLI is the Cobra-based command-line interface. type CLI struct { - root *cobra.Command - opts Options + root *cobra.Command + opts Options + logger log.Logger } // NewCLI sets up the CLI. -func NewCLI() *CLI { - cli := &CLI{} +func NewCLI() (*CLI, error) { + logger, err := log.NewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo) + if err != nil { + return nil, err + } + + cli := &CLI{ + logger: logger, + } cli.root = &cobra.Command{ Use: "generator", Short: "End-to-end testnet generator", @@ -51,7 +67,7 @@ func NewCLI() *CLI { cli.root.PersistentFlags().IntVarP(&cli.opts.MaxNetworkSize, "max-size", "", 0, "Maxmum network size (nodes), 0 is unlimited") - return cli + return cli, nil } // generate generates manifests in a directory. @@ -90,9 +106,9 @@ func (cli *CLI) generate() error { } // Run runs the CLI. -func (cli *CLI) Run() { - if err := cli.root.Execute(); err != nil { - logger.Error(err.Error()) +func (cli *CLI) Run(ctx context.Context) { + if err := cli.root.ExecuteContext(ctx); err != nil { + cli.logger.Error(err.Error()) os.Exit(1) } } diff --git a/test/e2e/node/main.go b/test/e2e/node/main.go index 7f46eea89..2cbb9e4b0 100644 --- a/test/e2e/node/main.go +++ b/test/e2e/node/main.go @@ -34,8 +34,6 @@ import ( e2e "github.com/tendermint/tendermint/test/e2e/pkg" ) -var logger = log.MustNewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo) - // main is the binary entrypoint. func main() { ctx, cancel := context.WithCancel(context.Background()) @@ -51,7 +49,6 @@ func main() { } if err := run(ctx, configFile); err != nil { - logger.Error(err.Error()) os.Exit(1) } } @@ -63,9 +60,20 @@ func run(ctx context.Context, configFile string) error { return err } + logger, err := log.NewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo) + if err != nil { + // have print here because we can't log (yet), use the logger + // everywhere else. + fmt.Fprintln(os.Stderr, "ERROR:", err) + return err + } + // Start remote signer (must start before node if running builtin). if cfg.PrivValServer != "" { - if err = startSigner(ctx, cfg); err != nil { + if err = startSigner(ctx, logger, cfg); err != nil { + logger.Error("starting signer", + "server", cfg.PrivValServer, + "err", err) return err } if cfg.Protocol == "builtin" { @@ -76,11 +84,11 @@ func run(ctx context.Context, configFile string) error { // Start app server. switch cfg.Protocol { case "socket", "grpc": - err = startApp(ctx, cfg) + err = startApp(ctx, logger, cfg) case "builtin": switch cfg.Mode { case string(e2e.ModeLight): - err = startLightNode(ctx, cfg) + err = startLightNode(ctx, logger, cfg) case string(e2e.ModeSeed): err = startSeedNode(ctx) default: @@ -90,6 +98,10 @@ func run(ctx context.Context, configFile string) error { err = fmt.Errorf("invalid protocol %q", cfg.Protocol) } if err != nil { + logger.Error("starting node", + "protocol", cfg.Protocol, + "mode", cfg.Mode, + "err", err) return err } @@ -100,7 +112,7 @@ func run(ctx context.Context, configFile string) error { } // startApp starts the application server, listening for connections from Tendermint. -func startApp(ctx context.Context, cfg *Config) error { +func startApp(ctx context.Context, logger log.Logger, cfg *Config) error { app, err := app.NewApplication(cfg.App()) if err != nil { return err @@ -160,7 +172,7 @@ func startSeedNode(ctx context.Context) error { return n.Start(ctx) } -func startLightNode(ctx context.Context, cfg *Config) error { +func startLightNode(ctx context.Context, logger log.Logger, cfg *Config) error { tmcfg, nodeLogger, err := setupNode() if err != nil { return err @@ -218,7 +230,7 @@ func startLightNode(ctx context.Context, cfg *Config) error { } // startSigner starts a signer server connecting to the given endpoint. -func startSigner(ctx context.Context, cfg *Config) error { +func startSigner(ctx context.Context, logger log.Logger, cfg *Config) error { filePV, err := privval.LoadFilePV(cfg.PrivValKey, cfg.PrivValState) if err != nil { return err @@ -236,7 +248,7 @@ func startSigner(ctx context.Context, cfg *Config) error { if err != nil { return err } - ss := grpcprivval.NewSignerServer(cfg.ChainID, filePV, logger) + ss := grpcprivval.NewSignerServer(logger, cfg.ChainID, filePV) s := grpc.NewServer() diff --git a/test/e2e/runner/evidence.go b/test/e2e/runner/evidence.go index b29b574a2..849e4edc3 100644 --- a/test/e2e/runner/evidence.go +++ b/test/e2e/runner/evidence.go @@ -12,7 +12,6 @@ import ( "time" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/test/factory" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/privval" @@ -63,7 +62,6 @@ func InjectEvidence(ctx context.Context, logger log.Logger, r *rand.Rand, testne if err != nil { return err } - valSet, err := types.ValidatorSetFromExistingValidators(valRes.Validators) if err != nil { return err @@ -257,26 +255,26 @@ func makeHeaderRandom(chainID string, height int64) *types.Header { Height: height, Time: time.Now(), LastBlockID: makeBlockID([]byte("headerhash"), 1000, []byte("partshash")), - LastCommitHash: crypto.CRandBytes(tmhash.Size), - DataHash: crypto.CRandBytes(tmhash.Size), - ValidatorsHash: crypto.CRandBytes(tmhash.Size), - NextValidatorsHash: crypto.CRandBytes(tmhash.Size), - ConsensusHash: crypto.CRandBytes(tmhash.Size), - AppHash: crypto.CRandBytes(tmhash.Size), - LastResultsHash: crypto.CRandBytes(tmhash.Size), - EvidenceHash: crypto.CRandBytes(tmhash.Size), + LastCommitHash: crypto.CRandBytes(crypto.HashSize), + DataHash: crypto.CRandBytes(crypto.HashSize), + ValidatorsHash: crypto.CRandBytes(crypto.HashSize), + NextValidatorsHash: crypto.CRandBytes(crypto.HashSize), + ConsensusHash: crypto.CRandBytes(crypto.HashSize), + AppHash: crypto.CRandBytes(crypto.HashSize), + LastResultsHash: crypto.CRandBytes(crypto.HashSize), + EvidenceHash: crypto.CRandBytes(crypto.HashSize), ProposerAddress: crypto.CRandBytes(crypto.AddressSize), } } func makeRandomBlockID() types.BlockID { - return makeBlockID(crypto.CRandBytes(tmhash.Size), 100, crypto.CRandBytes(tmhash.Size)) + return makeBlockID(crypto.CRandBytes(crypto.HashSize), 100, crypto.CRandBytes(crypto.HashSize)) } func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.BlockID { var ( - h = make([]byte, tmhash.Size) - psH = make([]byte, tmhash.Size) + h = make([]byte, crypto.HashSize) + psH = make([]byte, crypto.HashSize) ) copy(h, hash) copy(psH, partSetHash) @@ -289,8 +287,7 @@ func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.Bloc } } -func mutateValidatorSet(ctx context.Context, privVals []types.MockPV, vals *types.ValidatorSet, -) ([]types.PrivValidator, *types.ValidatorSet, error) { +func mutateValidatorSet(ctx context.Context, privVals []types.MockPV, vals *types.ValidatorSet) ([]types.PrivValidator, *types.ValidatorSet, error) { newVal, newPrivVal, err := factory.Validator(ctx, 10) if err != nil { return nil, nil, err diff --git a/test/e2e/runner/main.go b/test/e2e/runner/main.go index 7d147f411..c4a73d33f 100644 --- a/test/e2e/runner/main.go +++ b/test/e2e/runner/main.go @@ -134,7 +134,7 @@ func NewCLI(logger log.Logger) *CLI { if err = Wait(ctx, logger, cli.testnet, 5); err != nil { // wait for network to settle before tests return err } - if err := Test(logger, cli.testnet); err != nil { + if err := Test(cli.testnet); err != nil { return err } return nil @@ -259,7 +259,7 @@ func NewCLI(logger log.Logger) *CLI { Use: "test", Short: "Runs test cases against a running testnet", RunE: func(cmd *cobra.Command, args []string) error { - return Test(logger, cli.testnet) + return Test(cli.testnet) }, }) diff --git a/test/e2e/runner/setup.go b/test/e2e/runner/setup.go index 1b7d25bd7..507dc2d04 100644 --- a/test/e2e/runner/setup.go +++ b/test/e2e/runner/setup.go @@ -237,6 +237,7 @@ func MakeConfig(node *e2e.Node) (*config.Config, error) { cfg := config.DefaultConfig() cfg.Moniker = node.Name cfg.ProxyApp = AppAddressTCP + cfg.TxIndex = config.TestTxIndexConfig() if node.LogLevel != "" { cfg.LogLevel = node.LogLevel diff --git a/test/e2e/runner/test.go b/test/e2e/runner/test.go index 22fcd730e..2237588a1 100644 --- a/test/e2e/runner/test.go +++ b/test/e2e/runner/test.go @@ -3,12 +3,11 @@ package main import ( "os" - "github.com/tendermint/tendermint/libs/log" e2e "github.com/tendermint/tendermint/test/e2e/pkg" ) // Test runs test cases under tests/ -func Test(logger log.Logger, testnet *e2e.Testnet) error { +func Test(testnet *e2e.Testnet) error { err := os.Setenv("E2E_MANIFEST", testnet.File) if err != nil { return err diff --git a/test/e2e/tests/app_test.go b/test/e2e/tests/app_test.go index 7234a5cde..ed041e186 100644 --- a/test/e2e/tests/app_test.go +++ b/test/e2e/tests/app_test.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "math/rand" + "strconv" "testing" "time" @@ -45,26 +46,28 @@ func TestApp_Hash(t *testing.T) { testNode(t, func(ctx context.Context, t *testing.T, node e2e.Node) { client, err := node.Client() require.NoError(t, err) + info, err := client.ABCIInfo(ctx) require.NoError(t, err) require.NotEmpty(t, info.Response.LastBlockAppHash, "expected app to return app hash") - status, err := client.Status(ctx) + // In next-block execution, the app hash is stored in the next block + blockHeight := info.Response.LastBlockHeight + 1 + + require.Eventually(t, func() bool { + status, err := client.Status(ctx) + require.NoError(t, err) + require.NotZero(t, status.SyncInfo.LatestBlockHeight) + return status.SyncInfo.LatestBlockHeight >= blockHeight + }, 60*time.Second, 500*time.Millisecond) + + block, err := client.Block(ctx, &blockHeight) require.NoError(t, err) - require.NotZero(t, status.SyncInfo.LatestBlockHeight) - - block, err := client.Block(ctx, &info.Response.LastBlockHeight) - require.NoError(t, err) - - if info.Response.LastBlockHeight == block.Block.Height { - require.Equal(t, - fmt.Sprintf("%x", info.Response.LastBlockAppHash), - fmt.Sprintf("%x", block.Block.AppHash.Bytes()), - "app hash does not match last block's app hash") - } - - require.True(t, status.SyncInfo.LatestBlockHeight >= info.Response.LastBlockHeight, - "status out of sync with application") + require.Equal(t, blockHeight, block.Block.Height) + require.Equal(t, + fmt.Sprintf("%x", info.Response.LastBlockAppHash), + fmt.Sprintf("%x", block.Block.AppHash.Bytes()), + "app hash does not match last block's app hash") }) } @@ -184,3 +187,18 @@ func TestApp_Tx(t *testing.T) { } } + +func TestApp_VoteExtensions(t *testing.T) { + testNode(t, func(ctx context.Context, t *testing.T, node e2e.Node) { + client, err := node.Client() + require.NoError(t, err) + + // This special value should have been created by way of vote extensions + resp, err := client.ABCIQuery(ctx, "", []byte("extensionSum")) + require.NoError(t, err) + + extSum, err := strconv.Atoi(string(resp.Response.Value)) + require.NoError(t, err) + require.GreaterOrEqual(t, extSum, 0) + }) +} diff --git a/test/fuzz/Makefile b/test/fuzz/Makefile deleted file mode 100644 index 3bf4486b8..000000000 --- a/test/fuzz/Makefile +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/make -f - -.PHONY: fuzz-mempool -fuzz-mempool: - cd mempool && \ - rm -f *-fuzz.zip && \ - go-fuzz-build && \ - go-fuzz - -.PHONY: fuzz-p2p-sc -fuzz-p2p-sc: - cd p2p/secretconnection && \ - rm -f *-fuzz.zip && \ - go run ./init-corpus/main.go && \ - go-fuzz-build && \ - go-fuzz - -.PHONY: fuzz-rpc-server -fuzz-rpc-server: - cd rpc/jsonrpc/server && \ - rm -f *-fuzz.zip && \ - go-fuzz-build && \ - go-fuzz - -clean: - find . -name corpus -type d -exec rm -rf {} +; - find . -name crashers -type d -exec rm -rf {} +; - find . -name suppressions -type d -exec rm -rf {} +; - find . -name *\.zip -type f -delete diff --git a/test/fuzz/README.md b/test/fuzz/README.md index 707217afd..11ec9d521 100644 --- a/test/fuzz/README.md +++ b/test/fuzz/README.md @@ -5,68 +5,18 @@ Fuzzing for various packages in Tendermint using [go-fuzz](https://github.com/dv Inputs: - mempool `CheckTx` (using kvstore in-process ABCI app) -- p2p `Addrbook#AddAddress` -- p2p `pex.Reactor#Receive` - p2p `SecretConnection#Read` and `SecretConnection#Write` - rpc jsonrpc server -## Directory structure - -``` -| test -| |- corpus/ -| |- crashers/ -| |- init-corpus/ -| |- suppressions/ -| |- testdata/ -| |- .go -``` - -`/corpus` directory contains corpus data. The idea is to help the fuzzier to -understand what bytes sequences are semantically valid (e.g. if we're testing -PNG decoder, then we would put black-white PNG into corpus directory; with -blockchain reactor - we would put blockchain messages into corpus). - -`/init-corpus` (if present) contains a script for generating corpus data. - -`/testdata` directory may contain an additional data (like `addrbook.json`). - -Upon running the fuzzier, `/crashers` and `/suppressions` dirs will be created, -along with .zip archive. `/crashers` will show any inputs, which have -lead to panics (plus a trace). `/suppressions` will show any suppressed inputs. - ## Running -```sh -make fuzz-mempool -make fuzz-p2p-addrbook -make fuzz-p2p-pex -make fuzz-p2p-sc -make fuzz-rpc-server -``` - -Each command will create corpus data (if needed), generate a fuzz archive and -call `go-fuzz` executable. - -Then watch out for the respective outputs in the fuzzer output to announce new -crashers which can be found in the directory `crashers`. - -For example if we find +The fuzz tests are in native Go fuzzing format. Use the `go` +tool to run them: ```sh -ls crashers/ -61bde465f47c93254d64d643c3b2480e0a54666e -61bde465f47c93254d64d643c3b2480e0a54666e.output -61bde465f47c93254d64d643c3b2480e0a54666e.quoted -da39a3ee5e6b4b0d3255bfef95601890afd80709 -da39a3ee5e6b4b0d3255bfef95601890afd80709.output -da39a3ee5e6b4b0d3255bfef95601890afd80709.quoted +go test -fuzz Mempool ./tests +go test -fuzz P2PSecretConnection ./tests +go test -fuzz RPCJSONRPCServer ./tests ``` -the crashing bytes generated by the fuzzer will be in -`61bde465f47c93254d64d643c3b2480e0a54666e` the respective crash report in -`61bde465f47c93254d64d643c3b2480e0a54666e.output` - -and the bug report can be created by retrieving the bytes in -`61bde465f47c93254d64d643c3b2480e0a54666e` and feeding those back into the -`Fuzz` function. +See [the Go Fuzzing introduction](https://go.dev/doc/fuzz/) for more information. diff --git a/test/fuzz/mempool/fuzz_test.go b/test/fuzz/mempool/fuzz_test.go deleted file mode 100644 index 69f34db64..000000000 --- a/test/fuzz/mempool/fuzz_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package mempool_test - -import ( - "io" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - - mempool "github.com/tendermint/tendermint/test/fuzz/mempool" -) - -const testdataCasesDir = "testdata/cases" - -func TestMempoolTestdataCases(t *testing.T) { - entries, err := os.ReadDir(testdataCasesDir) - require.NoError(t, err) - - for _, e := range entries { - entry := e - t.Run(entry.Name(), func(t *testing.T) { - defer func() { - r := recover() - require.Nilf(t, r, "testdata/cases test panic") - }() - f, err := os.Open(filepath.Join(testdataCasesDir, entry.Name())) - require.NoError(t, err) - input, err := io.ReadAll(f) - require.NoError(t, err) - mempool.Fuzz(input) - }) - } -} diff --git a/test/fuzz/oss-fuzz-build.sh b/test/fuzz/oss-fuzz-build.sh index 8eb30f510..836253d4d 100755 --- a/test/fuzz/oss-fuzz-build.sh +++ b/test/fuzz/oss-fuzz-build.sh @@ -1,14 +1,22 @@ -#!/bin/bash -eu +#!/bin/bash + +set -euo pipefail export FUZZ_ROOT="github.com/tendermint/tendermint" -(cd test/fuzz/p2p/addrbook; go run ./init-corpus/main.go) -compile_go_fuzzer "$FUZZ_ROOT"/test/fuzz/p2p/addrbook Fuzz fuzz_p2p_addrbook fuzz -(cd test/fuzz/p2p/pex; go run ./init-corpus/main.go) -compile_go_fuzzer "$FUZZ_ROOT"/test/fuzz/p2p/pex Fuzz fuzz_p2p_pex fuzz -(cd test/fuzz/p2p/secret_connection; go run ./init-corpus/main.go) -compile_go_fuzzer "$FUZZ_ROOT"/test/fuzz/p2p/secret_connection Fuzz fuzz_p2p_secret_connection fuzz +build_go_fuzzer() { + local function="$1" + local fuzzer="$2" -compile_go_fuzzer "$FUZZ_ROOT"/test/fuzz/mempool Fuzz fuzz_mempool fuzz + gotip run github.com/orijtech/otils/corpus2ossfuzz@latest -o "$OUT"/"$fuzzer"_seed_corpus.zip -corpus test/fuzz/tests/testdata/fuzz/"$function" + compile_native_go_fuzzer "$FUZZ_ROOT"/test/fuzz/tests "$function" "$fuzzer" +} -compile_go_fuzzer "$FUZZ_ROOT"/test/fuzz/rpc/jsonrpc/server Fuzz fuzz_rpc_jsonrpc_server fuzz +gotip get github.com/AdamKorcz/go-118-fuzz-build/utils +gotip get github.com/prometheus/common/expfmt@v0.32.1 + +build_go_fuzzer FuzzP2PSecretConnection fuzz_p2p_secretconnection + +build_go_fuzzer FuzzMempool fuzz_mempool + +build_go_fuzzer FuzzRPCJSONRPCServer fuzz_rpc_jsonrpc_server diff --git a/test/fuzz/p2p/secretconnection/fuzz_test.go b/test/fuzz/p2p/secretconnection/fuzz_test.go deleted file mode 100644 index 6fe19b03b..000000000 --- a/test/fuzz/p2p/secretconnection/fuzz_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package secretconnection_test - -import ( - "io" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/tendermint/tendermint/test/fuzz/p2p/secretconnection" -) - -const testdataCasesDir = "testdata/cases" - -func TestSecretConnectionTestdataCases(t *testing.T) { - entries, err := os.ReadDir(testdataCasesDir) - require.NoError(t, err) - - for _, e := range entries { - entry := e - t.Run(entry.Name(), func(t *testing.T) { - defer func() { - r := recover() - require.Nilf(t, r, "testdata/cases test panic") - }() - f, err := os.Open(filepath.Join(testdataCasesDir, entry.Name())) - require.NoError(t, err) - input, err := io.ReadAll(f) - require.NoError(t, err) - secretconnection.Fuzz(input) - }) - } -} diff --git a/test/fuzz/p2p/secretconnection/init-corpus/main.go b/test/fuzz/p2p/secretconnection/init-corpus/main.go deleted file mode 100644 index 3a2537ff7..000000000 --- a/test/fuzz/p2p/secretconnection/init-corpus/main.go +++ /dev/null @@ -1,47 +0,0 @@ -// nolint: gosec -package main - -import ( - "flag" - "fmt" - "log" - "os" - "path/filepath" -) - -func main() { - baseDir := flag.String("base", ".", `where the "corpus" directory will live`) - flag.Parse() - - initCorpus(*baseDir) -} - -func initCorpus(baseDir string) { - log.SetFlags(0) - - corpusDir := filepath.Join(baseDir, "corpus") - if err := os.MkdirAll(corpusDir, 0755); err != nil { - log.Fatal(err) - } - - data := []string{ - "dadc04c2-cfb1-4aa9-a92a-c0bf780ec8b6", - "", - " ", - " a ", - `{"a": 12, "tsp": 999, k: "blue"}`, - `9999.999`, - `""`, - `Tendermint fuzzing`, - } - - for i, datum := range data { - filename := filepath.Join(corpusDir, fmt.Sprintf("%d", i)) - - if err := os.WriteFile(filename, []byte(datum), 0644); err != nil { - log.Fatalf("can't write %v to %q: %v", datum, filename, err) - } - - log.Printf("wrote %q", filename) - } -} diff --git a/test/fuzz/rpc/jsonrpc/server/fuzz_test.go b/test/fuzz/rpc/jsonrpc/server/fuzz_test.go deleted file mode 100644 index 8a34da8a6..000000000 --- a/test/fuzz/rpc/jsonrpc/server/fuzz_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package server_test - -import ( - "io" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/tendermint/tendermint/test/fuzz/rpc/jsonrpc/server" -) - -const testdataCasesDir = "testdata/cases" - -func TestServerTestdataCases(t *testing.T) { - entries, err := os.ReadDir(testdataCasesDir) - require.NoError(t, err) - - for _, e := range entries { - entry := e - t.Run(entry.Name(), func(t *testing.T) { - defer func() { - r := recover() - require.Nilf(t, r, "testdata/cases test panic") - }() - f, err := os.Open(filepath.Join(testdataCasesDir, entry.Name())) - require.NoError(t, err) - input, err := io.ReadAll(f) - require.NoError(t, err) - server.Fuzz(input) - }) - } -} diff --git a/test/fuzz/rpc/jsonrpc/server/handler.go b/test/fuzz/rpc/jsonrpc/server/handler.go deleted file mode 100644 index c9203e9f5..000000000 --- a/test/fuzz/rpc/jsonrpc/server/handler.go +++ /dev/null @@ -1,65 +0,0 @@ -package server - -import ( - "bytes" - "context" - "encoding/json" - "io" - "net/http" - "net/http/httptest" - - "github.com/tendermint/tendermint/libs/log" - rs "github.com/tendermint/tendermint/rpc/jsonrpc/server" - "github.com/tendermint/tendermint/rpc/jsonrpc/types" -) - -var rpcFuncMap = map[string]*rs.RPCFunc{ - "c": rs.NewRPCFunc(func(ctx context.Context, s string, i int) (string, error) { - return "foo", nil - }, "s", "i"), -} -var mux *http.ServeMux - -func init() { - mux = http.NewServeMux() - rs.RegisterRPCFuncs(mux, rpcFuncMap, log.NewNopLogger()) -} - -func Fuzz(data []byte) int { - if len(data) == 0 { - return -1 - } - - req, _ := http.NewRequest("POST", "http://localhost/", bytes.NewReader(data)) - rec := httptest.NewRecorder() - mux.ServeHTTP(rec, req) - res := rec.Result() - blob, err := io.ReadAll(res.Body) - if err != nil { - panic(err) - } - if err := res.Body.Close(); err != nil { - panic(err) - } - if len(blob) == 0 { - return 1 - } - - if outputJSONIsSlice(blob) { - recv := []types.RPCResponse{} - if err := json.Unmarshal(blob, &recv); err != nil { - panic(err) - } - return 1 - } - recv := &types.RPCResponse{} - if err := json.Unmarshal(blob, recv); err != nil { - panic(err) - } - return 1 -} - -func outputJSONIsSlice(input []byte) bool { - slice := []interface{}{} - return json.Unmarshal(input, &slice) == nil -} diff --git a/test/fuzz/rpc/jsonrpc/server/testdata/1184f5b8d4b6dd08709cf1513f26744167065e0d b/test/fuzz/rpc/jsonrpc/server/testdata/1184f5b8d4b6dd08709cf1513f26744167065e0d deleted file mode 100644 index 6e7ea636e..000000000 --- a/test/fuzz/rpc/jsonrpc/server/testdata/1184f5b8d4b6dd08709cf1513f26744167065e0d +++ /dev/null @@ -1 +0,0 @@ -[0] \ No newline at end of file diff --git a/test/fuzz/rpc/jsonrpc/server/testdata/cases/1184f5b8d4b6dd08709cf1513f26744167065e0d b/test/fuzz/rpc/jsonrpc/server/testdata/cases/1184f5b8d4b6dd08709cf1513f26744167065e0d deleted file mode 100644 index 6e7ea636e..000000000 --- a/test/fuzz/rpc/jsonrpc/server/testdata/cases/1184f5b8d4b6dd08709cf1513f26744167065e0d +++ /dev/null @@ -1 +0,0 @@ -[0] \ No newline at end of file diff --git a/test/fuzz/rpc/jsonrpc/server/testdata/cases/bbcffb1cdb2cea50fd3dd8c1524905551d0b2e79 b/test/fuzz/rpc/jsonrpc/server/testdata/cases/bbcffb1cdb2cea50fd3dd8c1524905551d0b2e79 deleted file mode 100644 index e0be2aa4b..000000000 --- a/test/fuzz/rpc/jsonrpc/server/testdata/cases/bbcffb1cdb2cea50fd3dd8c1524905551d0b2e79 +++ /dev/null @@ -1 +0,0 @@ -[0,0] \ No newline at end of file diff --git a/test/fuzz/rpc/jsonrpc/server/testdata/cases/clusterfuzz-testcase-minimized-fuzz_rpc_jsonrpc_server-4738572803506176 b/test/fuzz/rpc/jsonrpc/server/testdata/cases/clusterfuzz-testcase-minimized-fuzz_rpc_jsonrpc_server-4738572803506176 deleted file mode 100644 index 0f7836d2f..000000000 --- a/test/fuzz/rpc/jsonrpc/server/testdata/cases/clusterfuzz-testcase-minimized-fuzz_rpc_jsonrpc_server-4738572803506176 +++ /dev/null @@ -1 +0,0 @@ -[{"iD":7},{"iD":7}] \ No newline at end of file diff --git a/test/fuzz/rpc/jsonrpc/server/testdata/clusterfuzz-testcase-minimized-fuzz_rpc_jsonrpc_server-4738572803506176 b/test/fuzz/rpc/jsonrpc/server/testdata/clusterfuzz-testcase-minimized-fuzz_rpc_jsonrpc_server-4738572803506176 deleted file mode 100644 index 0f7836d2f..000000000 --- a/test/fuzz/rpc/jsonrpc/server/testdata/clusterfuzz-testcase-minimized-fuzz_rpc_jsonrpc_server-4738572803506176 +++ /dev/null @@ -1 +0,0 @@ -[{"iD":7},{"iD":7}] \ No newline at end of file diff --git a/test/fuzz/mempool/checktx.go b/test/fuzz/tests/mempool_test.go similarity index 59% rename from test/fuzz/mempool/checktx.go rename to test/fuzz/tests/mempool_test.go index 8be90f0c2..2c8623036 100644 --- a/test/fuzz/mempool/checktx.go +++ b/test/fuzz/tests/mempool_test.go @@ -1,7 +1,10 @@ -package mempool +//go:build gofuzz || go1.18 + +package tests import ( "context" + "testing" abciclient "github.com/tendermint/tendermint/abci/client" "github.com/tendermint/tendermint/abci/example/kvstore" @@ -10,10 +13,7 @@ import ( "github.com/tendermint/tendermint/libs/log" ) -var mp *mempool.TxMempool -var getMp func() mempool.Mempool - -func init() { +func FuzzMempool(f *testing.F) { app := kvstore.NewApplication() logger := log.NewNopLogger() conn := abciclient.NewLocalClient(logger, app) @@ -25,19 +25,9 @@ func init() { cfg := config.DefaultMempoolConfig() cfg.Broadcast = false - getMp = func() mempool.Mempool { - if mp == nil { - mp = mempool.NewTxMempool(logger, cfg, conn) - } - return mp - } -} + mp := mempool.NewTxMempool(logger, cfg, conn) -func Fuzz(data []byte) int { - err := getMp().CheckTx(context.Background(), data, nil, mempool.TxInfo{}) - if err != nil { - return 0 - } - - return 1 + f.Fuzz(func(t *testing.T, data []byte) { + _ = mp.CheckTx(context.Background(), data, nil, mempool.TxInfo{}) + }) } diff --git a/test/fuzz/p2p/secretconnection/read_write.go b/test/fuzz/tests/p2p_secretconnection_test.go similarity index 94% rename from test/fuzz/p2p/secretconnection/read_write.go rename to test/fuzz/tests/p2p_secretconnection_test.go index 87d547e55..65f268a7b 100644 --- a/test/fuzz/p2p/secretconnection/read_write.go +++ b/test/fuzz/tests/p2p_secretconnection_test.go @@ -1,19 +1,28 @@ -package secretconnection +//go:build gofuzz || go1.18 + +package tests import ( "bytes" "fmt" "io" "log" + "testing" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/internal/libs/async" sc "github.com/tendermint/tendermint/internal/p2p/conn" ) -func Fuzz(data []byte) int { +func FuzzP2PSecretConnection(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzz(data) + }) +} + +func fuzz(data []byte) { if len(data) == 0 { - return -1 + return } fooConn, barConn := makeSecretConnPair() @@ -44,14 +53,11 @@ func Fuzz(data []byte) int { } copy(dataRead[totalRead:], buf[:m]) totalRead += m - log.Printf("total read: %d", totalRead) } if !bytes.Equal(data, dataRead) { panic("bytes written != read") } - - return 1 } type kvstoreConn struct { diff --git a/test/fuzz/tests/rpc_jsonrpc_server_test.go b/test/fuzz/tests/rpc_jsonrpc_server_test.go new file mode 100644 index 000000000..67dee9ef2 --- /dev/null +++ b/test/fuzz/tests/rpc_jsonrpc_server_test.go @@ -0,0 +1,72 @@ +//go:build gofuzz || go1.18 + +package tests + +import ( + "bytes" + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/tendermint/tendermint/libs/log" + rpcserver "github.com/tendermint/tendermint/rpc/jsonrpc/server" + "github.com/tendermint/tendermint/rpc/jsonrpc/types" +) + +func FuzzRPCJSONRPCServer(f *testing.F) { + type args struct { + S string `json:"s"` + I int `json:"i"` + } + var rpcFuncMap = map[string]*rpcserver.RPCFunc{ + "c": rpcserver.NewRPCFunc(func(context.Context, *args) (string, error) { + return "foo", nil + }), + } + + mux := http.NewServeMux() + rpcserver.RegisterRPCFuncs(mux, rpcFuncMap, log.NewNopLogger()) + f.Fuzz(func(t *testing.T, data []byte) { + if len(data) == 0 { + return + } + + req, err := http.NewRequest("POST", "http://localhost/", bytes.NewReader(data)) + if err != nil { + panic(err) + } + rec := httptest.NewRecorder() + mux.ServeHTTP(rec, req) + res := rec.Result() + blob, err := io.ReadAll(res.Body) + if err != nil { + panic(err) + } + if err := res.Body.Close(); err != nil { + panic(err) + } + if len(blob) == 0 { + return + } + + if outputJSONIsSlice(blob) { + var recv []types.RPCResponse + if err := json.Unmarshal(blob, &recv); err != nil { + panic(err) + } + return + } + var recv types.RPCResponse + if err := json.Unmarshal(blob, &recv); err != nil { + panic(err) + } + }) +} + +func outputJSONIsSlice(input []byte) bool { + var slice []json.RawMessage + return json.Unmarshal(input, &slice) == nil +} diff --git a/test/fuzz/tests/testdata/fuzz/FuzzMempool/1daffc1033a0bfc7f0c2bccb7440674e67a9e2cc0a4531863076254ada059863 b/test/fuzz/tests/testdata/fuzz/FuzzMempool/1daffc1033a0bfc7f0c2bccb7440674e67a9e2cc0a4531863076254ada059863 new file mode 100644 index 000000000..88467017a --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzMempool/1daffc1033a0bfc7f0c2bccb7440674e67a9e2cc0a4531863076254ada059863 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("S1") diff --git a/test/fuzz/tests/testdata/fuzz/FuzzMempool/582528ddfad69eb57775199a43e0f9fd5c94bba343ce7bb6724d4ebafe311ed4 b/test/fuzz/tests/testdata/fuzz/FuzzMempool/582528ddfad69eb57775199a43e0f9fd5c94bba343ce7bb6724d4ebafe311ed4 new file mode 100644 index 000000000..a96f5599e --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzMempool/582528ddfad69eb57775199a43e0f9fd5c94bba343ce7bb6724d4ebafe311ed4 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("0") diff --git a/test/fuzz/tests/testdata/fuzz/FuzzMempool/d40a98862ed393eb712e47a91bcef18e6f24cf368bb4bd248c7a7101ef8e178d b/test/fuzz/tests/testdata/fuzz/FuzzMempool/d40a98862ed393eb712e47a91bcef18e6f24cf368bb4bd248c7a7101ef8e178d new file mode 100644 index 000000000..e0f2da225 --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzMempool/d40a98862ed393eb712e47a91bcef18e6f24cf368bb4bd248c7a7101ef8e178d @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/0f1a3d10e4d642e42a3ccd9bad652d355431f5824327271aca6f648e8cd4e786 b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/0f1a3d10e4d642e42a3ccd9bad652d355431f5824327271aca6f648e8cd4e786 new file mode 100644 index 000000000..f0b8ea88b --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/0f1a3d10e4d642e42a3ccd9bad652d355431f5824327271aca6f648e8cd4e786 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte(" ") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/172c521d1c5e7a5cce55e39b235928fc1c8c4adbb4635913c204c4724cf47d20 b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/172c521d1c5e7a5cce55e39b235928fc1c8c4adbb4635913c204c4724cf47d20 new file mode 100644 index 000000000..a3668a6db --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/172c521d1c5e7a5cce55e39b235928fc1c8c4adbb4635913c204c4724cf47d20 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("{\"a\": 12, \"tsp\": 999, k: \"blue\"}") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/a9481542b8154bfe8fe868c8907cb66557347cb9b45709b17da861997d7cabea b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/a9481542b8154bfe8fe868c8907cb66557347cb9b45709b17da861997d7cabea new file mode 100644 index 000000000..98241189c --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/a9481542b8154bfe8fe868c8907cb66557347cb9b45709b17da861997d7cabea @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\"\"") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/ba3758980fe724f83bdf1cb97caa73657b4a78d48e5fd6fc3b1590d24799e803 b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/ba3758980fe724f83bdf1cb97caa73657b4a78d48e5fd6fc3b1590d24799e803 new file mode 100644 index 000000000..c479f2604 --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/ba3758980fe724f83bdf1cb97caa73657b4a78d48e5fd6fc3b1590d24799e803 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("9999.999") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/c22ff3cdf5145a03ecc6a2c18a7ec4eb3c9e1384af92cfa14cf50951535b6c85 b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/c22ff3cdf5145a03ecc6a2c18a7ec4eb3c9e1384af92cfa14cf50951535b6c85 new file mode 100644 index 000000000..280f15bf7 --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/c22ff3cdf5145a03ecc6a2c18a7ec4eb3c9e1384af92cfa14cf50951535b6c85 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte(" a ") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/d40a98862ed393eb712e47a91bcef18e6f24cf368bb4bd248c7a7101ef8e178d b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/d40a98862ed393eb712e47a91bcef18e6f24cf368bb4bd248c7a7101ef8e178d new file mode 100644 index 000000000..e0f2da225 --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/d40a98862ed393eb712e47a91bcef18e6f24cf368bb4bd248c7a7101ef8e178d @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/dc7304b2cddeadd08647d30b1d027f749960376c338e14a81e0396ffc6e6d6bd b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/dc7304b2cddeadd08647d30b1d027f749960376c338e14a81e0396ffc6e6d6bd new file mode 100644 index 000000000..017f8d03f --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/dc7304b2cddeadd08647d30b1d027f749960376c338e14a81e0396ffc6e6d6bd @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("Tendermint fuzzing") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/058ae08103537df220789dea46edb8b7cf7368e90da0cb35888a1452f4d114a2 b/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/058ae08103537df220789dea46edb8b7cf7368e90da0cb35888a1452f4d114a2 new file mode 100644 index 000000000..53742f182 --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/058ae08103537df220789dea46edb8b7cf7368e90da0cb35888a1452f4d114a2 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("[{\"iD\":7},{\"iD\":7}]") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/2ab633cb322fca9e76fc965b430076844ebd0b3c4f30f5263b94a3d50f00bce6 b/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/2ab633cb322fca9e76fc965b430076844ebd0b3c4f30f5263b94a3d50f00bce6 new file mode 100644 index 000000000..ef2bd593a --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/2ab633cb322fca9e76fc965b430076844ebd0b3c4f30f5263b94a3d50f00bce6 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("[0,0]") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/aadb440fa55da05c1185e3e64b33c804d994cce06781e8c39481411793a8a73f b/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/aadb440fa55da05c1185e3e64b33c804d994cce06781e8c39481411793a8a73f new file mode 100644 index 000000000..fb9f33963 --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/aadb440fa55da05c1185e3e64b33c804d994cce06781e8c39481411793a8a73f @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("[0]") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/d40a98862ed393eb712e47a91bcef18e6f24cf368bb4bd248c7a7101ef8e178d b/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/d40a98862ed393eb712e47a91bcef18e6f24cf368bb4bd248c7a7101ef8e178d new file mode 100644 index 000000000..e0f2da225 --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/d40a98862ed393eb712e47a91bcef18e6f24cf368bb4bd248c7a7101ef8e178d @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("") \ No newline at end of file diff --git a/third_party/proto/gogoproto/gogo.proto b/third_party/proto/gogoproto/gogo.proto deleted file mode 100644 index 31c516cd0..000000000 --- a/third_party/proto/gogoproto/gogo.proto +++ /dev/null @@ -1,147 +0,0 @@ -// Protocol Buffers for Go with Gadgets -// -// Copied from https://github.com/gogo/protobuf/blob/master/gogoproto/gogo.proto -// -// Copyright (c) 2013, The GoGo Authors. All rights reserved. -// http://github.com/gogo/protobuf -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -syntax = "proto2"; -package gogoproto; - -import "google/protobuf/descriptor.proto"; - -option java_package = "com.google.protobuf"; -option java_outer_classname = "GoGoProtos"; -option go_package = "github.com/gogo/protobuf/gogoproto"; - -extend google.protobuf.EnumOptions { - optional bool goproto_enum_prefix = 62001; - optional bool goproto_enum_stringer = 62021; - optional bool enum_stringer = 62022; - optional string enum_customname = 62023; - optional bool enumdecl = 62024; -} - -extend google.protobuf.EnumValueOptions { - optional string enumvalue_customname = 66001; -} - -extend google.protobuf.FileOptions { - optional bool goproto_getters_all = 63001; - optional bool goproto_enum_prefix_all = 63002; - optional bool goproto_stringer_all = 63003; - optional bool verbose_equal_all = 63004; - optional bool face_all = 63005; - optional bool gostring_all = 63006; - optional bool populate_all = 63007; - optional bool stringer_all = 63008; - optional bool onlyone_all = 63009; - - optional bool equal_all = 63013; - optional bool description_all = 63014; - optional bool testgen_all = 63015; - optional bool benchgen_all = 63016; - optional bool marshaler_all = 63017; - optional bool unmarshaler_all = 63018; - optional bool stable_marshaler_all = 63019; - - optional bool sizer_all = 63020; - - optional bool goproto_enum_stringer_all = 63021; - optional bool enum_stringer_all = 63022; - - optional bool unsafe_marshaler_all = 63023; - optional bool unsafe_unmarshaler_all = 63024; - - optional bool goproto_extensions_map_all = 63025; - optional bool goproto_unrecognized_all = 63026; - optional bool gogoproto_import = 63027; - optional bool protosizer_all = 63028; - optional bool compare_all = 63029; - optional bool typedecl_all = 63030; - optional bool enumdecl_all = 63031; - - optional bool goproto_registration = 63032; - optional bool messagename_all = 63033; - - optional bool goproto_sizecache_all = 63034; - optional bool goproto_unkeyed_all = 63035; -} - -extend google.protobuf.MessageOptions { - optional bool goproto_getters = 64001; - optional bool goproto_stringer = 64003; - optional bool verbose_equal = 64004; - optional bool face = 64005; - optional bool gostring = 64006; - optional bool populate = 64007; - optional bool stringer = 67008; - optional bool onlyone = 64009; - - optional bool equal = 64013; - optional bool description = 64014; - optional bool testgen = 64015; - optional bool benchgen = 64016; - optional bool marshaler = 64017; - optional bool unmarshaler = 64018; - optional bool stable_marshaler = 64019; - - optional bool sizer = 64020; - - optional bool unsafe_marshaler = 64023; - optional bool unsafe_unmarshaler = 64024; - - optional bool goproto_extensions_map = 64025; - optional bool goproto_unrecognized = 64026; - - optional bool protosizer = 64028; - optional bool compare = 64029; - - optional bool typedecl = 64030; - - optional bool messagename = 64033; - - optional bool goproto_sizecache = 64034; - optional bool goproto_unkeyed = 64035; -} - -extend google.protobuf.FieldOptions { - optional bool nullable = 65001; - optional bool embed = 65002; - optional string customtype = 65003; - optional string customname = 65004; - optional string jsontag = 65005; - optional string moretags = 65006; - optional string casttype = 65007; - optional string castkey = 65008; - optional string castvalue = 65009; - - optional bool stdtime = 65010; - optional bool stdduration = 65011; - optional bool wktpointer = 65012; - - optional string castrepeated = 65013; -} diff --git a/types/block.go b/types/block.go index d6e45af6a..17e9812cf 100644 --- a/types/block.go +++ b/types/block.go @@ -13,7 +13,6 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/merkle" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/bits" tmbytes "github.com/tendermint/tendermint/libs/bytes" tmmath "github.com/tendermint/tendermint/libs/math" @@ -603,21 +602,19 @@ const ( // CommitSig is a part of the Vote included in a Commit. type CommitSig struct { - BlockIDFlag BlockIDFlag `json:"block_id_flag"` - ValidatorAddress Address `json:"validator_address"` - Timestamp time.Time `json:"timestamp"` - Signature []byte `json:"signature"` - VoteExtension VoteExtensionToSign `json:"vote_extension"` + BlockIDFlag BlockIDFlag `json:"block_id_flag"` + ValidatorAddress Address `json:"validator_address"` + Timestamp time.Time `json:"timestamp"` + Signature []byte `json:"signature"` } // NewCommitSigForBlock returns new CommitSig with BlockIDFlagCommit. -func NewCommitSigForBlock(signature []byte, valAddr Address, ts time.Time, ext VoteExtensionToSign) CommitSig { +func NewCommitSigForBlock(signature []byte, valAddr Address, ts time.Time) CommitSig { return CommitSig{ BlockIDFlag: BlockIDFlagCommit, ValidatorAddress: valAddr, Timestamp: ts, Signature: signature, - VoteExtension: ext, } } @@ -650,14 +647,12 @@ func (cs CommitSig) Absent() bool { // 1. first 6 bytes of signature // 2. first 6 bytes of validator address // 3. block ID flag -// 4. first 6 bytes of the vote extension -// 5. timestamp +// 4. timestamp func (cs CommitSig) String() string { - return fmt.Sprintf("CommitSig{%X by %X on %v with %X @ %s}", + return fmt.Sprintf("CommitSig{%X by %X on %v @ %s}", tmbytes.Fingerprint(cs.Signature), tmbytes.Fingerprint(cs.ValidatorAddress), cs.BlockIDFlag, - tmbytes.Fingerprint(cs.VoteExtension.BytesPacked()), CanonicalTime(cs.Timestamp)) } @@ -729,7 +724,6 @@ func (cs *CommitSig) ToProto() *tmproto.CommitSig { ValidatorAddress: cs.ValidatorAddress, Timestamp: cs.Timestamp, Signature: cs.Signature, - VoteExtension: cs.VoteExtension.ToProto(), } } @@ -741,7 +735,6 @@ func (cs *CommitSig) FromProto(csp tmproto.CommitSig) error { cs.ValidatorAddress = csp.ValidatorAddress cs.Timestamp = csp.Timestamp cs.Signature = csp.Signature - cs.VoteExtension = VoteExtensionToSignFromProto(csp.VoteExtension) return cs.ValidateBasic() } @@ -786,7 +779,11 @@ func CommitToVoteSet(chainID string, commit *Commit, vals *ValidatorSet) *VoteSe if commitSig.Absent() { continue // OK, some precommits can be missing. } - added, err := voteSet.AddVote(commit.GetVote(int32(idx))) + vote := commit.GetVote(int32(idx)) + if err := vote.ValidateBasic(); err != nil { + panic(fmt.Errorf("failed to validate vote reconstructed from LastCommit: %w", err)) + } + added, err := voteSet.AddVote(vote) if !added || err != nil { panic(fmt.Errorf("failed to reconstruct LastCommit: %w", err)) } @@ -808,7 +805,6 @@ func (commit *Commit) GetVote(valIdx int32) *Vote { ValidatorAddress: commitSig.ValidatorAddress, ValidatorIndex: valIdx, Signature: commitSig.Signature, - VoteExtension: commitSig.VoteExtension.ToVoteExtension(), } } @@ -1129,9 +1125,9 @@ func (blockID BlockID) IsNil() bool { // IsComplete returns true if this is a valid BlockID of a non-nil block. func (blockID BlockID) IsComplete() bool { - return len(blockID.Hash) == tmhash.Size && + return len(blockID.Hash) == crypto.HashSize && blockID.PartSetHeader.Total > 0 && - len(blockID.PartSetHeader.Hash) == tmhash.Size + len(blockID.PartSetHeader.Hash) == crypto.HashSize } // String returns a human readable string representation of the BlockID. diff --git a/types/block_meta_test.go b/types/block_meta_test.go index a1a382ffa..0ce90c40b 100644 --- a/types/block_meta_test.go +++ b/types/block_meta_test.go @@ -5,13 +5,13 @@ import ( "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" tmrand "github.com/tendermint/tendermint/libs/rand" ) func TestBlockMeta_ToProto(t *testing.T) { h := MakeRandHeader() - bi := BlockID{Hash: h.Hash(), PartSetHeader: PartSetHeader{Total: 123, Hash: tmrand.Bytes(tmhash.Size)}} + bi := BlockID{Hash: h.Hash(), PartSetHeader: PartSetHeader{Total: 123, Hash: tmrand.Bytes(crypto.HashSize)}} bm := &BlockMeta{ BlockID: bi, @@ -48,9 +48,9 @@ func TestBlockMeta_ToProto(t *testing.T) { func TestBlockMeta_ValidateBasic(t *testing.T) { h := MakeRandHeader() - bi := BlockID{Hash: h.Hash(), PartSetHeader: PartSetHeader{Total: 123, Hash: tmrand.Bytes(tmhash.Size)}} - bi2 := BlockID{Hash: tmrand.Bytes(tmhash.Size), - PartSetHeader: PartSetHeader{Total: 123, Hash: tmrand.Bytes(tmhash.Size)}} + bi := BlockID{Hash: h.Hash(), PartSetHeader: PartSetHeader{Total: 123, Hash: tmrand.Bytes(crypto.HashSize)}} + bi2 := BlockID{Hash: tmrand.Bytes(crypto.HashSize), + PartSetHeader: PartSetHeader{Total: 123, Hash: tmrand.Bytes(crypto.HashSize)}} bi3 := BlockID{Hash: []byte("incorrect hash"), PartSetHeader: PartSetHeader{Total: 123, Hash: []byte("incorrect hash")}} diff --git a/types/block_test.go b/types/block_test.go index 4ed47dd9d..7f2378505 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -19,7 +19,6 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/merkle" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/bits" "github.com/tendermint/tendermint/libs/bytes" tmrand "github.com/tendermint/tendermint/libs/rand" @@ -213,8 +212,8 @@ func TestBlockString(t *testing.T) { func makeBlockIDRandom() BlockID { var ( - blockHash = make([]byte, tmhash.Size) - partSetHash = make([]byte, tmhash.Size) + blockHash = make([]byte, crypto.HashSize) + partSetHash = make([]byte, crypto.HashSize) ) rand.Read(blockHash) //nolint: errcheck // ignore errcheck for read rand.Read(partSetHash) //nolint: errcheck // ignore errcheck for read @@ -223,8 +222,8 @@ func makeBlockIDRandom() BlockID { func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) BlockID { var ( - h = make([]byte, tmhash.Size) - psH = make([]byte, tmhash.Size) + h = make([]byte, crypto.HashSize) + psH = make([]byte, crypto.HashSize) ) copy(h, hash) copy(psH, partSetHash) @@ -274,7 +273,7 @@ func TestCommit(t *testing.T) { require.NotNil(t, commit.BitArray()) assert.Equal(t, bits.NewBitArray(10).Size(), commit.BitArray().Size()) - assert.Equal(t, voteSet.GetByIndex(0), commit.GetByIndex(0)) + assert.Equal(t, voteWithoutExtension(voteSet.GetByIndex(0)), commit.GetByIndex(0)) assert.True(t, commit.IsCommit()) } @@ -324,10 +323,10 @@ func TestMaxCommitBytes(t *testing.T) { Height: math.MaxInt64, Round: math.MaxInt32, BlockID: BlockID{ - Hash: tmhash.Sum([]byte("blockID_hash")), + Hash: crypto.Checksum([]byte("blockID_hash")), PartSetHeader: PartSetHeader{ Total: math.MaxInt32, - Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), + Hash: crypto.Checksum([]byte("blockID_part_set_header_hash")), }, }, Signatures: []CommitSig{cs}, @@ -359,15 +358,15 @@ func TestHeaderHash(t *testing.T) { ChainID: "chainId", Height: 3, Time: time.Date(2019, 10, 13, 16, 14, 44, 0, time.UTC), - LastBlockID: makeBlockID(make([]byte, tmhash.Size), 6, make([]byte, tmhash.Size)), - LastCommitHash: tmhash.Sum([]byte("last_commit_hash")), - DataHash: tmhash.Sum([]byte("data_hash")), - ValidatorsHash: tmhash.Sum([]byte("validators_hash")), - NextValidatorsHash: tmhash.Sum([]byte("next_validators_hash")), - ConsensusHash: tmhash.Sum([]byte("consensus_hash")), - AppHash: tmhash.Sum([]byte("app_hash")), - LastResultsHash: tmhash.Sum([]byte("last_results_hash")), - EvidenceHash: tmhash.Sum([]byte("evidence_hash")), + LastBlockID: makeBlockID(make([]byte, crypto.HashSize), 6, make([]byte, crypto.HashSize)), + LastCommitHash: crypto.Checksum([]byte("last_commit_hash")), + DataHash: crypto.Checksum([]byte("data_hash")), + ValidatorsHash: crypto.Checksum([]byte("validators_hash")), + NextValidatorsHash: crypto.Checksum([]byte("next_validators_hash")), + ConsensusHash: crypto.Checksum([]byte("consensus_hash")), + AppHash: crypto.Checksum([]byte("app_hash")), + LastResultsHash: crypto.Checksum([]byte("last_results_hash")), + EvidenceHash: crypto.Checksum([]byte("evidence_hash")), ProposerAddress: crypto.AddressHash([]byte("proposer_address")), }, hexBytesFromString(t, "F740121F553B5418C3EFBD343C2DBFE9E007BB67B0D020A0741374BAB65242A4")}, {"nil header yields nil", nil, nil}, @@ -376,15 +375,15 @@ func TestHeaderHash(t *testing.T) { ChainID: "chainId", Height: 3, Time: time.Date(2019, 10, 13, 16, 14, 44, 0, time.UTC), - LastBlockID: makeBlockID(make([]byte, tmhash.Size), 6, make([]byte, tmhash.Size)), - LastCommitHash: tmhash.Sum([]byte("last_commit_hash")), - DataHash: tmhash.Sum([]byte("data_hash")), + LastBlockID: makeBlockID(make([]byte, crypto.HashSize), 6, make([]byte, crypto.HashSize)), + LastCommitHash: crypto.Checksum([]byte("last_commit_hash")), + DataHash: crypto.Checksum([]byte("data_hash")), ValidatorsHash: nil, - NextValidatorsHash: tmhash.Sum([]byte("next_validators_hash")), - ConsensusHash: tmhash.Sum([]byte("consensus_hash")), - AppHash: tmhash.Sum([]byte("app_hash")), - LastResultsHash: tmhash.Sum([]byte("last_results_hash")), - EvidenceHash: tmhash.Sum([]byte("evidence_hash")), + NextValidatorsHash: crypto.Checksum([]byte("next_validators_hash")), + ConsensusHash: crypto.Checksum([]byte("consensus_hash")), + AppHash: crypto.Checksum([]byte("app_hash")), + LastResultsHash: crypto.Checksum([]byte("last_results_hash")), + EvidenceHash: crypto.Checksum([]byte("evidence_hash")), ProposerAddress: crypto.AddressHash([]byte("proposer_address")), }, nil}, } @@ -455,15 +454,15 @@ func TestMaxHeaderBytes(t *testing.T) { ChainID: maxChainID, Height: math.MaxInt64, Time: timestamp, - LastBlockID: makeBlockID(make([]byte, tmhash.Size), math.MaxInt32, make([]byte, tmhash.Size)), - LastCommitHash: tmhash.Sum([]byte("last_commit_hash")), - DataHash: tmhash.Sum([]byte("data_hash")), - ValidatorsHash: tmhash.Sum([]byte("validators_hash")), - NextValidatorsHash: tmhash.Sum([]byte("next_validators_hash")), - ConsensusHash: tmhash.Sum([]byte("consensus_hash")), - AppHash: tmhash.Sum([]byte("app_hash")), - LastResultsHash: tmhash.Sum([]byte("last_results_hash")), - EvidenceHash: tmhash.Sum([]byte("evidence_hash")), + LastBlockID: makeBlockID(make([]byte, crypto.HashSize), math.MaxInt32, make([]byte, crypto.HashSize)), + LastCommitHash: crypto.Checksum([]byte("last_commit_hash")), + DataHash: crypto.Checksum([]byte("data_hash")), + ValidatorsHash: crypto.Checksum([]byte("validators_hash")), + NextValidatorsHash: crypto.Checksum([]byte("next_validators_hash")), + ConsensusHash: crypto.Checksum([]byte("consensus_hash")), + AppHash: crypto.Checksum([]byte("app_hash")), + LastResultsHash: crypto.Checksum([]byte("last_results_hash")), + EvidenceHash: crypto.Checksum([]byte("evidence_hash")), ProposerAddress: crypto.AddressHash([]byte("proposer_address")), } @@ -571,7 +570,7 @@ func TestCommitToVoteSet(t *testing.T) { voteSet2 := CommitToVoteSet(chainID, commit, valSet) for i := int32(0); int(i) < len(vals); i++ { - vote1 := voteSet.GetByIndex(i) + vote1 := voteWithoutExtension(voteSet.GetByIndex(i)) vote2 := voteSet2.GetByIndex(i) vote3 := commit.GetVote(i) @@ -765,7 +764,7 @@ func MakeRandHeader() Header { chainID := "test" t := time.Now() height := mrand.Int63() - randBytes := tmrand.Bytes(tmhash.Size) + randBytes := tmrand.Bytes(crypto.HashSize) randAddress := tmrand.Bytes(crypto.AddressSize) h := Header{ Version: version.Consensus{Block: version.BlockProtocol, App: 1}, @@ -1014,7 +1013,7 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size+1), + Hash: make([]byte, crypto.HashSize+1), }, }, true, "wrong Hash", @@ -1026,9 +1025,9 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size+1), + Hash: make([]byte, crypto.HashSize+1), }, }, }, @@ -1041,12 +1040,12 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, - LastCommitHash: make([]byte, tmhash.Size+1), + LastCommitHash: make([]byte, crypto.HashSize+1), }, true, "wrong LastCommitHash", }, @@ -1057,13 +1056,13 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size+1), + LastCommitHash: make([]byte, crypto.HashSize), + DataHash: make([]byte, crypto.HashSize+1), }, true, "wrong DataHash", }, @@ -1074,14 +1073,14 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size), - EvidenceHash: make([]byte, tmhash.Size+1), + LastCommitHash: make([]byte, crypto.HashSize), + DataHash: make([]byte, crypto.HashSize), + EvidenceHash: make([]byte, crypto.HashSize+1), }, true, "wrong EvidenceHash", }, @@ -1092,14 +1091,14 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size), - EvidenceHash: make([]byte, tmhash.Size), + LastCommitHash: make([]byte, crypto.HashSize), + DataHash: make([]byte, crypto.HashSize), + EvidenceHash: make([]byte, crypto.HashSize), ProposerAddress: make([]byte, crypto.AddressSize+1), }, true, "invalid ProposerAddress length", @@ -1111,16 +1110,16 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size), - EvidenceHash: make([]byte, tmhash.Size), + LastCommitHash: make([]byte, crypto.HashSize), + DataHash: make([]byte, crypto.HashSize), + EvidenceHash: make([]byte, crypto.HashSize), ProposerAddress: make([]byte, crypto.AddressSize), - ValidatorsHash: make([]byte, tmhash.Size+1), + ValidatorsHash: make([]byte, crypto.HashSize+1), }, true, "wrong ValidatorsHash", }, @@ -1131,17 +1130,17 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size), - EvidenceHash: make([]byte, tmhash.Size), + LastCommitHash: make([]byte, crypto.HashSize), + DataHash: make([]byte, crypto.HashSize), + EvidenceHash: make([]byte, crypto.HashSize), ProposerAddress: make([]byte, crypto.AddressSize), - ValidatorsHash: make([]byte, tmhash.Size), - NextValidatorsHash: make([]byte, tmhash.Size+1), + ValidatorsHash: make([]byte, crypto.HashSize), + NextValidatorsHash: make([]byte, crypto.HashSize+1), }, true, "wrong NextValidatorsHash", }, @@ -1152,18 +1151,18 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size), - EvidenceHash: make([]byte, tmhash.Size), + LastCommitHash: make([]byte, crypto.HashSize), + DataHash: make([]byte, crypto.HashSize), + EvidenceHash: make([]byte, crypto.HashSize), ProposerAddress: make([]byte, crypto.AddressSize), - ValidatorsHash: make([]byte, tmhash.Size), - NextValidatorsHash: make([]byte, tmhash.Size), - ConsensusHash: make([]byte, tmhash.Size+1), + ValidatorsHash: make([]byte, crypto.HashSize), + NextValidatorsHash: make([]byte, crypto.HashSize), + ConsensusHash: make([]byte, crypto.HashSize+1), }, true, "wrong ConsensusHash", }, @@ -1174,19 +1173,19 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size), - EvidenceHash: make([]byte, tmhash.Size), + LastCommitHash: make([]byte, crypto.HashSize), + DataHash: make([]byte, crypto.HashSize), + EvidenceHash: make([]byte, crypto.HashSize), ProposerAddress: make([]byte, crypto.AddressSize), - ValidatorsHash: make([]byte, tmhash.Size), - NextValidatorsHash: make([]byte, tmhash.Size), - ConsensusHash: make([]byte, tmhash.Size), - LastResultsHash: make([]byte, tmhash.Size+1), + ValidatorsHash: make([]byte, crypto.HashSize), + NextValidatorsHash: make([]byte, crypto.HashSize), + ConsensusHash: make([]byte, crypto.HashSize), + LastResultsHash: make([]byte, crypto.HashSize+1), }, true, "wrong LastResultsHash", }, @@ -1197,19 +1196,19 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size), - EvidenceHash: make([]byte, tmhash.Size), + LastCommitHash: make([]byte, crypto.HashSize), + DataHash: make([]byte, crypto.HashSize), + EvidenceHash: make([]byte, crypto.HashSize), ProposerAddress: make([]byte, crypto.AddressSize), - ValidatorsHash: make([]byte, tmhash.Size), - NextValidatorsHash: make([]byte, tmhash.Size), - ConsensusHash: make([]byte, tmhash.Size), - LastResultsHash: make([]byte, tmhash.Size), + ValidatorsHash: make([]byte, crypto.HashSize), + NextValidatorsHash: make([]byte, crypto.HashSize), + ConsensusHash: make([]byte, crypto.HashSize), + LastResultsHash: make([]byte, crypto.HashSize), }, false, "", }, @@ -1262,9 +1261,9 @@ func TestCommit_ValidateBasic(t *testing.T) { Height: 1, Round: 1, BlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, }, @@ -1276,9 +1275,9 @@ func TestCommit_ValidateBasic(t *testing.T) { Height: 1, Round: 1, BlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, Signatures: []CommitSig{ @@ -1297,9 +1296,9 @@ func TestCommit_ValidateBasic(t *testing.T) { Height: 1, Round: 1, BlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, Signatures: []CommitSig{ diff --git a/types/canonical.go b/types/canonical.go index 2e2063e36..9a3d995ec 100644 --- a/types/canonical.go +++ b/types/canonical.go @@ -51,26 +51,29 @@ func CanonicalizeProposal(chainID string, proposal *tmproto.Proposal) tmproto.Ca } } -func GetVoteExtensionToSign(ext *tmproto.VoteExtension) *tmproto.VoteExtensionToSign { - if ext == nil { - return nil - } - return &tmproto.VoteExtensionToSign{ - AppDataToSign: ext.AppDataToSign, +// CanonicalizeVote transforms the given Vote to a CanonicalVote, which does +// not contain ValidatorIndex and ValidatorAddress fields, or any fields +// relating to vote extensions. +func CanonicalizeVote(chainID string, vote *tmproto.Vote) tmproto.CanonicalVote { + return tmproto.CanonicalVote{ + Type: vote.Type, + Height: vote.Height, // encoded as sfixed64 + Round: int64(vote.Round), // encoded as sfixed64 + BlockID: CanonicalizeBlockID(vote.BlockID), + Timestamp: vote.Timestamp, + ChainID: chainID, } } -// CanonicalizeVote transforms the given Vote to a CanonicalVote, which does -// not contain ValidatorIndex and ValidatorAddress fields. -func CanonicalizeVote(chainID string, vote *tmproto.Vote) tmproto.CanonicalVote { - return tmproto.CanonicalVote{ - Type: vote.Type, - Height: vote.Height, // encoded as sfixed64 - Round: int64(vote.Round), // encoded as sfixed64 - BlockID: CanonicalizeBlockID(vote.BlockID), - Timestamp: vote.Timestamp, - ChainID: chainID, - VoteExtension: GetVoteExtensionToSign(vote.VoteExtension), +// CanonicalizeVoteExtension extracts the vote extension from the given vote +// and constructs a CanonicalizeVoteExtension struct, whose representation in +// bytes is what is signed in order to produce the vote extension's signature. +func CanonicalizeVoteExtension(chainID string, vote *tmproto.Vote) tmproto.CanonicalVoteExtension { + return tmproto.CanonicalVoteExtension{ + Extension: vote.Extension, + Height: vote.Height, + Round: int64(vote.Round), + ChainId: chainID, } } diff --git a/types/canonical_test.go b/types/canonical_test.go index 53a8ea52f..2ccb80ff7 100644 --- a/types/canonical_test.go +++ b/types/canonical_test.go @@ -4,13 +4,13 @@ import ( "reflect" "testing" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" tmrand "github.com/tendermint/tendermint/libs/rand" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) func TestCanonicalizeBlockID(t *testing.T) { - randhash := tmrand.Bytes(tmhash.Size) + randhash := tmrand.Bytes(crypto.HashSize) block1 := tmproto.BlockID{Hash: randhash, PartSetHeader: tmproto.PartSetHeader{Total: 5, Hash: randhash}} block2 := tmproto.BlockID{Hash: randhash, diff --git a/types/events.go b/types/events.go index cd535e71b..d87b74cb8 100644 --- a/types/events.go +++ b/types/events.go @@ -1,7 +1,6 @@ package types import ( - "context" "fmt" "strings" @@ -308,15 +307,15 @@ func QueryForEvent(eventValue string) *tmquery.Query { // BlockEventPublisher publishes all block related events type BlockEventPublisher interface { - PublishEventNewBlock(ctx context.Context, block EventDataNewBlock) error - PublishEventNewBlockHeader(ctx context.Context, header EventDataNewBlockHeader) error - PublishEventNewEvidence(ctx context.Context, evidence EventDataNewEvidence) error - PublishEventTx(context.Context, EventDataTx) error - PublishEventValidatorSetUpdates(context.Context, EventDataValidatorSetUpdates) error + PublishEventNewBlock(EventDataNewBlock) error + PublishEventNewBlockHeader(EventDataNewBlockHeader) error + PublishEventNewEvidence(EventDataNewEvidence) error + PublishEventTx(EventDataTx) error + PublishEventValidatorSetUpdates(EventDataValidatorSetUpdates) error } type TxEventPublisher interface { - PublishEventTx(context.Context, EventDataTx) error + PublishEventTx(EventDataTx) error } // eventWithAttr constructs a single abci.Event with a single attribute. diff --git a/types/evidence.go b/types/evidence.go index 1ea53d9b5..aed954a93 100644 --- a/types/evidence.go +++ b/types/evidence.go @@ -12,8 +12,8 @@ import ( "time" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/merkle" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/jsontypes" tmmath "github.com/tendermint/tendermint/libs/math" tmrand "github.com/tendermint/tendermint/libs/rand" @@ -23,13 +23,13 @@ import ( // Evidence represents any provable malicious activity by a validator. // Verification logic for each evidence is part of the evidence module. type Evidence interface { - ABCI() []abci.Evidence // forms individual evidence to be sent to the application - Bytes() []byte // bytes which comprise the evidence - Hash() []byte // hash of the evidence - Height() int64 // height of the infraction - String() string // string format of the evidence - Time() time.Time // time of the infraction - ValidateBasic() error // basic consistency check + ABCI() []abci.Misbehavior // forms individual evidence to be sent to the application + Bytes() []byte // bytes which comprise the evidence + Hash() []byte // hash of the evidence + Height() int64 // height of the infraction + String() string // string format of the evidence + Time() time.Time // time of the infraction + ValidateBasic() error // basic consistency check // Implementations must support tagged encoding in JSON. jsontypes.Tagged @@ -87,9 +87,9 @@ func NewDuplicateVoteEvidence(vote1, vote2 *Vote, blockTime time.Time, valSet *V } // ABCI returns the application relevant representation of the evidence -func (dve *DuplicateVoteEvidence) ABCI() []abci.Evidence { - return []abci.Evidence{{ - Type: abci.EvidenceType_DUPLICATE_VOTE, +func (dve *DuplicateVoteEvidence) ABCI() []abci.Misbehavior { + return []abci.Misbehavior{{ + Type: abci.MisbehaviorType_DUPLICATE_VOTE, Validator: abci.Validator{ Address: dve.VoteA.ValidatorAddress, Power: dve.ValidatorPower, @@ -113,7 +113,7 @@ func (dve *DuplicateVoteEvidence) Bytes() []byte { // Hash returns the hash of the evidence. func (dve *DuplicateVoteEvidence) Hash() []byte { - return tmhash.Sum(dve.Bytes()) + return crypto.Checksum(dve.Bytes()) } // Height returns the height of the infraction @@ -211,14 +211,28 @@ func DuplicateVoteEvidenceFromProto(pb *tmproto.DuplicateVoteEvidence) (*Duplica return nil, errors.New("nil duplicate vote evidence") } - vA, err := VoteFromProto(pb.VoteA) - if err != nil { - return nil, err + var vA *Vote + if pb.VoteA != nil { + var err error + vA, err = VoteFromProto(pb.VoteA) + if err != nil { + return nil, err + } + if err = vA.ValidateBasic(); err != nil { + return nil, err + } } - vB, err := VoteFromProto(pb.VoteB) - if err != nil { - return nil, err + var vB *Vote + if pb.VoteB != nil { + var err error + vB, err = VoteFromProto(pb.VoteB) + if err != nil { + return nil, err + } + if err = vB.ValidateBasic(); err != nil { + return nil, err + } } dve := &DuplicateVoteEvidence{ @@ -257,12 +271,12 @@ func (*LightClientAttackEvidence) TypeTag() string { return "tendermint/LightCli var _ Evidence = &LightClientAttackEvidence{} -// ABCI forms an array of abci evidence for each byzantine validator -func (l *LightClientAttackEvidence) ABCI() []abci.Evidence { - abciEv := make([]abci.Evidence, len(l.ByzantineValidators)) +// ABCI forms an array of abci.Misbehavior for each byzantine validator +func (l *LightClientAttackEvidence) ABCI() []abci.Misbehavior { + abciEv := make([]abci.Misbehavior, len(l.ByzantineValidators)) for idx, val := range l.ByzantineValidators { - abciEv[idx] = abci.Evidence{ - Type: abci.EvidenceType_LIGHT_CLIENT_ATTACK, + abciEv[idx] = abci.Misbehavior{ + Type: abci.MisbehaviorType_LIGHT_CLIENT_ATTACK, Validator: TM2PB.Validator(val), Height: l.Height(), Time: l.Timestamp, @@ -360,10 +374,10 @@ func (l *LightClientAttackEvidence) ConflictingHeaderIsInvalid(trustedHeader *He func (l *LightClientAttackEvidence) Hash() []byte { buf := make([]byte, binary.MaxVarintLen64) n := binary.PutVarint(buf, l.CommonHeight) - bz := make([]byte, tmhash.Size+n) - copy(bz[:tmhash.Size-1], l.ConflictingBlock.Hash().Bytes()) - copy(bz[tmhash.Size:], buf) - return tmhash.Sum(bz) + bz := make([]byte, crypto.HashSize+n) + copy(bz[:crypto.HashSize-1], l.ConflictingBlock.Hash().Bytes()) + copy(bz[crypto.HashSize:], buf) + return crypto.Checksum(bz) } // Height returns the last height at which the primary provider and witness provider had the same header. @@ -683,8 +697,8 @@ func (evl EvidenceList) Has(evidence Evidence) bool { // ToABCI converts the evidence list to a slice of the ABCI protobuf messages // for use when communicating the evidence to an application. -func (evl EvidenceList) ToABCI() []abci.Evidence { - var el []abci.Evidence +func (evl EvidenceList) ToABCI() []abci.Misbehavior { + var el []abci.Misbehavior for _, e := range evl { el = append(el, e.ABCI()...) } @@ -829,10 +843,10 @@ func makeMockVote(height int64, round, index int32, addr Address, func randBlockID() BlockID { return BlockID{ - Hash: tmrand.Bytes(tmhash.Size), + Hash: tmrand.Bytes(crypto.HashSize), PartSetHeader: PartSetHeader{ Total: 1, - Hash: tmrand.Bytes(tmhash.Size), + Hash: tmrand.Bytes(crypto.HashSize), }, } } diff --git a/types/evidence_test.go b/types/evidence_test.go index dd3b54d08..27e346343 100644 --- a/types/evidence_test.go +++ b/types/evidence_test.go @@ -13,7 +13,6 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/tmhash" tmrand "github.com/tendermint/tendermint/libs/rand" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/version" @@ -93,15 +92,15 @@ func TestDuplicateVoteEvidence(t *testing.T) { ev, err := NewMockDuplicateVoteEvidence(ctx, height, time.Now(), "mock-chain-id") require.NoError(t, err) - assert.Equal(t, ev.Hash(), tmhash.Sum(ev.Bytes())) + assert.Equal(t, ev.Hash(), crypto.Checksum(ev.Bytes())) assert.NotNil(t, ev.String()) assert.Equal(t, ev.Height(), height) } func TestDuplicateVoteEvidenceValidation(t *testing.T) { val := NewMockPV() - blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) - blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) + blockID := makeBlockID(crypto.Checksum([]byte("blockhash")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) + blockID2 := makeBlockID(crypto.Checksum([]byte("blockhash2")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) const chainID = "mychain" ctx, cancel := context.WithCancel(context.Background()) @@ -153,7 +152,7 @@ func TestLightClientAttackEvidenceBasic(t *testing.T) { header := makeHeaderRandom() header.Height = height - blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) + blockID := makeBlockID(crypto.Checksum([]byte("blockhash")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) commit, err := makeCommit(ctx, blockID, height, 1, voteSet, privVals, defaultVoteTime) require.NoError(t, err) lcae := &LightClientAttackEvidence{ @@ -217,7 +216,7 @@ func TestLightClientAttackEvidenceValidation(t *testing.T) { header := makeHeaderRandom() header.Height = height header.ValidatorsHash = valSet.Hash() - blockID := makeBlockID(header.Hash(), math.MaxInt32, tmhash.Sum([]byte("partshash"))) + blockID := makeBlockID(header.Hash(), math.MaxInt32, crypto.Checksum([]byte("partshash"))) commit, err := makeCommit(ctx, blockID, height, 1, voteSet, privVals, time.Now()) require.NoError(t, err) lcae := &LightClientAttackEvidence{ @@ -295,7 +294,14 @@ func TestMockEvidenceValidateBasic(t *testing.T) { func makeVote( ctx context.Context, - t *testing.T, val PrivValidator, chainID string, valIndex int32, height int64, round int32, step int, blockID BlockID, + t *testing.T, + val PrivValidator, + chainID string, + valIndex int32, + height int64, + round int32, + step int, + blockID BlockID, time time.Time, ) *Vote { pubKey, err := val.GetPubKey(ctx) @@ -325,14 +331,14 @@ func makeHeaderRandom() *Header { Height: int64(mrand.Uint32() + 1), Time: time.Now(), LastBlockID: makeBlockIDRandom(), - LastCommitHash: crypto.CRandBytes(tmhash.Size), - DataHash: crypto.CRandBytes(tmhash.Size), - ValidatorsHash: crypto.CRandBytes(tmhash.Size), - NextValidatorsHash: crypto.CRandBytes(tmhash.Size), - ConsensusHash: crypto.CRandBytes(tmhash.Size), - AppHash: crypto.CRandBytes(tmhash.Size), - LastResultsHash: crypto.CRandBytes(tmhash.Size), - EvidenceHash: crypto.CRandBytes(tmhash.Size), + LastCommitHash: crypto.CRandBytes(crypto.HashSize), + DataHash: crypto.CRandBytes(crypto.HashSize), + ValidatorsHash: crypto.CRandBytes(crypto.HashSize), + NextValidatorsHash: crypto.CRandBytes(crypto.HashSize), + ConsensusHash: crypto.CRandBytes(crypto.HashSize), + AppHash: crypto.CRandBytes(crypto.HashSize), + LastResultsHash: crypto.CRandBytes(crypto.HashSize), + EvidenceHash: crypto.CRandBytes(crypto.HashSize), ProposerAddress: crypto.CRandBytes(crypto.AddressSize), } } @@ -343,8 +349,8 @@ func TestEvidenceProto(t *testing.T) { // -------- Votes -------- val := NewMockPV() - blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) - blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) + blockID := makeBlockID(crypto.Checksum([]byte("blockhash")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) + blockID2 := makeBlockID(crypto.Checksum([]byte("blockhash2")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) const chainID = "mychain" v := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, 1, 0x01, blockID, defaultVoteTime) v2 := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, 2, 0x01, blockID2, defaultVoteTime) @@ -388,8 +394,8 @@ func TestEvidenceVectors(t *testing.T) { // Votes for duplicateEvidence val := NewMockPV() val.PrivKey = ed25519.GenPrivKeyFromSecret([]byte("it's a secret")) // deterministic key - blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) - blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) + blockID := makeBlockID(crypto.Checksum([]byte("blockhash")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) + blockID2 := makeBlockID(crypto.Checksum([]byte("blockhash2")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) const chainID = "mychain" v := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, 1, 0x01, blockID, defaultVoteTime) v2 := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, 2, 0x01, blockID2, defaultVoteTime) @@ -417,7 +423,7 @@ func TestEvidenceVectors(t *testing.T) { EvidenceHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), ProposerAddress: []byte("2915b7b15f979e48ebc61774bb1d86ba3136b7eb"), } - blockID3 := makeBlockID(header.Hash(), math.MaxInt32, tmhash.Sum([]byte("partshash"))) + blockID3 := makeBlockID(header.Hash(), math.MaxInt32, crypto.Checksum([]byte("partshash"))) commit, err := makeCommit(ctx, blockID3, height, 1, voteSet, privVals, defaultVoteTime) require.NoError(t, err) lcae := &LightClientAttackEvidence{ diff --git a/types/genesis_test.go b/types/genesis_test.go index 99227ad3b..722358111 100644 --- a/types/genesis_test.go +++ b/types/genesis_test.go @@ -72,6 +72,14 @@ func TestBasicGenesisDoc(t *testing.T) { "app_state":{"account_owner": "Bob"}, "consensus_params": { "synchrony": {"precision": "1", "message_delay": "10"}, + "timeout": { + "propose": "30000000000", + "propose_delta": "50000000", + "vote": "30000000000", + "vote_delta": "50000000", + "commit": "10000000000", + "bypass_commit_timeout": false + }, "validator": {"pub_key_types":["ed25519"]}, "block": {"max_bytes": "100"}, "evidence": {"max_age_num_blocks": "100", "max_age_duration": "10"} diff --git a/types/keys.go b/types/keys.go deleted file mode 100644 index 941e82b65..000000000 --- a/types/keys.go +++ /dev/null @@ -1,6 +0,0 @@ -package types - -// UNSTABLE -var ( - PeerStateKey = "ConsensusReactor.peerState" -) diff --git a/types/params.go b/types/params.go index fc9c4aaad..e8ee6fcdf 100644 --- a/types/params.go +++ b/types/params.go @@ -1,6 +1,7 @@ package types import ( + "crypto/sha256" "errors" "fmt" "time" @@ -8,7 +9,6 @@ import ( "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/secp256k1" "github.com/tendermint/tendermint/crypto/sr25519" - "github.com/tendermint/tendermint/crypto/tmhash" tmstrings "github.com/tendermint/tendermint/libs/strings" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) @@ -42,6 +42,7 @@ type ConsensusParams struct { Validator ValidatorParams `json:"validator"` Version VersionParams `json:"version"` Synchrony SynchronyParams `json:"synchrony"` + Timeout TimeoutParams `json:"timeout"` } // HashedParams is a subset of ConsensusParams. @@ -85,6 +86,16 @@ type SynchronyParams struct { MessageDelay time.Duration `json:"message_delay,string"` } +// TimeoutParams configure the timings of the steps of the Tendermint consensus algorithm. +type TimeoutParams struct { + Propose time.Duration `json:"propose,string"` + ProposeDelta time.Duration `json:"propose_delta,string"` + Vote time.Duration `json:"vote,string"` + VoteDelta time.Duration `json:"vote_delta,string"` + Commit time.Duration `json:"commit,string"` + BypassCommitTimeout bool `json:"bypass_commit_timeout"` +} + // DefaultConsensusParams returns a default ConsensusParams. func DefaultConsensusParams() *ConsensusParams { return &ConsensusParams{ @@ -93,6 +104,7 @@ func DefaultConsensusParams() *ConsensusParams { Validator: DefaultValidatorParams(), Version: DefaultVersionParams(), Synchrony: DefaultSynchronyParams(), + Timeout: DefaultTimeoutParams(), } } @@ -137,6 +149,79 @@ func DefaultSynchronyParams() SynchronyParams { } } +// SynchronyParamsOrDefaults returns the SynchronyParams, filling in any zero values +// with the Tendermint defined default values. +func (s SynchronyParams) SynchronyParamsOrDefaults() SynchronyParams { + // TODO: Remove this method and all uses once development on v0.37 begins. + // See: https://github.com/tendermint/tendermint/issues/8187 + + defaults := DefaultSynchronyParams() + if s.Precision == 0 { + s.Precision = defaults.Precision + } + if s.MessageDelay == 0 { + s.MessageDelay = defaults.MessageDelay + } + return s +} + +func DefaultTimeoutParams() TimeoutParams { + return TimeoutParams{ + Propose: 3000 * time.Millisecond, + ProposeDelta: 500 * time.Millisecond, + Vote: 1000 * time.Millisecond, + VoteDelta: 500 * time.Millisecond, + Commit: 1000 * time.Millisecond, + BypassCommitTimeout: false, + } +} + +// TimeoutParamsOrDefaults returns the SynchronyParams, filling in any zero values +// with the Tendermint defined default values. +func (t TimeoutParams) TimeoutParamsOrDefaults() TimeoutParams { + // TODO: Remove this method and all uses once development on v0.37 begins. + // See: https://github.com/tendermint/tendermint/issues/8187 + + defaults := DefaultTimeoutParams() + if t.Propose == 0 { + t.Propose = defaults.Propose + } + if t.ProposeDelta == 0 { + t.ProposeDelta = defaults.ProposeDelta + } + if t.Vote == 0 { + t.Vote = defaults.Vote + } + if t.VoteDelta == 0 { + t.VoteDelta = defaults.VoteDelta + } + if t.Commit == 0 { + t.Commit = defaults.Commit + } + return t +} + +// ProposeTimeout returns the amount of time to wait for a proposal. +func (t TimeoutParams) ProposeTimeout(round int32) time.Duration { + return time.Duration( + t.Propose.Nanoseconds()+t.ProposeDelta.Nanoseconds()*int64(round), + ) * time.Nanosecond +} + +// VoteTimeout returns the amount of time to wait for remaining votes after receiving any +2/3 votes. +func (t TimeoutParams) VoteTimeout(round int32) time.Duration { + return time.Duration( + t.Vote.Nanoseconds()+t.VoteDelta.Nanoseconds()*int64(round), + ) * time.Nanosecond +} + +// CommitTime accepts ti, the time at which the consensus engine received +2/3 +// precommits for a block and returns the point in time at which the consensus +// engine should begin consensus on the next block. +func (t TimeoutParams) CommitTime(ti time.Time) time.Time { + return ti.Add(t.Commit) +} + func (val *ValidatorParams) IsValidPubkeyType(pubkeyType string) bool { for i := 0; i < len(val.PubKeyTypes); i++ { if val.PubKeyTypes[i] == pubkeyType { @@ -150,6 +235,9 @@ func (params *ConsensusParams) Complete() { if params.Synchrony == (SynchronyParams{}) { params.Synchrony = DefaultSynchronyParams() } + if params.Timeout == (TimeoutParams{}) { + params.Timeout = DefaultTimeoutParams() + } } // Validate validates the ConsensusParams to ensure all values are within their @@ -199,6 +287,26 @@ func (params ConsensusParams) ValidateConsensusParams() error { params.Synchrony.Precision) } + if params.Timeout.Propose <= 0 { + return fmt.Errorf("timeout.ProposeDelta must be greater than 0. Got: %d", params.Timeout.Propose) + } + + if params.Timeout.ProposeDelta <= 0 { + return fmt.Errorf("timeout.ProposeDelta must be greater than 0. Got: %d", params.Timeout.ProposeDelta) + } + + if params.Timeout.Vote <= 0 { + return fmt.Errorf("timeout.Vote must be greater than 0. Got: %d", params.Timeout.Vote) + } + + if params.Timeout.VoteDelta <= 0 { + return fmt.Errorf("timeout.VoteDelta must be greater than 0. Got: %d", params.Timeout.VoteDelta) + } + + if params.Timeout.Commit <= 0 { + return fmt.Errorf("timeout.Commit must be greater than 0. Got: %d", params.Timeout.Commit) + } + if len(params.Validator.PubKeyTypes) == 0 { return errors.New("len(Validator.PubKeyTypes) must be greater than 0") } @@ -220,8 +328,6 @@ func (params ConsensusParams) ValidateConsensusParams() error { // This allows the ConsensusParams to evolve more without breaking the block // protocol. No need for a Merkle tree here, just a small struct to hash. func (params ConsensusParams) HashConsensusParams() []byte { - hasher := tmhash.New() - hp := tmproto.HashedParams{ BlockMaxBytes: params.Block.MaxBytes, BlockMaxGas: params.Block.MaxGas, @@ -232,11 +338,9 @@ func (params ConsensusParams) HashConsensusParams() []byte { panic(err) } - _, err = hasher.Write(bz) - if err != nil { - panic(err) - } - return hasher.Sum(nil) + sum := sha256.Sum256(bz) + + return sum[:] } func (params *ConsensusParams) Equals(params2 *ConsensusParams) bool { @@ -244,6 +348,7 @@ func (params *ConsensusParams) Equals(params2 *ConsensusParams) bool { params.Evidence == params2.Evidence && params.Version == params2.Version && params.Synchrony == params2.Synchrony && + params.Timeout == params2.Timeout && tmstrings.StringSliceEqual(params.Validator.PubKeyTypes, params2.Validator.PubKeyTypes) } @@ -275,8 +380,30 @@ func (params ConsensusParams) UpdateConsensusParams(params2 *tmproto.ConsensusPa res.Version.AppVersion = params2.Version.AppVersion } if params2.Synchrony != nil { - res.Synchrony.Precision = params2.Synchrony.Precision - res.Synchrony.MessageDelay = params2.Synchrony.MessageDelay + if params2.Synchrony.MessageDelay != nil { + res.Synchrony.MessageDelay = *params2.Synchrony.GetMessageDelay() + } + if params2.Synchrony.Precision != nil { + res.Synchrony.Precision = *params2.Synchrony.GetPrecision() + } + } + if params2.Timeout != nil { + if params2.Timeout.Propose != nil { + res.Timeout.Propose = *params2.Timeout.GetPropose() + } + if params2.Timeout.ProposeDelta != nil { + res.Timeout.ProposeDelta = *params2.Timeout.GetProposeDelta() + } + if params2.Timeout.Vote != nil { + res.Timeout.Vote = *params2.Timeout.GetVote() + } + if params2.Timeout.VoteDelta != nil { + res.Timeout.VoteDelta = *params2.Timeout.GetVoteDelta() + } + if params2.Timeout.Commit != nil { + res.Timeout.Commit = *params2.Timeout.GetCommit() + } + res.Timeout.BypassCommitTimeout = params2.Timeout.GetBypassCommitTimeout() } return res } @@ -299,14 +426,22 @@ func (params *ConsensusParams) ToProto() tmproto.ConsensusParams { AppVersion: params.Version.AppVersion, }, Synchrony: &tmproto.SynchronyParams{ - MessageDelay: params.Synchrony.MessageDelay, - Precision: params.Synchrony.Precision, + MessageDelay: ¶ms.Synchrony.MessageDelay, + Precision: ¶ms.Synchrony.Precision, + }, + Timeout: &tmproto.TimeoutParams{ + Propose: ¶ms.Timeout.Propose, + ProposeDelta: ¶ms.Timeout.ProposeDelta, + Vote: ¶ms.Timeout.Vote, + VoteDelta: ¶ms.Timeout.VoteDelta, + Commit: ¶ms.Timeout.Commit, + BypassCommitTimeout: params.Timeout.BypassCommitTimeout, }, } } func ConsensusParamsFromProto(pbParams tmproto.ConsensusParams) ConsensusParams { - return ConsensusParams{ + c := ConsensusParams{ Block: BlockParams{ MaxBytes: pbParams.Block.MaxBytes, MaxGas: pbParams.Block.MaxGas, @@ -322,9 +457,32 @@ func ConsensusParamsFromProto(pbParams tmproto.ConsensusParams) ConsensusParams Version: VersionParams{ AppVersion: pbParams.Version.AppVersion, }, - Synchrony: SynchronyParams{ - MessageDelay: pbParams.Synchrony.MessageDelay, - Precision: pbParams.Synchrony.Precision, - }, } + if pbParams.Synchrony != nil { + if pbParams.Synchrony.MessageDelay != nil { + c.Synchrony.MessageDelay = *pbParams.Synchrony.GetMessageDelay() + } + if pbParams.Synchrony.Precision != nil { + c.Synchrony.Precision = *pbParams.Synchrony.GetPrecision() + } + } + if pbParams.Timeout != nil { + if pbParams.Timeout.Propose != nil { + c.Timeout.Propose = *pbParams.Timeout.GetPropose() + } + if pbParams.Timeout.ProposeDelta != nil { + c.Timeout.ProposeDelta = *pbParams.Timeout.GetProposeDelta() + } + if pbParams.Timeout.Vote != nil { + c.Timeout.Vote = *pbParams.Timeout.GetVote() + } + if pbParams.Timeout.VoteDelta != nil { + c.Timeout.VoteDelta = *pbParams.Timeout.GetVoteDelta() + } + if pbParams.Timeout.Commit != nil { + c.Timeout.Commit = *pbParams.Timeout.GetCommit() + } + c.Timeout.BypassCommitTimeout = pbParams.Timeout.BypassCommitTimeout + } + return c } diff --git a/types/params_test.go b/types/params_test.go index 0aaf1e2b9..f19ed001b 100644 --- a/types/params_test.go +++ b/types/params_test.go @@ -19,11 +19,13 @@ var ( func TestConsensusParamsValidation(t *testing.T) { testCases := []struct { + name string params ConsensusParams valid bool }{ // test block params { + name: "block params valid", params: makeParams(makeParamsArgs{ blockBytes: 1, evidenceAge: 2, @@ -32,6 +34,7 @@ func TestConsensusParamsValidation(t *testing.T) { valid: true, }, { + name: "block params invalid MaxBytes", params: makeParams(makeParamsArgs{ blockBytes: 0, evidenceAge: 2, @@ -40,6 +43,7 @@ func TestConsensusParamsValidation(t *testing.T) { valid: false, }, { + name: "block params large MaxBytes", params: makeParams(makeParamsArgs{ blockBytes: 47 * 1024 * 1024, evidenceAge: 2, @@ -48,6 +52,7 @@ func TestConsensusParamsValidation(t *testing.T) { valid: true, }, { + name: "block params small MaxBytes", params: makeParams(makeParamsArgs{ blockBytes: 10, evidenceAge: 2, @@ -56,6 +61,7 @@ func TestConsensusParamsValidation(t *testing.T) { valid: true, }, { + name: "block params 100MB MaxBytes", params: makeParams(makeParamsArgs{ blockBytes: 100 * 1024 * 1024, evidenceAge: 2, @@ -64,6 +70,7 @@ func TestConsensusParamsValidation(t *testing.T) { valid: true, }, { + name: "block params MaxBytes too large", params: makeParams(makeParamsArgs{ blockBytes: 101 * 1024 * 1024, evidenceAge: 2, @@ -72,14 +79,7 @@ func TestConsensusParamsValidation(t *testing.T) { valid: false, }, { - params: makeParams(makeParamsArgs{ - blockBytes: 1024 * 1024 * 1024, - evidenceAge: 2, - precision: 1, - messageDelay: 1}), - valid: false, - }, - { + name: "block params 1GB MaxBytes", params: makeParams(makeParamsArgs{ blockBytes: 1024 * 1024 * 1024, evidenceAge: 2, @@ -89,6 +89,7 @@ func TestConsensusParamsValidation(t *testing.T) { }, // test evidence params { + name: "evidence MaxAge and MaxBytes 0", params: makeParams(makeParamsArgs{ blockBytes: 1, evidenceAge: 0, @@ -98,6 +99,7 @@ func TestConsensusParamsValidation(t *testing.T) { valid: false, }, { + name: "evidence MaxBytes greater than Block.MaxBytes", params: makeParams(makeParamsArgs{ blockBytes: 1, evidenceAge: 2, @@ -107,6 +109,7 @@ func TestConsensusParamsValidation(t *testing.T) { valid: false, }, { + name: "evidence size below Block.MaxBytes", params: makeParams(makeParamsArgs{ blockBytes: 1000, evidenceAge: 2, @@ -116,6 +119,7 @@ func TestConsensusParamsValidation(t *testing.T) { valid: true, }, { + name: "evidence MaxAgeDuration < 0", params: makeParams(makeParamsArgs{ blockBytes: 1, evidenceAge: -1, @@ -124,8 +128,8 @@ func TestConsensusParamsValidation(t *testing.T) { messageDelay: 1}), valid: false, }, - // test no pubkey type provided { + name: "no pubkey types", params: makeParams(makeParamsArgs{ evidenceAge: 2, pubkeyTypes: []string{}, @@ -133,8 +137,8 @@ func TestConsensusParamsValidation(t *testing.T) { messageDelay: 1}), valid: false, }, - // test invalid pubkey type provided { + name: "invalid pubkey types", params: makeParams(makeParamsArgs{ evidenceAge: 2, pubkeyTypes: []string{"potatoes make good pubkeys"}, @@ -142,8 +146,8 @@ func TestConsensusParamsValidation(t *testing.T) { messageDelay: 1}), valid: false, }, - // test invalid pubkey type provided { + name: "negative MessageDelay", params: makeParams(makeParamsArgs{ evidenceAge: 2, precision: 1, @@ -151,6 +155,7 @@ func TestConsensusParamsValidation(t *testing.T) { valid: false, }, { + name: "negative Precision", params: makeParams(makeParamsArgs{ evidenceAge: 2, precision: -1, @@ -159,28 +164,52 @@ func TestConsensusParamsValidation(t *testing.T) { }, } for i, tc := range testCases { - if tc.valid { - assert.NoErrorf(t, tc.params.ValidateConsensusParams(), "expected no error for valid params (#%d)", i) - } else { - assert.Errorf(t, tc.params.ValidateConsensusParams(), "expected error for non valid params (#%d)", i) - } + t.Run(tc.name, func(t *testing.T) { + if tc.valid { + assert.NoErrorf(t, tc.params.ValidateConsensusParams(), "expected no error for valid params (#%d)", i) + } else { + assert.Errorf(t, tc.params.ValidateConsensusParams(), "expected error for non valid params (#%d)", i) + } + }) } } type makeParamsArgs struct { - blockBytes int64 - blockGas int64 - evidenceAge int64 - maxEvidenceBytes int64 - pubkeyTypes []string - precision time.Duration - messageDelay time.Duration + blockBytes int64 + blockGas int64 + evidenceAge int64 + maxEvidenceBytes int64 + pubkeyTypes []string + precision time.Duration + messageDelay time.Duration + bypassCommitTimeout bool + + propose *time.Duration + proposeDelta *time.Duration + vote *time.Duration + voteDelta *time.Duration + commit *time.Duration } func makeParams(args makeParamsArgs) ConsensusParams { if args.pubkeyTypes == nil { args.pubkeyTypes = valEd25519 } + if args.propose == nil { + args.propose = durationPtr(1) + } + if args.proposeDelta == nil { + args.proposeDelta = durationPtr(1) + } + if args.vote == nil { + args.vote = durationPtr(1) + } + if args.voteDelta == nil { + args.voteDelta = durationPtr(1) + } + if args.commit == nil { + args.commit = durationPtr(1) + } return ConsensusParams{ Block: BlockParams{ MaxBytes: args.blockBytes, @@ -198,8 +227,15 @@ func makeParams(args makeParamsArgs) ConsensusParams { Precision: args.precision, MessageDelay: args.messageDelay, }, + Timeout: TimeoutParams{ + Propose: *args.propose, + ProposeDelta: *args.proposeDelta, + Vote: *args.vote, + VoteDelta: *args.voteDelta, + Commit: *args.commit, + BypassCommitTimeout: args.bypassCommitTimeout, + }, } - } func TestConsensusParamsHash(t *testing.T) { @@ -246,12 +282,41 @@ func TestConsensusParamsUpdate(t *testing.T) { intialParams: makeParams(makeParamsArgs{evidenceAge: 3, precision: time.Second, messageDelay: 3 * time.Second}), updates: &tmproto.ConsensusParams{ Synchrony: &tmproto.SynchronyParams{ - Precision: time.Second * 2, - MessageDelay: time.Second * 4, + Precision: durationPtr(time.Second * 2), + MessageDelay: durationPtr(time.Second * 4), }, }, updatedParams: makeParams(makeParamsArgs{evidenceAge: 3, precision: 2 * time.Second, messageDelay: 4 * time.Second}), }, + { + // update timeout params + intialParams: makeParams(makeParamsArgs{ + propose: durationPtr(3 * time.Second), + proposeDelta: durationPtr(500 * time.Millisecond), + vote: durationPtr(time.Second), + voteDelta: durationPtr(500 * time.Millisecond), + commit: durationPtr(time.Second), + bypassCommitTimeout: false, + }), + updates: &tmproto.ConsensusParams{ + Timeout: &tmproto.TimeoutParams{ + Propose: durationPtr(2 * time.Second), + ProposeDelta: durationPtr(400 * time.Millisecond), + Vote: durationPtr(5 * time.Second), + VoteDelta: durationPtr(400 * time.Millisecond), + Commit: durationPtr(time.Minute), + BypassCommitTimeout: true, + }, + }, + updatedParams: makeParams(makeParamsArgs{ + propose: durationPtr(2 * time.Second), + proposeDelta: durationPtr(400 * time.Millisecond), + vote: durationPtr(5 * time.Second), + voteDelta: durationPtr(400 * time.Millisecond), + commit: durationPtr(time.Minute), + bypassCommitTimeout: true, + }), + }, // fine updates { intialParams: makeParams(makeParamsArgs{blockBytes: 1, blockGas: 2, evidenceAge: 3}), @@ -339,3 +404,7 @@ func TestProto(t *testing.T) { } } + +func durationPtr(t time.Duration) *time.Duration { + return &t +} diff --git a/types/priv_validator.go b/types/priv_validator.go index 5a9b27cb6..72027c622 100644 --- a/types/priv_validator.go +++ b/types/priv_validator.go @@ -90,11 +90,24 @@ func (pv MockPV) SignVote(ctx context.Context, chainID string, vote *tmproto.Vot } signBytes := VoteSignBytes(useChainID, vote) + extSignBytes := VoteExtensionSignBytes(useChainID, vote) sig, err := pv.PrivKey.Sign(signBytes) if err != nil { return err } vote.Signature = sig + + var extSig []byte + // We only sign vote extensions for precommits + if vote.Type == tmproto.PrecommitType { + extSig, err = pv.PrivKey.Sign(extSignBytes) + if err != nil { + return err + } + } else if len(vote.Extension) > 0 { + return errors.New("unexpected vote extension - vote extensions are only allowed in precommits") + } + vote.ExtensionSignature = extSig return nil } diff --git a/types/proposal_test.go b/types/proposal_test.go index b8b8b3a67..6b2b3dd59 100644 --- a/types/proposal_test.go +++ b/types/proposal_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/internal/libs/protoio" tmrand "github.com/tendermint/tendermint/libs/rand" tmtime "github.com/tendermint/tendermint/libs/time" @@ -61,7 +61,7 @@ func TestProposalVerifySignature(t *testing.T) { prop := NewProposal( 4, 2, 2, - BlockID{tmrand.Bytes(tmhash.Size), PartSetHeader{777, tmrand.Bytes(tmhash.Size)}}, tmtime.Now()) + BlockID{tmrand.Bytes(crypto.HashSize), PartSetHeader{777, tmrand.Bytes(crypto.HashSize)}}, tmtime.Now()) p := prop.ToProto() signBytes := ProposalSignBytes("test_chain_id", p) @@ -163,7 +163,7 @@ func TestProposalValidateBasic(t *testing.T) { p.Signature = make([]byte, MaxSignatureSize+1) }, true}, } - blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) + blockID := makeBlockID(crypto.Checksum([]byte("blockhash")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) for _, tc := range testCases { tc := tc diff --git a/types/protobuf_test.go b/types/protobuf_test.go index 3ab095eee..c482153c1 100644 --- a/types/protobuf_test.go +++ b/types/protobuf_test.go @@ -14,11 +14,11 @@ import ( func TestABCIPubKey(t *testing.T) { pkEd := ed25519.GenPrivKey().PubKey() - err := testABCIPubKey(t, pkEd, ABCIPubKeyTypeEd25519) + err := testABCIPubKey(t, pkEd) assert.NoError(t, err) } -func testABCIPubKey(t *testing.T, pk crypto.PubKey, typeStr string) error { +func testABCIPubKey(t *testing.T, pk crypto.PubKey) error { abciPubKey, err := encoding.PubKeyToProto(pk) require.NoError(t, err) pk2, err := encoding.PubKeyFromProto(abciPubKey) diff --git a/types/results.go b/types/results.go deleted file mode 100644 index 511364000..000000000 --- a/types/results.go +++ /dev/null @@ -1,54 +0,0 @@ -package types - -import ( - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto/merkle" -) - -// ABCIResults wraps the deliver tx results to return a proof. -type ABCIResults []*abci.ExecTxResult - -// NewResults strips non-deterministic fields from ResponseDeliverTx responses -// and returns ABCIResults. -func NewResults(responses []*abci.ExecTxResult) ABCIResults { - res := make(ABCIResults, len(responses)) - for i, d := range responses { - res[i] = deterministicExecTxResult(d) - } - return res -} - -// Hash returns a merkle hash of all results. -func (a ABCIResults) Hash() []byte { - return merkle.HashFromByteSlices(a.toByteSlices()) -} - -// ProveResult returns a merkle proof of one result from the set -func (a ABCIResults) ProveResult(i int) merkle.Proof { - _, proofs := merkle.ProofsFromByteSlices(a.toByteSlices()) - return *proofs[i] -} - -func (a ABCIResults) toByteSlices() [][]byte { - l := len(a) - bzs := make([][]byte, l) - for i := 0; i < l; i++ { - bz, err := a[i].Marshal() - if err != nil { - panic(err) - } - bzs[i] = bz - } - return bzs -} - -// deterministicExecTxResult strips non-deterministic fields from -// ResponseDeliverTx and returns another ResponseDeliverTx. -func deterministicExecTxResult(response *abci.ExecTxResult) *abci.ExecTxResult { - return &abci.ExecTxResult{ - Code: response.Code, - Data: response.Data, - GasWanted: response.GasWanted, - GasUsed: response.GasUsed, - } -} diff --git a/types/results_test.go b/types/results_test.go deleted file mode 100644 index da7eebed0..000000000 --- a/types/results_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package types - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - abci "github.com/tendermint/tendermint/abci/types" -) - -func TestABCIResults(t *testing.T) { - a := &abci.ExecTxResult{Code: 0, Data: nil} - b := &abci.ExecTxResult{Code: 0, Data: []byte{}} - c := &abci.ExecTxResult{Code: 0, Data: []byte("one")} - d := &abci.ExecTxResult{Code: 14, Data: nil} - e := &abci.ExecTxResult{Code: 14, Data: []byte("foo")} - f := &abci.ExecTxResult{Code: 14, Data: []byte("bar")} - - // Nil and []byte{} should produce the same bytes - bzA, err := a.Marshal() - require.NoError(t, err) - bzB, err := b.Marshal() - require.NoError(t, err) - - require.Equal(t, bzA, bzB) - - // a and b should be the same, don't go in results. - results := ABCIResults{a, c, d, e, f} - - // Make sure each result serializes differently - last := []byte{} - assert.Equal(t, last, bzA) // first one is empty - for i, res := range results[1:] { - bz, err := res.Marshal() - require.NoError(t, err) - - assert.NotEqual(t, last, bz, "%d", i) - last = bz - } - - // Make sure that we can get a root hash from results and verify proofs. - root := results.Hash() - assert.NotEmpty(t, root) - - for i, res := range results { - bz, err := res.Marshal() - require.NoError(t, err) - - proof := results.ProveResult(i) - valid := proof.Verify(root, bz) - assert.NoError(t, valid, "%d", i) - } -} diff --git a/types/signable.go b/types/signable.go index 25e307316..85b9bb911 100644 --- a/types/signable.go +++ b/types/signable.go @@ -11,13 +11,3 @@ var ( // XXX: secp256k1 does not have Size nor MaxSize defined. MaxSignatureSize = tmmath.MaxInt(ed25519.SignatureSize, 64) ) - -// Signable is an interface for all signable things. -// It typically removes signatures before serializing. -// SignBytes returns the bytes to be signed -// NOTE: chainIDs are part of the SignBytes but not -// necessarily the object themselves. -// NOTE: Expected to panic if there is an error marshaling. -type Signable interface { - SignBytes(chainID string) []byte -} diff --git a/types/test_util.go b/types/test_util.go index dbd3f81ec..8aea2f02c 100644 --- a/types/test_util.go +++ b/types/test_util.go @@ -43,5 +43,16 @@ func signAddVote(ctx context.Context, privVal PrivValidator, vote *Vote, voteSet return false, err } vote.Signature = v.Signature + vote.ExtensionSignature = v.ExtensionSignature return voteSet.AddVote(vote) } + +// Votes constructed from commits don't have extensions, because we don't store +// the extensions themselves in the commit. This method is used to construct a +// copy of a vote, but nil its extension and signature. +func voteWithoutExtension(v *Vote) *Vote { + vc := v.Copy() + vc.Extension = nil + vc.ExtensionSignature = nil + return vc +} diff --git a/types/tx.go b/types/tx.go index 746252238..1d429ddf1 100644 --- a/types/tx.go +++ b/types/tx.go @@ -5,9 +5,11 @@ import ( "crypto/sha256" "errors" "fmt" + "sort" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/merkle" - "github.com/tendermint/tendermint/crypto/tmhash" tmbytes "github.com/tendermint/tendermint/libs/bytes" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) @@ -21,7 +23,7 @@ type Tx []byte func (tx Tx) Key() TxKey { return sha256.Sum256(tx) } // Hash computes the TMHASH hash of the wire encoded transaction. -func (tx Tx) Hash() []byte { return tmhash.Sum(tx) } +func (tx Tx) Hash() []byte { return crypto.Checksum(tx) } // String returns the hex-encoded transaction as a string. func (tx Tx) String() string { return fmt.Sprintf("Tx{%X}", []byte(tx)) } @@ -32,13 +34,8 @@ type Txs []Tx // Hash returns the Merkle root hash of the transaction hashes. // i.e. the leaves of the tree are the hashes of the txs. func (txs Txs) Hash() []byte { - // These allocations will be removed once Txs is switched to [][]byte, - // ref #2603. This is because golang does not allow type casting slices without unsafe - txBzs := make([][]byte, len(txs)) - for i := 0; i < len(txs); i++ { - txBzs[i] = txs[i].Hash() - } - return merkle.HashFromByteSlices(txBzs) + hl := txs.hashList() + return merkle.HashFromByteSlices(hl) } // Index returns the index of this transaction in the list, or -1 if not found @@ -61,16 +58,9 @@ func (txs Txs) IndexByHash(hash []byte) int { return -1 } -// Proof returns a simple merkle proof for this node. -// Panics if i < 0 or i >= len(txs) -// TODO: optimize this! func (txs Txs) Proof(i int) TxProof { - l := len(txs) - bzs := make([][]byte, l) - for i := 0; i < l; i++ { - bzs[i] = txs[i].Hash() - } - root, proofs := merkle.ProofsFromByteSlices(bzs) + hl := txs.hashList() + root, proofs := merkle.ProofsFromByteSlices(hl) return TxProof{ RootHash: root, @@ -79,11 +69,23 @@ func (txs Txs) Proof(i int) TxProof { } } +func (txs Txs) hashList() [][]byte { + hl := make([][]byte, len(txs)) + for i := 0; i < len(txs); i++ { + hl[i] = txs[i].Hash() + } + return hl +} + +// Txs is a slice of transactions. Sorting a Txs value orders the transactions +// lexicographically. +func (txs Txs) Len() int { return len(txs) } +func (txs Txs) Swap(i, j int) { txs[i], txs[j] = txs[j], txs[i] } +func (txs Txs) Less(i, j int) bool { + return bytes.Compare(txs[i], txs[j]) == -1 +} + // ToSliceOfBytes converts a Txs to slice of byte slices. -// -// NOTE: This method should become obsolete once Txs is switched to [][]byte. -// ref: #2603 -// TODO This function is to disappear when TxRecord is introduced func (txs Txs) ToSliceOfBytes() [][]byte { txBzs := make([][]byte, len(txs)) for i := 0; i < len(txs); i++ { @@ -92,14 +94,179 @@ func (txs Txs) ToSliceOfBytes() [][]byte { return txBzs } -// ToTxs converts a raw slice of byte slices into a Txs type. -// TODO This function is to disappear when TxRecord is introduced -func ToTxs(txs [][]byte) Txs { - txBzs := make(Txs, len(txs)) - for i := 0; i < len(txs); i++ { - txBzs[i] = txs[i] +// TxRecordSet contains indexes into an underlying set of transactions. +// These indexes are useful for validating and working with a list of TxRecords +// from the PrepareProposal response. +// +// Only one copy of the original data is referenced by all of the indexes but a +// transaction may appear in multiple indexes. +type TxRecordSet struct { + // all holds the complete list of all transactions from the original list of + // TxRecords. + all Txs + + // included is an index of the transactions that will be included in the block + // and is constructed from the list of both added and unmodified transactions. + // included maintains the original order that the transactions were present + // in the list of TxRecords. + included Txs + + // added, unmodified, removed, and unknown are indexes for each of the actions + // that may be supplied with a transaction. + // + // Because each transaction only has one action, it can be referenced by + // at most 3 indexes in this data structure: the action-specific index, the + // included index, and the all index. + added Txs + unmodified Txs + removed Txs + unknown Txs +} + +// NewTxRecordSet constructs a new set from the given transaction records. +// The contents of the input transactions are shared by the set, and must not +// be modified during the lifetime of the set. +func NewTxRecordSet(trs []*abci.TxRecord) TxRecordSet { + txrSet := TxRecordSet{ + all: make([]Tx, len(trs)), } - return txBzs + for i, tr := range trs { + + txrSet.all[i] = Tx(tr.Tx) + + // The following set of assignments do not allocate new []byte, they create + // pointers to the already allocated slice. + switch tr.GetAction() { + case abci.TxRecord_UNKNOWN: + txrSet.unknown = append(txrSet.unknown, txrSet.all[i]) + case abci.TxRecord_UNMODIFIED: + txrSet.unmodified = append(txrSet.unmodified, txrSet.all[i]) + txrSet.included = append(txrSet.included, txrSet.all[i]) + case abci.TxRecord_ADDED: + txrSet.added = append(txrSet.added, txrSet.all[i]) + txrSet.included = append(txrSet.included, txrSet.all[i]) + case abci.TxRecord_REMOVED: + txrSet.removed = append(txrSet.removed, txrSet.all[i]) + } + } + return txrSet +} + +// IncludedTxs returns the transactions marked for inclusion in a block. This +// list maintains the order that the transactions were included in the list of +// TxRecords that were used to construct the TxRecordSet. +func (t TxRecordSet) IncludedTxs() []Tx { + return t.included +} + +// RemovedTxs returns the transactions marked for removal by the application. +func (t TxRecordSet) RemovedTxs() []Tx { + return t.removed +} + +// Validate checks that the record set was correctly constructed from the original +// list of transactions. +func (t TxRecordSet) Validate(maxSizeBytes int64, otxs Txs) error { + if len(t.unknown) > 0 { + return fmt.Errorf("%d transactions marked unknown (first unknown hash: %x)", len(t.unknown), t.unknown[0].Hash()) + } + + // The following validation logic performs a set of sorts on the data in the TxRecordSet indexes. + // It sorts the original transaction list, otxs, once. + // It sorts the new transaction list twice: once when sorting 'all', the total list, + // and once by sorting the set of the added, removed, and unmodified transactions indexes, + // which, when combined, comprise the complete list of modified transactions. + // + // Each of the added, removed, and unmodified indices is then iterated and once + // and each value index is checked against the sorted original list for containment. + // Asymptotically, this yields a total runtime of O(N*log(N) + 2*M*log(M) + M*log(N)). + // in the input size of the original list, N, and the input size of the new list, M, respectively. + // Performance gains are likely possible, but this was preferred for readability and maintainability. + + // Sort a copy of the complete transaction slice so we can check for + // duplication. The copy is so we do not change the original ordering. + // Only the slices are copied, the transaction contents are shared. + allCopy := sortedCopy(t.all) + + for i, cur := range allCopy { + // allCopy is sorted, so any duplicated data will be adjacent. + if i+1 < len(allCopy) && bytes.Equal(cur, allCopy[i+1]) { + return fmt.Errorf("found duplicate transaction with hash: %x", cur.Hash()) + } + } + + // create copies of each of the action-specific indexes so that order of the original + // indexes can be preserved. + addedCopy := sortedCopy(t.added) + removedCopy := sortedCopy(t.removed) + unmodifiedCopy := sortedCopy(t.unmodified) + + var size int64 + for _, cur := range append(unmodifiedCopy, addedCopy...) { + size += int64(len(cur)) + if size > maxSizeBytes { + return fmt.Errorf("transaction data size exceeds maximum %d", maxSizeBytes) + } + } + + // make a defensive copy of otxs so that the order of + // the caller's data is not altered. + otxsCopy := sortedCopy(otxs) + + if ix, ok := containsAll(otxsCopy, unmodifiedCopy); !ok { + return fmt.Errorf("new transaction incorrectly marked as removed, transaction hash: %x", unmodifiedCopy[ix].Hash()) + } + + if ix, ok := containsAll(otxsCopy, removedCopy); !ok { + return fmt.Errorf("new transaction incorrectly marked as removed, transaction hash: %x", removedCopy[ix].Hash()) + } + if ix, ok := containsAny(otxsCopy, addedCopy); ok { + return fmt.Errorf("existing transaction incorrectly marked as added, transaction hash: %x", addedCopy[ix].Hash()) + } + return nil +} + +func sortedCopy(txs Txs) Txs { + cp := make(Txs, len(txs)) + copy(cp, txs) + sort.Sort(cp) + return cp +} + +// containsAny checks that list a contains one of the transactions in list +// b. If a match is found, the index in b of the matching transaction is returned. +// Both lists must be sorted. +func containsAny(a, b []Tx) (int, bool) { + for i, cur := range b { + if _, ok := contains(a, cur); ok { + return i, true + } + } + return -1, false +} + +// containsAll checks that super contains all of the transactions in the sub +// list. If not all values in sub are present in super, the index in sub of the +// first Tx absent from super is returned. +func containsAll(super, sub Txs) (int, bool) { + for i, cur := range sub { + if _, ok := contains(super, cur); !ok { + return i, false + } + } + return -1, true +} + +// contains checks that the sorted list, set contains elem. If set does contain elem, then the +// index in set of elem is returned. +func contains(set []Tx, elem Tx) (int, bool) { + n := sort.Search(len(set), func(i int) bool { + return bytes.Compare(elem, set[i]) <= 0 + }) + if n == len(set) || !bytes.Equal(elem, set[n]) { + return -1, false + } + return n, true } // TxProof represents a Merkle proof of the presence of a transaction in the Merkle tree. diff --git a/types/tx_test.go b/types/tx_test.go index e2f12772a..77afa1b23 100644 --- a/types/tx_test.go +++ b/types/tx_test.go @@ -2,12 +2,13 @@ package types import ( "bytes" - mrand "math/rand" + "math/rand" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" ctest "github.com/tendermint/tendermint/internal/libs/test" tmrand "github.com/tendermint/tendermint/libs/rand" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -21,11 +22,6 @@ func makeTxs(cnt, size int) Txs { return txs } -func randInt(low, high int) int { - off := mrand.Int() % (high - low) - return low + off -} - func TestTxIndex(t *testing.T) { for i := 0; i < 20; i++ { txs := makeTxs(15, 60) @@ -52,6 +48,179 @@ func TestTxIndexByHash(t *testing.T) { } } +func TestValidateTxRecordSet(t *testing.T) { + t.Run("should error on total transaction size exceeding max data size", func(t *testing.T) { + trs := []*abci.TxRecord{ + { + Action: abci.TxRecord_ADDED, + Tx: Tx([]byte{1, 2, 3, 4, 5}), + }, + { + Action: abci.TxRecord_ADDED, + Tx: Tx([]byte{6, 7, 8, 9, 10}), + }, + } + txrSet := NewTxRecordSet(trs) + err := txrSet.Validate(9, []Tx{}) + require.Error(t, err) + }) + t.Run("should not error on removed transaction size exceeding max data size", func(t *testing.T) { + trs := []*abci.TxRecord{ + { + Action: abci.TxRecord_ADDED, + Tx: Tx([]byte{1, 2, 3, 4, 5}), + }, + { + Action: abci.TxRecord_ADDED, + Tx: Tx([]byte{6, 7, 8, 9}), + }, + { + Action: abci.TxRecord_REMOVED, + Tx: Tx([]byte{10}), + }, + } + txrSet := NewTxRecordSet(trs) + err := txrSet.Validate(9, []Tx{[]byte{10}}) + require.NoError(t, err) + }) + t.Run("should error on duplicate transactions with the same action", func(t *testing.T) { + trs := []*abci.TxRecord{ + { + Action: abci.TxRecord_ADDED, + Tx: Tx([]byte{1, 2, 3, 4, 5}), + }, + { + Action: abci.TxRecord_ADDED, + Tx: Tx([]byte{100}), + }, + { + Action: abci.TxRecord_ADDED, + Tx: Tx([]byte{1, 2, 3, 4, 5}), + }, + { + Action: abci.TxRecord_ADDED, + Tx: Tx([]byte{200}), + }, + } + txrSet := NewTxRecordSet(trs) + err := txrSet.Validate(100, []Tx{}) + require.Error(t, err) + }) + t.Run("should error on duplicate transactions with mixed actions", func(t *testing.T) { + trs := []*abci.TxRecord{ + { + Action: abci.TxRecord_ADDED, + Tx: Tx([]byte{1, 2, 3, 4, 5}), + }, + { + Action: abci.TxRecord_ADDED, + Tx: Tx([]byte{100}), + }, + { + Action: abci.TxRecord_REMOVED, + Tx: Tx([]byte{1, 2, 3, 4, 5}), + }, + { + Action: abci.TxRecord_ADDED, + Tx: Tx([]byte{200}), + }, + } + txrSet := NewTxRecordSet(trs) + err := txrSet.Validate(100, []Tx{}) + require.Error(t, err) + }) + t.Run("should error on new transactions marked UNMODIFIED", func(t *testing.T) { + trs := []*abci.TxRecord{ + { + Action: abci.TxRecord_UNMODIFIED, + Tx: Tx([]byte{1, 2, 3, 4, 5}), + }, + } + txrSet := NewTxRecordSet(trs) + err := txrSet.Validate(100, []Tx{}) + require.Error(t, err) + }) + t.Run("should error on new transactions marked REMOVED", func(t *testing.T) { + trs := []*abci.TxRecord{ + { + Action: abci.TxRecord_REMOVED, + Tx: Tx([]byte{1, 2, 3, 4, 5}), + }, + } + txrSet := NewTxRecordSet(trs) + err := txrSet.Validate(100, []Tx{}) + require.Error(t, err) + }) + t.Run("should error on existing transaction marked as ADDED", func(t *testing.T) { + trs := []*abci.TxRecord{ + { + Action: abci.TxRecord_ADDED, + Tx: Tx([]byte{5, 4, 3, 2, 1}), + }, + { + Action: abci.TxRecord_ADDED, + Tx: Tx([]byte{6}), + }, + { + Action: abci.TxRecord_ADDED, + Tx: Tx([]byte{1, 2, 3, 4, 5}), + }, + } + txrSet := NewTxRecordSet(trs) + err := txrSet.Validate(100, []Tx{{0}, {1, 2, 3, 4, 5}}) + require.Error(t, err) + }) + t.Run("should error if any transaction marked as UNKNOWN", func(t *testing.T) { + trs := []*abci.TxRecord{ + { + Action: abci.TxRecord_UNKNOWN, + Tx: Tx([]byte{1, 2, 3, 4, 5}), + }, + } + txrSet := NewTxRecordSet(trs) + err := txrSet.Validate(100, []Tx{}) + require.Error(t, err) + }) + t.Run("TxRecordSet preserves order", func(t *testing.T) { + trs := []*abci.TxRecord{ + { + Action: abci.TxRecord_ADDED, + Tx: Tx([]byte{100}), + }, + { + Action: abci.TxRecord_ADDED, + Tx: Tx([]byte{99}), + }, + { + Action: abci.TxRecord_ADDED, + Tx: Tx([]byte{55}), + }, + { + Action: abci.TxRecord_ADDED, + Tx: Tx([]byte{12}), + }, + { + Action: abci.TxRecord_ADDED, + Tx: Tx([]byte{66}), + }, + { + Action: abci.TxRecord_ADDED, + Tx: Tx([]byte{9}), + }, + { + Action: abci.TxRecord_ADDED, + Tx: Tx([]byte{17}), + }, + } + txrSet := NewTxRecordSet(trs) + err := txrSet.Validate(100, []Tx{}) + require.NoError(t, err) + for i, tx := range txrSet.IncludedTxs() { + require.Equal(t, Tx(trs[i].Tx), tx) + } + }) +} + func TestValidTxProof(t *testing.T) { cases := []struct { txs Txs @@ -150,3 +319,7 @@ func assertBadProof(t *testing.T, root []byte, bad []byte, good TxProof) { } } } + +func randInt(low, high int) int { + return rand.Intn(high-low) + low +} diff --git a/types/validation.go b/types/validation.go index 8655bdabd..21c8730f5 100644 --- a/types/validation.go +++ b/types/validation.go @@ -4,8 +4,8 @@ import ( "errors" "fmt" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/batch" - "github.com/tendermint/tendermint/crypto/tmhash" tmmath "github.com/tendermint/tendermint/libs/math" ) @@ -132,11 +132,11 @@ func VerifyCommitLightTrusting(chainID string, vals *ValidatorSet, commit *Commi } // ValidateHash returns an error if the hash is not empty, but its -// size != tmhash.Size. +// size != crypto.HashSize. func ValidateHash(h []byte) error { - if len(h) > 0 && len(h) != tmhash.Size { + if len(h) > 0 && len(h) != crypto.HashSize { return fmt.Errorf("expected size to be %d bytes, got %d bytes", - tmhash.Size, + crypto.HashSize, len(h), ) } diff --git a/types/validator_set.go b/types/validator_set.go index 1f773d1b5..d825ff590 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -68,7 +68,7 @@ type ValidatorSet struct { // MaxVotesCount - commits by a validator set larger than this will fail // validation. func NewValidatorSet(valz []*Validator) *ValidatorSet { - vals := &ValidatorSet{} + vals := &ValidatorSet{Validators: []*Validator{}} err := vals.updateWithChangeSet(valz, false) if err != nil { panic(fmt.Errorf("cannot create validator set: %w", err)) @@ -235,9 +235,6 @@ func (vals *ValidatorSet) shiftByAvgProposerPriority() { // Makes a copy of the validator list. func validatorListCopy(valsList []*Validator) []*Validator { - if valsList == nil { - return nil - } valsCopy := make([]*Validator, len(valsList)) for i, val := range valsList { valsCopy[i] = val.Copy() diff --git a/types/vote.go b/types/vote.go index 7333f98fc..f20ee491e 100644 --- a/types/vote.go +++ b/types/vote.go @@ -46,86 +46,43 @@ func NewConflictingVoteError(vote1, vote2 *Vote) *ErrVoteConflictingVotes { // Address is hex bytes. type Address = crypto.Address -// VoteExtensionToSign is a subset of VoteExtension -// that is signed by the validators private key -type VoteExtensionToSign struct { - AppDataToSign []byte `json:"app_data_to_sign"` -} - -func (ext VoteExtensionToSign) ToProto() *tmproto.VoteExtensionToSign { - if ext.IsEmpty() { - return nil - } - return &tmproto.VoteExtensionToSign{ - AppDataToSign: ext.AppDataToSign, - } -} - -func VoteExtensionToSignFromProto(pext *tmproto.VoteExtensionToSign) VoteExtensionToSign { - if pext == nil { - return VoteExtensionToSign{} - } - return VoteExtensionToSign{ - AppDataToSign: pext.AppDataToSign, - } -} - -func (ext VoteExtensionToSign) IsEmpty() bool { - return len(ext.AppDataToSign) == 0 -} - -// BytesPacked returns a bytes-packed representation for -// debugging and human identification. This function should -// not be used for any logical operations. -func (ext VoteExtensionToSign) BytesPacked() []byte { - res := []byte{} - res = append(res, ext.AppDataToSign...) - return res -} - -// ToVoteExtension constructs a VoteExtension from a VoteExtensionToSign -func (ext VoteExtensionToSign) ToVoteExtension() VoteExtension { - return VoteExtension{ - AppDataToSign: ext.AppDataToSign, - } -} - -// VoteExtension is a set of data provided by the application -// that is additionally included in the vote -type VoteExtension struct { - AppDataToSign []byte `json:"app_data_to_sign"` - AppDataSelfAuthenticating []byte `json:"app_data_self_authenticating"` -} - -// ToSign constructs a VoteExtensionToSign from a VoteExtenstion -func (ext VoteExtension) ToSign() VoteExtensionToSign { - return VoteExtensionToSign{ - AppDataToSign: ext.AppDataToSign, - } -} - -// BytesPacked returns a bytes-packed representation for -// debugging and human identification. This function should -// not be used for any logical operations. -func (ext VoteExtension) BytesPacked() []byte { - res := []byte{} - res = append(res, ext.AppDataToSign...) - res = append(res, ext.AppDataSelfAuthenticating...) - return res -} - // Vote represents a prevote, precommit, or commit vote from validators for // consensus. type Vote struct { - Type tmproto.SignedMsgType `json:"type"` - Height int64 `json:"height,string"` - Round int32 `json:"round"` // assume there will not be greater than 2_147_483_647 rounds - BlockID BlockID `json:"block_id"` // zero if vote is nil. - Timestamp time.Time `json:"timestamp"` - ValidatorAddress Address `json:"validator_address"` - ValidatorIndex int32 `json:"validator_index"` - Signature []byte `json:"signature"` - VoteExtension VoteExtension `json:"vote_extension"` + Type tmproto.SignedMsgType `json:"type"` + Height int64 `json:"height,string"` + Round int32 `json:"round"` // assume there will not be greater than 2_147_483_647 rounds + BlockID BlockID `json:"block_id"` // zero if vote is nil. + Timestamp time.Time `json:"timestamp"` + ValidatorAddress Address `json:"validator_address"` + ValidatorIndex int32 `json:"validator_index"` + Signature []byte `json:"signature"` + Extension []byte `json:"extension"` + ExtensionSignature []byte `json:"extension_signature"` +} + +// VoteFromProto attempts to convert the given serialization (Protobuf) type to +// our Vote domain type. No validation is performed on the resulting vote - +// this is left up to the caller to decide whether to call ValidateBasic or +// ValidateWithExtension. +func VoteFromProto(pv *tmproto.Vote) (*Vote, error) { + blockID, err := BlockIDFromProto(&pv.BlockID) + if err != nil { + return nil, err + } + + return &Vote{ + Type: pv.Type, + Height: pv.Height, + Round: pv.Round, + BlockID: *blockID, + Timestamp: pv.Timestamp, + ValidatorAddress: pv.ValidatorAddress, + ValidatorIndex: pv.ValidatorIndex, + Signature: pv.Signature, + Extension: pv.Extension, + ExtensionSignature: pv.ExtensionSignature, + }, nil } // CommitSig converts the Vote to a CommitSig. @@ -149,12 +106,11 @@ func (vote *Vote) CommitSig() CommitSig { ValidatorAddress: vote.ValidatorAddress, Timestamp: vote.Timestamp, Signature: vote.Signature, - VoteExtension: vote.VoteExtension.ToSign(), } } // VoteSignBytes returns the proto-encoding of the canonicalized Vote, for -// signing. Panics is the marshaling fails. +// signing. Panics if the marshaling fails. // // The encoded Protobuf message is varint length-prefixed (using MarshalDelimited) // for backwards-compatibility with the Amino encoding, due to e.g. hardware @@ -171,9 +127,23 @@ func VoteSignBytes(chainID string, vote *tmproto.Vote) []byte { return bz } +// VoteExtensionSignBytes returns the proto-encoding of the canonicalized vote +// extension for signing. Panics if the marshaling fails. +// +// Similar to VoteSignBytes, the encoded Protobuf message is varint +// length-prefixed for backwards-compatibility with the Amino encoding. +func VoteExtensionSignBytes(chainID string, vote *tmproto.Vote) []byte { + pb := CanonicalizeVoteExtension(chainID, vote) + bz, err := protoio.MarshalDelimited(&pb) + if err != nil { + panic(err) + } + + return bz +} + func (vote *Vote) Copy() *Vote { voteCopy := *vote - voteCopy.VoteExtension = vote.VoteExtension.Copy() return &voteCopy } @@ -213,23 +183,54 @@ func (vote *Vote) String() string { typeString, tmbytes.Fingerprint(vote.BlockID.Hash), tmbytes.Fingerprint(vote.Signature), - tmbytes.Fingerprint(vote.VoteExtension.BytesPacked()), + tmbytes.Fingerprint(vote.Extension), CanonicalTime(vote.Timestamp), ) } -func (vote *Vote) Verify(chainID string, pubKey crypto.PubKey) error { +func (vote *Vote) verifyAndReturnProto(chainID string, pubKey crypto.PubKey) (*tmproto.Vote, error) { if !bytes.Equal(pubKey.Address(), vote.ValidatorAddress) { - return ErrVoteInvalidValidatorAddress + return nil, ErrVoteInvalidValidatorAddress } v := vote.ToProto() if !pubKey.VerifySignature(VoteSignBytes(chainID, v), vote.Signature) { - return ErrVoteInvalidSignature + return nil, ErrVoteInvalidSignature + } + return v, nil +} + +// Verify checks whether the signature associated with this vote corresponds to +// the given chain ID and public key. This function does not validate vote +// extension signatures - to do so, use VerifyWithExtension instead. +func (vote *Vote) Verify(chainID string, pubKey crypto.PubKey) error { + _, err := vote.verifyAndReturnProto(chainID, pubKey) + return err +} + +// VerifyWithExtension performs the same verification as Verify, but +// additionally checks whether the vote extension signature corresponds to the +// given chain ID and public key. We only verify vote extension signatures for +// precommits. +func (vote *Vote) VerifyWithExtension(chainID string, pubKey crypto.PubKey) error { + v, err := vote.verifyAndReturnProto(chainID, pubKey) + if err != nil { + return err + } + // We only verify vote extension signatures for precommits. + if vote.Type == tmproto.PrecommitType { + extSignBytes := VoteExtensionSignBytes(chainID, v) + // TODO: Remove extension signature nil check to enforce vote extension + // signing once we resolve https://github.com/tendermint/tendermint/issues/8272 + if vote.ExtensionSignature != nil && !pubKey.VerifySignature(extSignBytes, vote.ExtensionSignature) { + return ErrVoteInvalidSignature + } } return nil } -// ValidateBasic performs basic validation. +// ValidateBasic checks whether the vote is well-formed. It does not, however, +// check vote extensions - for vote validation with vote extension validation, +// use ValidateWithExtension. func (vote *Vote) ValidateBasic() error { if !IsVoteTypeValid(vote.Type) { return errors.New("invalid Type") @@ -272,38 +273,41 @@ func (vote *Vote) ValidateBasic() error { return fmt.Errorf("signature is too big (max: %d)", MaxSignatureSize) } - // XXX: add length verification for vote extension? + // We should only ever see vote extensions in precommits. + if vote.Type != tmproto.PrecommitType { + if len(vote.Extension) > 0 { + return errors.New("unexpected vote extension") + } + if len(vote.ExtensionSignature) > 0 { + return errors.New("unexpected vote extension signature") + } + } return nil } -func (ext VoteExtension) Copy() VoteExtension { - res := VoteExtension{ - AppDataToSign: ext.AppDataToSign, - AppDataSelfAuthenticating: ext.AppDataSelfAuthenticating, - } - return res -} - -func (ext VoteExtension) IsEmpty() bool { - if len(ext.AppDataToSign) != 0 { - return false - } - if len(ext.AppDataSelfAuthenticating) != 0 { - return false - } - return true -} - -func (ext VoteExtension) ToProto() *tmproto.VoteExtension { - if ext.IsEmpty() { - return nil +// ValidateWithExtension performs the same validations as ValidateBasic, but +// additionally checks whether a vote extension signature is present. This +// function is used in places where vote extension signatures are expected. +func (vote *Vote) ValidateWithExtension() error { + if err := vote.ValidateBasic(); err != nil { + return err } - return &tmproto.VoteExtension{ - AppDataToSign: ext.AppDataToSign, - AppDataSelfAuthenticating: ext.AppDataSelfAuthenticating, + // We should always see vote extension signatures in precommits + if vote.Type == tmproto.PrecommitType { + // TODO(thane): Remove extension length check once + // https://github.com/tendermint/tendermint/issues/8272 is + // resolved. + if len(vote.Extension) > 0 && len(vote.ExtensionSignature) == 0 { + return errors.New("vote extension signature is missing") + } + if len(vote.ExtensionSignature) > MaxSignatureSize { + return fmt.Errorf("vote extension signature is too big (max: %d)", MaxSignatureSize) + } } + + return nil } // ToProto converts the handwritten type to proto generated type @@ -314,15 +318,16 @@ func (vote *Vote) ToProto() *tmproto.Vote { } return &tmproto.Vote{ - Type: vote.Type, - Height: vote.Height, - Round: vote.Round, - BlockID: vote.BlockID.ToProto(), - Timestamp: vote.Timestamp, - ValidatorAddress: vote.ValidatorAddress, - ValidatorIndex: vote.ValidatorIndex, - Signature: vote.Signature, - VoteExtension: vote.VoteExtension.ToProto(), + Type: vote.Type, + Height: vote.Height, + Round: vote.Round, + BlockID: vote.BlockID.ToProto(), + Timestamp: vote.Timestamp, + ValidatorAddress: vote.ValidatorAddress, + ValidatorIndex: vote.ValidatorIndex, + Signature: vote.Signature, + Extension: vote.Extension, + ExtensionSignature: vote.ExtensionSignature, } } @@ -341,38 +346,3 @@ func VotesToProto(votes []*Vote) []*tmproto.Vote { } return res } - -func VoteExtensionFromProto(pext *tmproto.VoteExtension) VoteExtension { - ext := VoteExtension{} - if pext != nil { - ext.AppDataToSign = pext.AppDataToSign - ext.AppDataSelfAuthenticating = pext.AppDataSelfAuthenticating - } - return ext -} - -// FromProto converts a proto generetad type to a handwritten type -// return type, nil if everything converts safely, otherwise nil, error -func VoteFromProto(pv *tmproto.Vote) (*Vote, error) { - if pv == nil { - return nil, errors.New("nil vote") - } - - blockID, err := BlockIDFromProto(&pv.BlockID) - if err != nil { - return nil, err - } - - vote := new(Vote) - vote.Type = pv.Type - vote.Height = pv.Height - vote.Round = pv.Round - vote.BlockID = *blockID - vote.Timestamp = pv.Timestamp - vote.ValidatorAddress = pv.ValidatorAddress - vote.ValidatorIndex = pv.ValidatorIndex - vote.Signature = pv.Signature - vote.VoteExtension = VoteExtensionFromProto(pv.VoteExtension) - - return vote, vote.ValidateBasic() -} diff --git a/types/vote_set.go b/types/vote_set.go index bb675e110..b4d149576 100644 --- a/types/vote_set.go +++ b/types/vote_set.go @@ -18,12 +18,6 @@ const ( MaxVotesCount = 10000 ) -// UNSTABLE -// XXX: duplicate of p2p.ID to avoid dependence between packages. -// Perhaps we can have a minimal types package containing this (and other things?) -// that both `types` and `p2p` import ? -type P2PID string - /* VoteSet helps collect signatures from validators at each height+round for a predefined vote type. @@ -71,7 +65,7 @@ type VoteSet struct { sum int64 // Sum of voting power for seen votes, discounting conflicts maj23 *BlockID // First 2/3 majority seen votesByBlock map[string]*blockVotes // string(blockHash|blockParts) -> blockVotes - peerMaj23s map[P2PID]BlockID // Maj23 for each peer + peerMaj23s map[string]BlockID // Maj23 for each peer } // Constructs a new VoteSet struct used to accumulate votes for given height/round. @@ -91,7 +85,7 @@ func NewVoteSet(chainID string, height int64, round int32, sum: 0, maj23: nil, votesByBlock: make(map[string]*blockVotes, valSet.Size()), - peerMaj23s: make(map[P2PID]BlockID), + peerMaj23s: make(map[string]BlockID), } } @@ -200,7 +194,7 @@ func (voteSet *VoteSet) addVote(vote *Vote) (added bool, err error) { } // Check signature. - if err := vote.Verify(voteSet.chainID, val.PubKey); err != nil { + if err := vote.VerifyWithExtension(voteSet.chainID, val.PubKey); err != nil { return false, fmt.Errorf("failed to verify vote with ChainID %s and PubKey %s: %w", voteSet.chainID, val.PubKey, err) } @@ -227,6 +221,9 @@ func (voteSet *VoteSet) getVote(valIndex int32, blockKey string) (vote *Vote, ok } func (voteSet *VoteSet) GetVotes() []*Vote { + if voteSet == nil { + return nil + } return voteSet.votes } @@ -310,7 +307,7 @@ func (voteSet *VoteSet) addVerifiedVote( // this can cause memory issues. // TODO: implement ability to remove peers too // NOTE: VoteSet must not be nil -func (voteSet *VoteSet) SetPeerMaj23(peerID P2PID, blockID BlockID) error { +func (voteSet *VoteSet) SetPeerMaj23(peerID string, blockID BlockID) error { if voteSet == nil { panic("SetPeerMaj23() on nil VoteSet") } @@ -527,9 +524,9 @@ func (voteSet *VoteSet) MarshalJSON() ([]byte, error) { // NOTE: insufficient for unmarshaling from (compressed votes) // TODO: make the peerMaj23s nicer to read (eg just the block hash) type VoteSetJSON struct { - Votes []string `json:"votes"` - VotesBitArray string `json:"votes_bit_array"` - PeerMaj23s map[P2PID]BlockID `json:"peer_maj_23s"` + Votes []string `json:"votes"` + VotesBitArray string `json:"votes_bit_array"` + PeerMaj23s map[string]BlockID `json:"peer_maj_23s"` } // Return the bit-array of votes including diff --git a/types/vote_set_test.go b/types/vote_set_test.go index 8baa74172..1805b4c3e 100644 --- a/types/vote_set_test.go +++ b/types/vote_set_test.go @@ -127,6 +127,7 @@ func TestVoteSet_AddVote_Bad(t *testing.T) { t.Errorf("expected VoteSet.Add to fail, wrong type") } } + } func TestVoteSet_2_3Majority(t *testing.T) { @@ -493,7 +494,7 @@ func TestVoteSet_MakeCommit(t *testing.T) { // Ensure that Commit is good. if err := commit.ValidateBasic(); err != nil { - t.Errorf("error in Commit.ValidateBasic(): %w", err) + t.Errorf("error in Commit.ValidateBasic(): %v", err) } } @@ -509,7 +510,6 @@ func randVoteSet( ) (*VoteSet, *ValidatorSet, []PrivValidator) { t.Helper() valSet, privValidators := randValidatorPrivValSet(ctx, t, numValidators, votingPower) - return NewVoteSet("test_chain_id", height, round, signedMsgType, valSet), valSet, privValidators } diff --git a/types/vote_test.go b/types/vote_test.go index 4a852d81f..5673ccf57 100644 --- a/types/vote_test.go +++ b/types/vote_test.go @@ -11,8 +11,8 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/libs/protoio" + tmtime "github.com/tendermint/tendermint/libs/time" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) @@ -23,7 +23,9 @@ func examplePrevote(t *testing.T) *Vote { func examplePrecommit(t testing.TB) *Vote { t.Helper() - return exampleVote(t, byte(tmproto.PrecommitType)) + vote := exampleVote(t, byte(tmproto.PrecommitType)) + vote.ExtensionSignature = []byte("signature") + return vote } func exampleVote(tb testing.TB, t byte) *Vote { @@ -37,16 +39,17 @@ func exampleVote(tb testing.TB, t byte) *Vote { Round: 2, Timestamp: stamp, BlockID: BlockID{ - Hash: tmhash.Sum([]byte("blockID_hash")), + Hash: crypto.Checksum([]byte("blockID_hash")), PartSetHeader: PartSetHeader{ Total: 1000000, - Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), + Hash: crypto.Checksum([]byte("blockID_part_set_header_hash")), }, }, ValidatorAddress: crypto.AddressHash([]byte("validator_address")), ValidatorIndex: 56789, } } + func TestVoteSignable(t *testing.T) { vote := examplePrecommit(t) v := vote.ToProto() @@ -130,12 +133,13 @@ func TestVoteSignBytesTestVectors(t *testing.T) { }, // containing vote extension 5: { - "test_chain_id", &Vote{Height: 1, Round: 1, VoteExtension: VoteExtension{ - AppDataToSign: []byte("signed"), - AppDataSelfAuthenticating: []byte("auth"), - }}, + "test_chain_id", &Vote{ + Height: 1, + Round: 1, + Extension: []byte("extension"), + }, []byte{ - 0x38, // length + 0x2e, // length 0x11, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 0x19, // (field_number << 3) | wire_type @@ -146,13 +150,6 @@ func TestVoteSignBytesTestVectors(t *testing.T) { // (field_number << 3) | wire_type 0x32, 0xd, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, // chainID - // (field_number << 3) | wire_type - 0x3a, - 0x8, // length - 0xa, // (field_number << 3) | wire_type - 0x6, // length - 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, // AppDataSigned - // SelfAuthenticating data is excluded on signing }, // chainID }, } @@ -208,6 +205,82 @@ func TestVoteVerifySignature(t *testing.T) { require.True(t, valid) } +// TestVoteExtension tests that the vote verification behaves correctly in each case +// of vote extension being set on the vote. +func TestVoteExtension(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + testCases := []struct { + name string + extension []byte + includeSignature bool + expectError bool + }{ + { + name: "all fields present", + extension: []byte("extension"), + includeSignature: true, + expectError: false, + }, + // TODO(thane): Re-enable once + // https://github.com/tendermint/tendermint/issues/8272 is resolved + //{ + // name: "no extension signature", + // extension: []byte("extension"), + // includeSignature: false, + // expectError: true, + //}, + { + name: "empty extension", + includeSignature: true, + expectError: false, + }, + // TODO: Re-enable once + // https://github.com/tendermint/tendermint/issues/8272 is resolved. + //{ + // name: "no extension and no signature", + // includeSignature: false, + // expectError: true, + //}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + height, round := int64(1), int32(0) + privVal := NewMockPV() + pk, err := privVal.GetPubKey(ctx) + require.NoError(t, err) + blk := Block{} + ps, err := blk.MakePartSet(BlockPartSizeBytes) + require.NoError(t, err) + vote := &Vote{ + ValidatorAddress: pk.Address(), + ValidatorIndex: 0, + Height: height, + Round: round, + Timestamp: tmtime.Now(), + Type: tmproto.PrecommitType, + BlockID: BlockID{blk.Hash(), ps.Header()}, + } + + v := vote.ToProto() + err = privVal.SignVote(ctx, "test_chain_id", v) + require.NoError(t, err) + vote.Signature = v.Signature + if tc.includeSignature { + vote.ExtensionSignature = v.ExtensionSignature + } + err = vote.VerifyWithExtension("test_chain_id", pk) + if tc.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + func TestIsVoteTypeValid(t *testing.T) { tc := []struct { name string @@ -265,39 +338,115 @@ func TestVoteString(t *testing.T) { } } -func TestVoteValidateBasic(t *testing.T) { +func signVote(ctx context.Context, t *testing.T, pv PrivValidator, chainID string, vote *Vote) { + t.Helper() + + v := vote.ToProto() + require.NoError(t, pv.SignVote(ctx, chainID, v)) + vote.Signature = v.Signature + vote.ExtensionSignature = v.ExtensionSignature +} + +func TestValidVotes(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() privVal := NewMockPV() testCases := []struct { - testName string + name string + vote *Vote malleateVote func(*Vote) - expectErr bool }{ - {"Good Vote", func(v *Vote) {}, false}, - {"Negative Height", func(v *Vote) { v.Height = -1 }, true}, - {"Negative Round", func(v *Vote) { v.Round = -1 }, true}, - {"Invalid BlockID", func(v *Vote) { - v.BlockID = BlockID{[]byte{1, 2, 3}, PartSetHeader{111, []byte("blockparts")}} - }, true}, - {"Invalid Address", func(v *Vote) { v.ValidatorAddress = make([]byte, 1) }, true}, - {"Invalid ValidatorIndex", func(v *Vote) { v.ValidatorIndex = -1 }, true}, - {"Invalid Signature", func(v *Vote) { v.Signature = nil }, true}, - {"Too big Signature", func(v *Vote) { v.Signature = make([]byte, MaxSignatureSize+1) }, true}, + {"good prevote", examplePrevote(t), func(v *Vote) {}}, + {"good precommit without vote extension", examplePrecommit(t), func(v *Vote) { v.Extension = nil }}, + {"good precommit with vote extension", examplePrecommit(t), func(v *Vote) { v.Extension = []byte("extension") }}, } for _, tc := range testCases { - tc := tc - t.Run(tc.testName, func(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + signVote(ctx, t, privVal, "test_chain_id", tc.vote) + tc.malleateVote(tc.vote) + require.NoError(t, tc.vote.ValidateBasic(), "ValidateBasic for %s", tc.name) + require.NoError(t, tc.vote.ValidateWithExtension(), "ValidateWithExtension for %s", tc.name) + } +} - vote := examplePrecommit(t) - v := vote.ToProto() - err := privVal.SignVote(ctx, "test_chain_id", v) - vote.Signature = v.Signature - require.NoError(t, err) - tc.malleateVote(vote) - assert.Equal(t, tc.expectErr, vote.ValidateBasic() != nil, "Validate Basic had an unexpected result") - }) +func TestInvalidVotes(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + privVal := NewMockPV() + + testCases := []struct { + name string + malleateVote func(*Vote) + }{ + {"negative height", func(v *Vote) { v.Height = -1 }}, + {"negative round", func(v *Vote) { v.Round = -1 }}, + {"invalid block ID", func(v *Vote) { v.BlockID = BlockID{[]byte{1, 2, 3}, PartSetHeader{111, []byte("blockparts")}} }}, + {"invalid address", func(v *Vote) { v.ValidatorAddress = make([]byte, 1) }}, + {"invalid validator index", func(v *Vote) { v.ValidatorIndex = -1 }}, + {"invalid signature", func(v *Vote) { v.Signature = nil }}, + {"oversized signature", func(v *Vote) { v.Signature = make([]byte, MaxSignatureSize+1) }}, + } + for _, tc := range testCases { + prevote := examplePrevote(t) + signVote(ctx, t, privVal, "test_chain_id", prevote) + tc.malleateVote(prevote) + require.Error(t, prevote.ValidateBasic(), "ValidateBasic for %s in invalid prevote", tc.name) + require.Error(t, prevote.ValidateWithExtension(), "ValidateWithExtension for %s in invalid prevote", tc.name) + + precommit := examplePrecommit(t) + signVote(ctx, t, privVal, "test_chain_id", precommit) + tc.malleateVote(precommit) + require.Error(t, precommit.ValidateBasic(), "ValidateBasic for %s in invalid precommit", tc.name) + require.Error(t, precommit.ValidateWithExtension(), "ValidateWithExtension for %s in invalid precommit", tc.name) + } +} + +func TestInvalidPrevotes(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + privVal := NewMockPV() + + testCases := []struct { + name string + malleateVote func(*Vote) + }{ + {"vote extension present", func(v *Vote) { v.Extension = []byte("extension") }}, + {"vote extension signature present", func(v *Vote) { v.ExtensionSignature = []byte("signature") }}, + } + for _, tc := range testCases { + prevote := examplePrevote(t) + signVote(ctx, t, privVal, "test_chain_id", prevote) + tc.malleateVote(prevote) + require.Error(t, prevote.ValidateBasic(), "ValidateBasic for %s", tc.name) + require.Error(t, prevote.ValidateWithExtension(), "ValidateWithExtension for %s", tc.name) + } +} + +func TestInvalidPrecommitExtensions(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + privVal := NewMockPV() + + testCases := []struct { + name string + malleateVote func(*Vote) + }{ + {"vote extension present without signature", func(v *Vote) { + v.Extension = []byte("extension") + v.ExtensionSignature = nil + }}, + // TODO(thane): Re-enable once https://github.com/tendermint/tendermint/issues/8272 is resolved + //{"missing vote extension signature", func(v *Vote) { v.ExtensionSignature = nil }}, + {"oversized vote extension signature", func(v *Vote) { v.ExtensionSignature = make([]byte, MaxSignatureSize+1) }}, + } + for _, tc := range testCases { + precommit := examplePrecommit(t) + signVote(ctx, t, privVal, "test_chain_id", precommit) + tc.malleateVote(precommit) + // We don't expect an error from ValidateBasic, because it doesn't + // handle vote extensions. + require.NoError(t, precommit.ValidateBasic(), "ValidateBasic for %s", tc.name) + require.Error(t, precommit.ValidateWithExtension(), "ValidateWithExtension for %s", tc.name) } } @@ -313,21 +462,28 @@ func TestVoteProtobuf(t *testing.T) { require.NoError(t, err) testCases := []struct { - msg string - v1 *Vote - expPass bool + msg string + vote *Vote + convertsOk bool + passesValidateBasic bool }{ - {"success", vote, true}, - {"fail vote validate basic", &Vote{}, false}, - {"failure nil", nil, false}, + {"success", vote, true, true}, + {"fail vote validate basic", &Vote{}, true, false}, } for _, tc := range testCases { - protoProposal := tc.v1.ToProto() + protoProposal := tc.vote.ToProto() v, err := VoteFromProto(protoProposal) - if tc.expPass { + if tc.convertsOk { require.NoError(t, err) - require.Equal(t, tc.v1, v, tc.msg) + } else { + require.Error(t, err) + } + + err = v.ValidateBasic() + if tc.passesValidateBasic { + require.NoError(t, err) + require.Equal(t, tc.vote, v, tc.msg) } else { require.Error(t, err) }