Browse Source

Merge branch 'master' into rateControl-refactor

pull/7828/head
JayT106 3 years ago
committed by GitHub
parent
commit
7bb87717ae
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
442 changed files with 70132 additions and 3869 deletions
  1. +4
    -1
      .github/CODEOWNERS
  2. +37
    -0
      .github/ISSUE_TEMPLATE/proposal.md
  3. +1
    -1
      .github/workflows/docker.yml
  4. +1
    -1
      .github/workflows/lint.yml
  5. +1
    -1
      .github/workflows/linter.yml
  6. +17
    -0
      .github/workflows/markdown-links.yml
  7. +24
    -0
      .github/workflows/proto-check.yml
  8. +64
    -0
      .github/workflows/proto-dockerfile.yml
  9. +8
    -7
      .gitignore
  10. +11
    -0
      .markdownlint.yml
  11. +13
    -1
      CHANGELOG.md
  12. +6
    -1
      CHANGELOG_PENDING.md
  13. +26
    -4
      Makefile
  14. +24
    -29
      README.md
  15. +28
    -1
      RELEASES.md
  16. +69
    -9
      UPGRADING.md
  17. +2
    -2
      abci/README.md
  18. +18
    -76
      abci/client/client.go
  19. +1
    -4
      abci/client/creators.go
  20. +4
    -13
      abci/client/doc.go
  21. +38
    -237
      abci/client/grpc_client.go
  22. +17
    -148
      abci/client/local_client.go
  23. +0
    -68
      abci/client/mocks/client.go
  24. +87
    -229
      abci/client/socket_client.go
  25. +6
    -6
      abci/example/example_test.go
  26. +282
    -23
      abci/example/kvstore/kvstore.go
  27. +10
    -7
      abci/example/kvstore/kvstore_test.go
  28. +13
    -302
      abci/example/kvstore/persistent_kvstore.go
  29. +9
    -15
      abci/server/grpc_server.go
  30. +90
    -120
      abci/server/socket_server.go
  31. +2
    -2
      abci/types/application.go
  32. +209
    -0
      abci/types/mocks/application.go
  33. +189
    -0
      abci/types/mocks/base.go
  34. +0
    -5
      abci/types/result.go
  35. +3034
    -566
      abci/types/types.pb.go
  36. +14
    -0
      buf.gen.yaml
  37. +16
    -0
      buf.yaml
  38. +13
    -22
      cmd/tendermint/commands/debug/debug.go
  39. +79
    -55
      cmd/tendermint/commands/debug/dump.go
  40. +79
    -72
      cmd/tendermint/commands/debug/kill.go
  41. +1
    -2
      cmd/tendermint/commands/light.go
  42. +4
    -0
      cmd/tendermint/commands/root.go
  43. +1
    -1
      cmd/tendermint/main.go
  44. +41
    -3
      config/config.go
  45. +27
    -0
      config/toml.go
  46. +9
    -5
      docs/.vuepress/config.js
  47. +1
    -0
      docs/.vuepress/redirects
  48. +2
    -2
      docs/README.md
  49. +1
    -1
      docs/app-dev/app-architecture.md
  50. +2
    -2
      docs/app-dev/getting-started.md
  51. +1
    -1
      docs/app-dev/indexing-transactions.md
  52. +8
    -1
      docs/architecture/README.md
  53. +1
    -1
      docs/architecture/adr-001-logging.md
  54. +2
    -2
      docs/architecture/adr-044-lite-client-with-weak-subjectivity.md
  55. +1
    -1
      docs/architecture/adr-045-abci-evidence.md
  56. +11
    -11
      docs/architecture/adr-047-handling-evidence-from-light-client.md
  57. +3
    -3
      docs/architecture/adr-056-light-client-amnesia-attacks.md
  58. +4
    -4
      docs/architecture/adr-067-mempool-refactor.md
  59. +5
    -5
      docs/architecture/adr-068-reverse-sync.md
  60. +0
    -5
      docs/architecture/adr-069-flexible-node-initialization.md
  61. +8
    -8
      docs/architecture/adr-071-proposer-based-timestamps.md
  62. +1
    -1
      docs/architecture/adr-073-libp2p.md
  63. +7
    -7
      docs/architecture/adr-074-timeout-params.md
  64. +35
    -33
      docs/architecture/adr-075-rpc-subscription.md
  65. +109
    -0
      docs/architecture/adr-077-block-retention.md
  66. +82
    -0
      docs/architecture/adr-078-nonzero-genesis.md
  67. +57
    -0
      docs/architecture/adr-079-ed25519-verification.md
  68. +203
    -0
      docs/architecture/adr-080-reverse-sync.md
  69. +23
    -5
      docs/architecture/adr-template.md
  70. BIN
      docs/architecture/img/block-retention.png
  71. +1
    -1
      docs/nodes/configuration.md
  72. +2
    -2
      docs/nodes/logging.md
  73. +1
    -0
      docs/nodes/metrics.md
  74. +1
    -1
      docs/nodes/running-in-production.md
  75. +3
    -3
      docs/nodes/validators.md
  76. +14285
    -20
      docs/package-lock.json
  77. +1
    -0
      docs/package.json
  78. +1
    -1
      docs/pre.sh
  79. +4
    -1
      docs/rfc/README.md
  80. BIN
      docs/rfc/images/abci++.png
  81. BIN
      docs/rfc/images/abci.png
  82. +2
    -2
      docs/rfc/rfc-002-ipc-ecosystem.md
  83. +8
    -8
      docs/rfc/rfc-003-performance-questions.md
  84. +0
    -0
      docs/rfc/rfc-008-do-not-panic.md
  85. +352
    -0
      docs/rfc/rfc-012-custom-indexing.md
  86. +253
    -0
      docs/rfc/rfc-013-abci++.md
  87. +94
    -0
      docs/rfc/rfc-014-semantic-versioning.md
  88. +3
    -3
      docs/roadmap/roadmap.md
  89. +1
    -1
      docs/tendermint-core/block-structure.md
  90. +1
    -1
      docs/tendermint-core/consensus/README.md
  91. +2
    -2
      docs/tendermint-core/light-client.md
  92. +2
    -2
      docs/tendermint-core/rpc.md
  93. +1
    -1
      docs/tendermint-core/subscription.md
  94. +1
    -1
      docs/tendermint-core/using-tendermint.md
  95. +2
    -2
      docs/tutorials/go-built-in.md
  96. +2
    -4
      docs/tutorials/go.md
  97. +0
    -1
      docs/versions
  98. +14
    -12
      go.mod
  99. +31
    -26
      go.sum
  100. +14
    -21
      internal/blocksync/pool.go

+ 4
- 1
.github/CODEOWNERS View File

@ -7,4 +7,7 @@
# global owners are only requested if there isn't a more specific
# codeowner specified below. For this reason, the global codeowners
# are often repeated in package-level definitions.
* @ebuchman @cmwaters @tychoish @williambanfield @creachadair
* @ebuchman @cmwaters @tychoish @williambanfield @creachadair @sergio-mena @jmalicevic @thanethomson @ancazamfir
# Spec related changes can be approved by the protocol design team
/spec @josef-widder @milosevic @cason

+ 37
- 0
.github/ISSUE_TEMPLATE/proposal.md View File

@ -0,0 +1,37 @@
---
name: Protocol Change Proposal
about: Create a proposal to request a change to the protocol
---
<!-- < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < ☺
v ✰ Thanks for opening an issue! ✰
v Before smashing the submit button please review the template.
v Word of caution: Under-specified proposals may be rejected summarily
☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > -->
# Protocol Change Proposal
## Summary
<!-- Short, concise description of the proposed change -->
## Problem Definition
<!-- Why do we need this change?
What problems may be addressed by introducing this change?
What benefits does Tendermint stand to gain by including this change?
Are there any disadvantages of including this change? -->
## Proposal
<!-- Detailed description of requirements of implementation -->
____
#### For Admin Use
- [ ] Not duplicate issue
- [ ] Appropriate labels applied
- [ ] Appropriate contributors tagged
- [ ] Contributor assigned/self-assigned

+ 1
- 1
.github/workflows/docker.yml View File

@ -43,7 +43,7 @@ jobs:
- name: Login to DockerHub
if: ${{ github.event_name != 'pull_request' }}
uses: docker/login-action@v1.12.0
uses: docker/login-action@v1.13.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}


+ 1
- 1
.github/workflows/lint.yml View File

@ -1,4 +1,4 @@
name: Lint
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.


+ 1
- 1
.github/workflows/linter.yml View File

@ -1,4 +1,4 @@
name: Lint
name: Markdown Linter
on:
push:
branches:


+ 17
- 0
.github/workflows/markdown-links.yml View File

@ -0,0 +1,17 @@
name: Check Markdown links
on:
push:
branches:
- master
pull_request:
branches: [master]
jobs:
markdown-link-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: gaurav-nelson/github-action-markdown-link-check@1.0.13
with:
check-modified-files-only: 'yes'

+ 24
- 0
.github/workflows/proto-check.yml View File

@ -0,0 +1,24 @@
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

+ 64
- 0
.github/workflows/proto-dockerfile.yml View File

@ -0,0 +1,64 @@
# 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 }}

+ 8
- 7
.gitignore View File

@ -47,10 +47,11 @@ test/fuzz/**/corpus
test/fuzz/**/crashers
test/fuzz/**/suppressions
test/fuzz/**/*.zip
proto/tendermint/blocksync/types.proto
proto/tendermint/consensus/types.proto
proto/tendermint/mempool/*.proto
proto/tendermint/p2p/*.proto
proto/tendermint/statesync/*.proto
proto/tendermint/types/*.proto
proto/tendermint/version/*.proto
proto/spec/**/*.pb.go
*.aux
*.bbl
*.blg
*.log
*.pdf
*.gz
*.dvi

+ 11
- 0
.markdownlint.yml View File

@ -0,0 +1,11 @@
default: true
MD001: false
MD007: {indent: 4}
MD013: false
MD024: {siblings_only: true}
MD025: false
MD033: false
MD036: false
MD010: false
MD012: false
MD028: false

+ 13
- 1
CHANGELOG.md View File

@ -209,6 +209,18 @@ 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.16
Special thanks to external contributors on this release: @yihuang
### BUG FIXES
- [consensus] [\#7617](https://github.com/tendermint/tendermint/issues/7617) calculate prevote message delay metric (backport #7551) (@williambanfield).
- [consensus] [\#7631](https://github.com/tendermint/tendermint/issues/7631) check proposal non-nil in prevote message delay metric (backport #7625) (@williambanfield).
- [statesync] [\#7885](https://github.com/tendermint/tendermint/issues/7885) statesync: assert app version matches (backport #7856) (@cmwaters).
- [statesync] [\#7881](https://github.com/tendermint/tendermint/issues/7881) fix app hash in state rollback (backport #7837) (@cmwaters).
- [cli] [#7837](https://github.com/tendermint/tendermint/pull/7837) fix app hash in state rollback. (@yihuang).
## v0.34.15
Special thanks to external contributors on this release: @thanethomson
@ -980,7 +992,7 @@ and a validator address plus a timestamp. Note we may remove the validator
address & timestamp fields in the future (see ADR-25).
`lite2` package has been added to solve `lite` issues and introduce weak
subjectivity interface. Refer to the [spec](https://github.com/tendermint/spec/blob/master/spec/consensus/light-client.md) for complete details.
subjectivity interface. Refer to the [spec](./spec/consensus/light-client/) for complete details.
`lite` package is now deprecated and will be removed in v0.34 release.
### BREAKING CHANGES:


+ 6
- 1
CHANGELOG_PENDING.md View File

@ -17,10 +17,13 @@ Special thanks to external contributors on this release:
- [mempool] \#7171 Remove legacy mempool implementation. (@tychoish)
- [rpc] \#7575 Rework how RPC responses are written back via HTTP. (@creachadair)
- [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)
- Apps
- [proto/tendermint] \#6976 Remove core protobuf files in favor of only housing them in the [tendermint/spec](https://github.com/tendermint/spec) repository.
- [tendermint/spec] \#7804 Migrate spec from [spec repo](https://github.com/tendermint/spec).
- [abci] \#7984 Remove the locks preventing concurrent use of ABCI applications by Tendermint. (@tychoish)
- P2P Protocol
@ -63,6 +66,7 @@ Special thanks to external contributors on this release:
- [internal/protoio] \#7325 Optimized `MarshalDelimited` by inlining the common case and using a `sync.Pool` in the worst case. (@odeke-em)
- [consensus] \#6969 remove logic to 'unlock' a locked block.
- [evidence] \#7700 Evidence messages contain single Evidence instead of EvidenceList (@jmalicevic)
- [evidence] \#7802 Evidence pool emits events when evidence is validated and updates a metric when the number of evidence in the evidence pool changes. (@jmalicevic)
- [pubsub] \#7319 Performance improvements for the event query API (@creachadair)
- [node] \#7521 Define concrete type for seed node implementation (@spacech1mp)
- [rpc] \#7612 paginate mempool /unconfirmed_txs rpc endpoint (@spacech1mp)
@ -74,3 +78,4 @@ Special thanks to external contributors on this release:
- fix: assignment copies lock value in `BitArray.UnmarshalJSON()` (@lklimek)
- [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)

+ 26
- 4
Makefile View File

@ -73,22 +73,42 @@ 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
proto-gen:
@docker pull -q tendermintdev/docker-build-proto
@echo "Generating Protobuf files"
@$(DOCKER_PROTO_BUILDER) sh ./scripts/protocgen.sh
@$(DOCKER_PROTO_BUILDER) buf generate --template=./buf.gen.yaml --config ./buf.yaml
.PHONY: proto-gen
proto-lint:
@$(DOCKER_PROTO_BUILDER) buf lint --error-format=json --config ./buf.yaml
.PHONY: proto-lint
proto-format:
@echo "Formatting Protobuf files"
@$(DOCKER_PROTO_BUILDER) find ./ -not -path "./third_party/*" -name *.proto -exec clang-format -i {} \;
@$(DOCKER_PROTO_BUILDER) 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
.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 ###
###############################################################################
@ -202,7 +222,9 @@ build-docs:
mkdir -p ~/output/$${path_prefix} ; \
cp -r .vuepress/dist/* ~/output/$${path_prefix}/ ; \
cp ~/output/$${path_prefix}/index.html ~/output ; \
done < versions ;
done < versions ; \
mkdir -p ~/output/master ; \
cp -r .vuepress/dist/* ~/output/master/
.PHONY: build-docs
###############################################################################


+ 24
- 29
README.md View File

@ -3,7 +3,7 @@
![banner](docs/tendermint-core-image.jpg)
[Byzantine-Fault Tolerant](https://en.wikipedia.org/wiki/Byzantine_fault_tolerance)
[State Machines](https://en.wikipedia.org/wiki/State_machine_replication).
[State Machine Replication](https://en.wikipedia.org/wiki/State_machine_replication).
Or [Blockchain](<https://en.wikipedia.org/wiki/Blockchain_(database)>), for short.
[![version](https://img.shields.io/github/tag/tendermint/tendermint.svg)](https://github.com/tendermint/tendermint/releases/latest)
@ -20,10 +20,14 @@ Or [Blockchain](<https://en.wikipedia.org/wiki/Blockchain_(database)>), for shor
Tendermint Core is a Byzantine Fault Tolerant (BFT) middleware that takes a state transition machine - written in any programming language - and securely replicates it on many machines.
For protocol details, see [the specification](https://github.com/tendermint/spec).
For protocol details, refer to the [Tendermint Specification](./spec/README.md).
For detailed analysis of the consensus protocol, including safety and liveness proofs,
see our recent paper, "[The latest gossip on BFT consensus](https://arxiv.org/abs/1807.04938)".
read our paper, "[The latest gossip on BFT consensus](https://arxiv.org/abs/1807.04938)".
## Documentation
Complete documentation can be found on the [website](https://docs.tendermint.com/).
## Releases
@ -33,7 +37,7 @@ Tendermint has been in the production of private and public environments, most n
See below for more details about [versioning](#versioning).
In any case, if you intend to run Tendermint in production, we're happy to help. You can
contact us [over email](mailto:hello@interchain.berlin) or [join the chat](https://discord.gg/cosmosnetwork).
contact us [over email](mailto:hello@interchain.io) or [join the chat](https://discord.gg/cosmosnetwork).
More on how releases are conducted can be found [here](./RELEASES.md).
@ -52,20 +56,15 @@ to notify you of vulnerabilities and fixes in Tendermint Core. You can subscribe
|-------------|------------------|
| Go version | Go1.17 or higher |
## Documentation
Complete documentation can be found on the [website](https://docs.tendermint.com/master/).
### Install
See the [install instructions](/docs/introduction/install.md).
See the [install instructions](./docs/introduction/install.md).
### Quick Start
- [Single node](/docs/introduction/quick-start.md)
- [Local cluster using docker-compose](/docs/tools/docker-compose.md)
- [Remote cluster using Terraform and Ansible](/docs/tools/terraform-and-ansible.md)
- [Join the Cosmos testnet](https://cosmos.network/testnet)
- [Single node](./docs/introduction/quick-start.md)
- [Local cluster using docker-compose](./docs/tools/docker-compose.md)
- [Remote cluster using Terraform and Ansible](./docs/tools/terraform-and-ansible.md)
## Contributing
@ -73,9 +72,9 @@ Please abide by the [Code of Conduct](CODE_OF_CONDUCT.md) in all interactions.
Before contributing to the project, please take a look at the [contributing guidelines](CONTRIBUTING.md)
and the [style guide](STYLE_GUIDE.md). You may also find it helpful to read the
[specifications](https://github.com/tendermint/spec), watch the [Developer Sessions](/docs/DEV_SESSIONS.md),
[specifications](./spec/README.md),
and familiarize yourself with our
[Architectural Decision Records](https://github.com/tendermint/tendermint/tree/master/docs/architecture).
[Architectural Decision Records (ADRs)](./docs/architecture/README.md) and [Request For Comments (RFCs)](./docs/rfc/README.md).
## Versioning
@ -112,26 +111,22 @@ in [UPGRADING.md](./UPGRADING.md).
## Resources
### Tendermint Core
### Roadmap
We keep a public up-to-date version of our roadmap [here](./docs/roadmap/roadmap.md)
For details about the blockchain data structures and the p2p protocols, see the
[Tendermint specification](https://docs.tendermint.com/master/spec/).
For details on using the software, see the [documentation](/docs/) which is also
hosted at: <https://docs.tendermint.com/master/>
### Tools
### Libraries
Benchmarking is provided by [`tm-load-test`](https://github.com/informalsystems/tm-load-test).
Additional tooling can be found in [/docs/tools](/docs/tools).
- [Cosmos SDK](http://github.com/cosmos/cosmos-sdk); A framework for building applications in Golang
- [Tendermint in Rust](https://github.com/informalsystems/tendermint-rs)
- [ABCI Tower](https://github.com/penumbra-zone/tower-abci)
### Applications
- [Cosmos SDK](http://github.com/cosmos/cosmos-sdk); a cryptocurrency application framework
- [Ethermint](http://github.com/cosmos/ethermint); Ethereum on Tendermint
- [Many more](https://tendermint.com/ecosystem)
- [Cosmos Hub](https://hub.cosmos.network/)
- [Terra](https://www.terra.money/)
- [Celestia](https://celestia.org/)
- [Anoma](https://anoma.network/)
### Research
@ -144,7 +139,7 @@ Additional tooling can be found in [/docs/tools](/docs/tools).
## Join us!
Tendermint Core is maintained by [Interchain GmbH](https://interchain.berlin).
If you'd like to work full-time on Tendermint Core, [we're hiring](https://interchain-gmbh.breezy.hr/p/682fb7e8a6f601-software-engineer-tendermint-core)!
If you'd like to work full-time on Tendermint Core, [we're hiring](https://interchain-gmbh.breezy.hr/)!
Funding for Tendermint Core development comes primarily from the [Interchain Foundation](https://interchain.io),
a Swiss non-profit. The Tendermint trademark is owned by [Tendermint Inc.](https://tendermint.com), the for-profit entity


+ 28
- 1
RELEASES.md View File

@ -42,15 +42,42 @@ In the following example, we'll assume that we're making a backport branch for
the 0.35.x line.
1. Start on `master`
2. Create and push the backport branch:
```sh
git checkout -b v0.35.x
git push origin v0.35.x
```
3. Create a PR to update the documentation directory for the backport branch.
We only maintain RFC and ADR documents on master, to avoid confusion.
In addition, we rewrite Markdown URLs pointing to master to point to the
backport branch, so that generated documentation will link to the correct
versions of files elsewhere in the repository. For context on the latter,
see https://github.com/tendermint/tendermint/issues/7675.
To prepare the PR:
```sh
# Remove the RFC and ADR documents from the backport.
# We only maintain these on master to avoid confusion.
git rm -r docs/rfc docs/architecture
# Update absolute links to point to the backport.
go run ./scripts/linkpatch -recur -target v0.35.x -skip-path docs/DOCS_README.md,docs/README.md docs
# Create and push the PR.
git checkout -b update-docs-v035x
git commit -m "Update docs for v0.35.x backport branch." docs
git push -u origin update-docs-v035x
```
Be sure to merge this PR before making other changes on the newly-created
backport branch.
After doing these steps, go back to `master` and do the following:
1. Tag `master` as the dev branch for the _next_ major release and push it back up.
1. Tag `master` as the dev branch for the _next_ major release and push it up to GitHub.
For example:
```sh
git tag -a v0.36.0-dev -m "Development base for Tendermint v0.36."


+ 69
- 9
UPGRADING.md View File

@ -2,6 +2,67 @@
This guide provides instructions for upgrading to specific versions of Tendermint Core.
## v0.36
### ABCI Changes
#### ABCI++
Coming soon...
#### ABCI Mutex
In previous versions of ABCI, Tendermint was prevented from making
concurrent calls to ABCI implementations by virtue of mutexes in the
implementation of Tendermint's ABCI infrastructure. These mutexes have
been removed from the current implementation and applications will now
be responsible for managing their own concurrency control.
To replicate the prior semantics, ensure that ABCI applications have a
single mutex that protects all ABCI method calls from concurrent
access. You can relax these requirements if your application can
provide safe concurrent access via other means. This safety is an
application concern so be very sure to test the application thoroughly
using realistic workloads and the race detector to ensure your
applications remains correct.
### RPC Changes
Tendermint v0.36 adds a new RPC event subscription API. The existing event
subscription API based on websockets is now deprecated. It will continue to
work throughout the v0.36 release, but the `subscribe`, `unsubscribe`, and
`unsubscribe_all` methods, along with websocket support, will be removed in
Tendermint v0.37. Callers currently using these features should migrate as
soon as is practical to the new API.
To enable the new API, node operators set a new `event-log-window-size`
parameter in the `[rpc]` section of the `config.toml` file. This defines a
duration of time during which the node will log all events published to the
event bus for use by RPC consumers.
Consumers use the new `events` JSON-RPC method to poll for events matching
their query in the log. Unlike the streaming API, events are not discarded if
the caller is slow, loses its connection, or crashes. As long as the client
recovers before its events expire from the log window, it will be able to
replay and catch up after recovering. Also unlike the streaming API, the client
can tell if it has truly missed events because they have expired from the log.
The `events` method is a normal JSON-RPC method, and does not require any
non-standard response processing (in contrast with the old `subscribe`).
Clients can modify their query at any time, and no longer need to coordinate
subscribe and unsubscribe calls to handle multiple queries.
The Go client implementations in the Tendermint Core repository have all been
updated to add a new `Events` method, including the light client proxy.
A new `rpc/client/eventstream` package has also been added to make it easier
for users to update existing use of the streaming API to use the polling API
The `eventstream` package handles polling and delivers matching events to a
callback.
For more detailed information, see [ADR 075](https://tinyurl.com/adr075) which
defines and describes the new API in detail.
## v0.35
### ABCI Changes
@ -113,11 +174,11 @@ To access any of the functionality previously available via the
`node.Node` type, use the `*local.Local` "RPC" client, that exposes
the full RPC interface provided as direct function calls. Import the
`github.com/tendermint/tendermint/rpc/client/local` package and pass
the node service as in the following:
the node service as in the following:
```go
node := node.NewDefault() //construct the node object
// start and set up the node service
// start and set up the node service
client := local.New(node.(local.NodeService))
// use client object to interact with the node
@ -144,10 +205,10 @@ both stacks.
The P2P library was reimplemented in this release. The new implementation is
enabled by default in this version of Tendermint. The legacy implementation is still
included in this version of Tendermint as a backstop to work around unforeseen
production issues. The new and legacy version are interoperable. If necessary,
production issues. The new and legacy version are interoperable. If necessary,
you can enable the legacy implementation in the server configuration file.
To make use of the legacy P2P implemementation add or update the following field of
To make use of the legacy P2P implemementation add or update the following field of
your server's configuration file under the `[p2p]` section:
```toml
@ -172,8 +233,8 @@ in the order in which they were received.
* `priority`: A priority queue of messages.
* `wdrr`: A queue implementing the Weighted Deficit Round Robin algorithm. A
weighted deficit round robin queue is created per peer. Each queue contains a
* `wdrr`: A queue implementing the Weighted Deficit Round Robin algorithm. A
weighted deficit round robin queue is created per peer. Each queue contains a
separate 'flow' for each of the channels of communication that exist between any two
peers. Tendermint maintains a channel per message type between peers. Each WDRR
queue maintains a shared buffered with a fixed capacity through which messages on different
@ -217,7 +278,7 @@ Note also that Tendermint 0.34 also requires Go 1.16 or higher.
were added to support the new State Sync feature.
Previously, syncing a new node to a preexisting network could take days; but with State Sync,
new nodes are able to join a network in a matter of seconds.
Read [the spec](https://docs.tendermint.com/master/spec/abci/apps.html#state-sync)
Read [the spec](https://github.com/tendermint/tendermint/blob/master/spec/abci/apps.md)
if you want to learn more about State Sync, or if you'd like your application to use it.
(If you don't want to support State Sync in your application, you can just implement these new
ABCI methods as no-ops, leaving them empty.)
@ -342,7 +403,6 @@ The `bech32` package has moved to the Cosmos SDK:
### CLI
The `tendermint lite` command has been renamed to `tendermint light` and has a slightly different API.
See [the docs](https://docs.tendermint.com/master/tendermint-core/light-client-protocol.html#http-proxy) for details.
### Light Client
@ -617,7 +677,7 @@ the compilation tag:
Use `cleveldb` tag instead of `gcc` to compile Tendermint with CLevelDB or
use `make build_c` / `make install_c` (full instructions can be found at
<https://tendermint.com/docs/introduction/install.html#compile-with-cleveldb-support>)
<https://docs.tendermint.com/v0.35/introduction/install.html)
## v0.31.0


+ 2
- 2
abci/README.md View File

@ -19,8 +19,8 @@ To get up and running quickly, see the [getting started guide](../docs/app-dev/g
A detailed description of the ABCI methods and message types is contained in:
- [The main spec](https://github.com/tendermint/spec/blob/master/spec/abci/abci.md)
- [A protobuf file](https://github.com/tendermint/spec/blob/master/proto/tendermint/abci/types.proto)
- [The main spec](../spec/abci/abci.md)
- [A protobuf file](../proto/tendermint/abci/types.proto)
- [A Go interface](./types/application.go)
## Protocol Buffers


+ 18
- 76
abci/client/client.go View File

@ -19,8 +19,8 @@ const (
// Client defines an interface for an ABCI client.
//
// All `Async` methods return a `ReqRes` object and an error.
// All `Sync` methods return the appropriate protobuf ResponseXxx struct and an error.
// All methods return the appropriate protobuf ResponseXxx struct and
// an error.
//
// NOTE these are client errors, eg. ABCI socket connectivity issues.
// Application-related errors are reflected in response via ABCI error codes
@ -28,14 +28,8 @@ const (
type Client interface {
service.Service
SetResponseCallback(Callback)
Error() error
// Asynchronous requests
FlushAsync(context.Context) (*ReqRes, error)
CheckTxAsync(context.Context, types.RequestCheckTx) (*ReqRes, error)
// Synchronous requests
Flush(context.Context) error
Echo(ctx context.Context, msg string) (*types.ResponseEcho, error)
Info(context.Context, types.RequestInfo) (*types.ResponseInfo, error)
@ -58,89 +52,37 @@ type Client interface {
// NewClient returns a new ABCI client of the specified transport type.
// It returns an error if the transport is not "socket" or "grpc"
func NewClient(logger log.Logger, addr, transport string, mustConnect bool) (client Client, err error) {
func NewClient(logger log.Logger, addr, transport string, mustConnect bool) (Client, error) {
switch transport {
case "socket":
client = NewSocketClient(logger, addr, mustConnect)
return NewSocketClient(logger, addr, mustConnect), nil
case "grpc":
client = NewGRPCClient(logger, addr, mustConnect)
return NewGRPCClient(logger, addr, mustConnect), nil
default:
err = fmt.Errorf("unknown abci transport %s", transport)
return nil, fmt.Errorf("unknown abci transport %s", transport)
}
return
}
type Callback func(*types.Request, *types.Response)
type ReqRes struct {
type requestAndResponse struct {
*types.Request
*sync.WaitGroup
*types.Response // Not set atomically, so be sure to use WaitGroup.
mtx sync.Mutex
done bool // Gets set to true once *after* WaitGroup.Done().
cb func(*types.Response) // A single callback that may be set.
}
func NewReqRes(req *types.Request) *ReqRes {
return &ReqRes{
Request: req,
WaitGroup: waitGroup1(),
Response: nil,
done: false,
cb: nil,
}
}
// Sets sets the callback. If reqRes is already done, it will call the cb
// immediately. Note, reqRes.cb should not change if reqRes.done and only one
// callback is supported.
func (r *ReqRes) SetCallback(cb func(res *types.Response)) {
r.mtx.Lock()
*types.Response
if r.done {
r.mtx.Unlock()
cb(r.Response)
return
}
r.cb = cb
r.mtx.Unlock()
mtx sync.Mutex
signal chan struct{}
}
// InvokeCallback invokes a thread-safe execution of the configured callback
// if non-nil.
func (r *ReqRes) InvokeCallback() {
r.mtx.Lock()
defer r.mtx.Unlock()
if r.cb != nil {
r.cb(r.Response)
func makeReqRes(req *types.Request) *requestAndResponse {
return &requestAndResponse{
Request: req,
Response: nil,
signal: make(chan struct{}),
}
}
// GetCallback returns the configured callback of the ReqRes object which may be
// nil. Note, it is not safe to concurrently call this in cases where it is
// marked done and SetCallback is called before calling GetCallback as that
// will invoke the callback twice and create a potential race condition.
//
// ref: https://github.com/tendermint/tendermint/issues/5439
func (r *ReqRes) GetCallback() func(*types.Response) {
// markDone marks the ReqRes object as done.
func (r *requestAndResponse) markDone() {
r.mtx.Lock()
defer r.mtx.Unlock()
return r.cb
}
// SetDone marks the ReqRes object as done.
func (r *ReqRes) SetDone() {
r.mtx.Lock()
r.done = true
r.mtx.Unlock()
}
func waitGroup1() (wg *sync.WaitGroup) {
wg = &sync.WaitGroup{}
wg.Add(1)
return
close(r.signal)
}

+ 1
- 4
abci/client/creators.go View File

@ -2,7 +2,6 @@ package abciclient
import (
"fmt"
"sync"
"github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
@ -14,10 +13,8 @@ type Creator func(log.Logger) (Client, error)
// NewLocalCreator returns a Creator for the given app,
// which will be running locally.
func NewLocalCreator(app types.Application) Creator {
mtx := new(sync.Mutex)
return func(logger log.Logger) (Client, error) {
return NewLocalClient(logger, mtx, app), nil
return NewLocalClient(logger, app), nil
}
}


+ 4
- 13
abci/client/doc.go View File

@ -7,23 +7,14 @@
//
// ## Socket client
//
// async: the client maintains an internal buffer of a fixed size. when the
// buffer becomes full, all Async calls will return an error immediately.
//
// sync: the client blocks on 1) enqueuing the Sync request 2) enqueuing the
// Flush requests 3) waiting for the Flush response
// The client blocks for enqueuing the request, for enqueuing the
// Flush to send the request, and for the Flush response to return.
//
// ## Local client
//
// async: global mutex is locked during each call (meaning it's not really async!)
// sync: global mutex is locked during each call
// The global mutex is locked during each call
//
// ## gRPC client
//
// async: gRPC is synchronous, but an internal buffer of a fixed size is used
// to store responses and later call callbacks (separate goroutine per
// response).
//
// sync: waits for all Async calls to complete (essentially what Flush does in
// the socket client) and calls Sync method.
// The client waits for all calls to complete.
package abciclient

+ 38
- 237
abci/client/grpc_client.go View File

@ -24,14 +24,12 @@ type grpcClient struct {
mustConnect bool
client types.ABCIApplicationClient
conn *grpc.ClientConn
chReqRes chan *ReqRes // dispatches "async" responses to callbacks *in order*, needed by mempool
mtx sync.Mutex
addr string
err error
resCb func(*types.Request, *types.Response) // listens to all callbacks
client types.ABCIApplicationClient
conn *grpc.ClientConn
mtx sync.Mutex
addr string
err error
}
var _ Client = (*grpcClient)(nil)
@ -39,25 +37,11 @@ var _ Client = (*grpcClient)(nil)
// NewGRPCClient creates a gRPC client, which will connect to addr upon the
// start. Note Client#Start returns an error if connection is unsuccessful and
// mustConnect is true.
//
// GRPC calls are synchronous, but some callbacks expect to be called
// asynchronously (eg. the mempool expects to be able to lock to remove bad txs
// from cache). To accommodate, we finish each call in its own go-routine,
// which is expensive, but easy - if you want something better, use the socket
// protocol! maybe one day, if people really want it, we use grpc streams, but
// hopefully not :D
func NewGRPCClient(logger log.Logger, addr string, mustConnect bool) Client {
cli := &grpcClient{
logger: logger,
addr: addr,
mustConnect: mustConnect,
// Buffering the channel is needed to make calls appear asynchronous,
// which is required when the caller makes multiple async calls before
// processing callbacks (e.g. due to holding locks). 64 means that a
// caller can make up to 64 async calls before a callback must be
// processed (otherwise it deadlocks). It also means that we can make 64
// gRPC calls while processing a slow callback at the channel head.
chReqRes: make(chan *ReqRes, 64),
}
cli.BaseService = *service.NewBaseService(logger, "grpcClient", cli)
return cli
@ -68,43 +52,6 @@ func dialerFunc(ctx context.Context, addr string) (net.Conn, error) {
}
func (cli *grpcClient) OnStart(ctx context.Context) error {
// This processes asynchronous request/response messages and dispatches
// them to callbacks.
go func() {
// Use a separate function to use defer for mutex unlocks (this handles panics)
callCb := func(reqres *ReqRes) {
cli.mtx.Lock()
defer cli.mtx.Unlock()
reqres.SetDone()
reqres.Done()
// Notify client listener if set
if cli.resCb != nil {
cli.resCb(reqres.Request, reqres.Response)
}
// Notify reqRes listener if set
if cb := reqres.GetCallback(); cb != nil {
cb(reqres.Response)
}
}
for {
select {
case reqres := <-cli.chReqRes:
if reqres != nil {
callCb(reqres)
} else {
cli.logger.Error("Received nil reqres")
}
case <-ctx.Done():
return
}
}
}()
RETRY_LOOP:
for {
conn, err := grpc.Dial(cli.addr,
@ -144,104 +91,19 @@ RETRY_LOOP:
}
func (cli *grpcClient) OnStop() {
if cli.conn != nil {
cli.conn.Close()
}
close(cli.chReqRes)
}
func (cli *grpcClient) StopForError(err error) {
if !cli.IsRunning() {
return
}
cli.mtx.Lock()
if cli.err == nil {
cli.err = err
}
cli.mtx.Unlock()
defer cli.mtx.Unlock()
cli.logger.Error("Stopping abci.grpcClient for error", "err", err)
if err := cli.Stop(); err != nil {
cli.logger.Error("error stopping abci.grpcClient", "err", err)
if cli.conn != nil {
cli.err = cli.conn.Close()
}
}
func (cli *grpcClient) Error() error {
cli.mtx.Lock()
defer cli.mtx.Unlock()
return cli.err
}
// Set listener for all responses
// NOTE: callback may get internally generated flush responses.
func (cli *grpcClient) SetResponseCallback(resCb Callback) {
cli.mtx.Lock()
cli.resCb = resCb
cli.mtx.Unlock()
}
//----------------------------------------
// NOTE: call is synchronous, use ctx to break early if needed
func (cli *grpcClient) FlushAsync(ctx context.Context) (*ReqRes, error) {
req := types.ToRequestFlush()
res, err := cli.client.Flush(ctx, req.GetFlush(), grpc.WaitForReady(true))
if err != nil {
return nil, err
}
return cli.finishAsyncCall(ctx, req, &types.Response{Value: &types.Response_Flush{Flush: res}})
}
// NOTE: call is synchronous, use ctx to break early if needed
func (cli *grpcClient) CheckTxAsync(ctx context.Context, params types.RequestCheckTx) (*ReqRes, error) {
req := types.ToRequestCheckTx(params)
res, err := cli.client.CheckTx(ctx, req.GetCheckTx(), grpc.WaitForReady(true))
if err != nil {
return nil, err
}
return cli.finishAsyncCall(ctx, req, &types.Response{Value: &types.Response_CheckTx{CheckTx: res}})
}
// finishAsyncCall creates a ReqRes for an async call, and immediately populates it
// with the response. We don't complete it until it's been ordered via the channel.
func (cli *grpcClient) finishAsyncCall(ctx context.Context, req *types.Request, res *types.Response) (*ReqRes, error) {
reqres := NewReqRes(req)
reqres.Response = res
select {
case cli.chReqRes <- reqres: // use channel for async responses, since they must be ordered
return reqres, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}
// finishSyncCall waits for an async call to complete. It is necessary to call all
// sync calls asynchronously as well, to maintain call and response ordering via
// the channel, and this method will wait until the async call completes.
func (cli *grpcClient) finishSyncCall(reqres *ReqRes) *types.Response {
// It's possible that the callback is called twice, since the callback can
// be called immediately on SetCallback() in addition to after it has been
// set. This is because completing the ReqRes happens in a separate critical
// section from the one where the callback is called: there is a race where
// SetCallback() is called between completing the ReqRes and dispatching the
// callback.
//
// We also buffer the channel with 1 response, since SetCallback() will be
// called synchronously if the reqres is already completed, in which case
// it will block on sending to the channel since it hasn't gotten around to
// receiving from it yet.
//
// ReqRes should really handle callback dispatch internally, to guarantee
// that it's only called once and avoid the above race conditions.
var once sync.Once
ch := make(chan *types.Response, 1)
reqres.SetCallback(func(res *types.Response) {
once.Do(func() {
ch <- res
})
})
return <-ch
return cli.err
}
//----------------------------------------
@ -249,122 +111,61 @@ func (cli *grpcClient) finishSyncCall(reqres *ReqRes) *types.Response {
func (cli *grpcClient) Flush(ctx context.Context) error { return nil }
func (cli *grpcClient) Echo(ctx context.Context, msg string) (*types.ResponseEcho, error) {
req := types.ToRequestEcho(msg)
return cli.client.Echo(ctx, req.GetEcho(), grpc.WaitForReady(true))
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) {
req := types.ToRequestInfo(params)
return cli.client.Info(ctx, req.GetInfo(), grpc.WaitForReady(true))
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) {
reqres, err := cli.CheckTxAsync(ctx, params)
if err != nil {
return nil, err
}
return cli.finishSyncCall(reqres).GetCheckTx(), cli.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) {
req := types.ToRequestQuery(params)
return cli.client.Query(ctx, req.GetQuery(), grpc.WaitForReady(true))
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))
}
func (cli *grpcClient) Commit(ctx context.Context) (*types.ResponseCommit, error) {
req := types.ToRequestCommit()
return cli.client.Commit(ctx, req.GetCommit(), grpc.WaitForReady(true))
return cli.client.Commit(ctx, types.ToRequestCommit().GetCommit(), grpc.WaitForReady(true))
}
func (cli *grpcClient) InitChain(
ctx context.Context,
params types.RequestInitChain,
) (*types.ResponseInitChain, error) {
req := types.ToRequestInitChain(params)
return cli.client.InitChain(ctx, req.GetInitChain(), grpc.WaitForReady(true))
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) {
req := types.ToRequestListSnapshots(params)
return cli.client.ListSnapshots(ctx, req.GetListSnapshots(), grpc.WaitForReady(true))
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) {
req := types.ToRequestOfferSnapshot(params)
return cli.client.OfferSnapshot(ctx, req.GetOfferSnapshot(), grpc.WaitForReady(true))
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) {
req := types.ToRequestLoadSnapshotChunk(params)
return cli.client.LoadSnapshotChunk(ctx, req.GetLoadSnapshotChunk(), grpc.WaitForReady(true))
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) {
req := types.ToRequestApplySnapshotChunk(params)
return cli.client.ApplySnapshotChunk(ctx, req.GetApplySnapshotChunk(), grpc.WaitForReady(true))
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) {
req := types.ToRequestPrepareProposal(params)
return cli.client.PrepareProposal(ctx, req.GetPrepareProposal(), grpc.WaitForReady(true))
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) {
req := types.ToRequestProcessProposal(params)
return cli.client.ProcessProposal(ctx, req.GetProcessProposal(), grpc.WaitForReady(true))
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) {
req := types.ToRequestExtendVote(params)
return cli.client.ExtendVote(ctx, req.GetExtendVote(), grpc.WaitForReady(true))
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) {
req := types.ToRequestVerifyVoteExtension(params)
return cli.client.VerifyVoteExtension(ctx, req.GetVerifyVoteExtension(), grpc.WaitForReady(true))
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) {
req := types.ToRequestFinalizeBlock(params)
return cli.client.FinalizeBlock(ctx, req.GetFinalizeBlock(), grpc.WaitForReady(true))
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))
}

+ 17
- 148
abci/client/local_client.go View File

@ -2,7 +2,6 @@ package abciclient
import (
"context"
"sync"
types "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
@ -15,10 +14,7 @@ import (
// RPC endpoint), but defers are used everywhere for the sake of consistency.
type localClient struct {
service.BaseService
mtx *sync.Mutex
types.Application
Callback
}
var _ Client = (*localClient)(nil)
@ -26,13 +22,9 @@ var _ Client = (*localClient)(nil)
// NewLocalClient creates a local client, which will be directly calling the
// methods of the given app.
//
// Both Async and Sync methods ignore the given context.Context parameter.
func NewLocalClient(logger log.Logger, mtx *sync.Mutex, app types.Application) Client {
if mtx == nil {
mtx = new(sync.Mutex)
}
// The client methods ignore their context argument.
func NewLocalClient(logger log.Logger, app types.Application) Client {
cli := &localClient{
mtx: mtx,
Application: app,
}
cli.BaseService = *service.NewBaseService(logger, "localClient", cli)
@ -41,205 +33,82 @@ func NewLocalClient(logger log.Logger, mtx *sync.Mutex, app types.Application) C
func (*localClient) OnStart(context.Context) error { return nil }
func (*localClient) OnStop() {}
func (app *localClient) SetResponseCallback(cb Callback) {
app.mtx.Lock()
defer app.mtx.Unlock()
app.Callback = cb
}
// TODO: change types.Application to include Error()?
func (app *localClient) Error() error {
return nil
}
func (app *localClient) FlushAsync(ctx context.Context) (*ReqRes, error) {
// Do nothing
return newLocalReqRes(types.ToRequestFlush(), nil), nil
}
func (app *localClient) CheckTxAsync(ctx context.Context, req types.RequestCheckTx) (*ReqRes, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
res := app.Application.CheckTx(req)
return app.callback(
types.ToRequestCheckTx(req),
types.ToResponseCheckTx(res),
), nil
}
func (*localClient) Error() error { return nil }
//-------------------------------------------------------
func (app *localClient) Flush(ctx context.Context) error {
return nil
}
func (*localClient) Flush(context.Context) error { return nil }
func (app *localClient) Echo(ctx context.Context, msg string) (*types.ResponseEcho, error) {
func (app *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) {
app.mtx.Lock()
defer app.mtx.Unlock()
res := app.Application.Info(req)
return &res, nil
}
func (app *localClient) CheckTx(
ctx context.Context,
req types.RequestCheckTx,
) (*types.ResponseCheckTx, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
func (app *localClient) CheckTx(_ context.Context, req types.RequestCheckTx) (*types.ResponseCheckTx, error) {
res := app.Application.CheckTx(req)
return &res, nil
}
func (app *localClient) Query(
ctx context.Context,
req types.RequestQuery,
) (*types.ResponseQuery, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
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) {
app.mtx.Lock()
defer app.mtx.Unlock()
res := app.Application.Commit()
return &res, nil
}
func (app *localClient) InitChain(
ctx context.Context,
req types.RequestInitChain,
) (*types.ResponseInitChain, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
func (app *localClient) InitChain(_ context.Context, req types.RequestInitChain) (*types.ResponseInitChain, error) {
res := app.Application.InitChain(req)
return &res, nil
}
func (app *localClient) ListSnapshots(
ctx context.Context,
req types.RequestListSnapshots,
) (*types.ResponseListSnapshots, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
func (app *localClient) ListSnapshots(_ context.Context, req types.RequestListSnapshots) (*types.ResponseListSnapshots, error) {
res := app.Application.ListSnapshots(req)
return &res, nil
}
func (app *localClient) OfferSnapshot(
ctx context.Context,
req types.RequestOfferSnapshot,
) (*types.ResponseOfferSnapshot, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
func (app *localClient) OfferSnapshot(_ context.Context, req types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) {
res := app.Application.OfferSnapshot(req)
return &res, nil
}
func (app *localClient) LoadSnapshotChunk(
ctx context.Context,
req types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
func (app *localClient) LoadSnapshotChunk(_ context.Context, req types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) {
res := app.Application.LoadSnapshotChunk(req)
return &res, nil
}
func (app *localClient) ApplySnapshotChunk(
ctx context.Context,
req types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
func (app *localClient) ApplySnapshotChunk(_ context.Context, req types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) {
res := app.Application.ApplySnapshotChunk(req)
return &res, nil
}
func (app *localClient) PrepareProposal(
ctx context.Context,
req types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
func (app *localClient) PrepareProposal(_ context.Context, req types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) {
res := app.Application.PrepareProposal(req)
return &res, nil
}
func (app *localClient) ProcessProposal(
ctx context.Context,
req types.RequestProcessProposal) (*types.ResponseProcessProposal, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
func (app *localClient) ProcessProposal(_ context.Context, req types.RequestProcessProposal) (*types.ResponseProcessProposal, error) {
res := app.Application.ProcessProposal(req)
return &res, nil
}
func (app *localClient) ExtendVote(
ctx context.Context,
req types.RequestExtendVote) (*types.ResponseExtendVote, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
func (app *localClient) ExtendVote(_ context.Context, req types.RequestExtendVote) (*types.ResponseExtendVote, error) {
res := app.Application.ExtendVote(req)
return &res, nil
}
func (app *localClient) VerifyVoteExtension(
ctx context.Context,
req types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
func (app *localClient) VerifyVoteExtension(_ context.Context, req types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) {
res := app.Application.VerifyVoteExtension(req)
return &res, nil
}
func (app *localClient) FinalizeBlock(
ctx context.Context,
req types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
func (app *localClient) FinalizeBlock(_ context.Context, req types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) {
res := app.Application.FinalizeBlock(req)
return &res, nil
}
//-------------------------------------------------------
func (app *localClient) callback(req *types.Request, res *types.Response) *ReqRes {
app.Callback(req, res)
return newLocalReqRes(req, res)
}
func newLocalReqRes(req *types.Request, res *types.Response) *ReqRes {
reqRes := NewReqRes(req)
reqRes.Response = res
reqRes.SetDone()
return reqRes
}

+ 0
- 68
abci/client/mocks/client.go View File

@ -5,10 +5,7 @@ package mocks
import (
context "context"
abciclient "github.com/tendermint/tendermint/abci/client"
mock "github.com/stretchr/testify/mock"
types "github.com/tendermint/tendermint/abci/types"
)
@ -63,29 +60,6 @@ func (_m *Client) CheckTx(_a0 context.Context, _a1 types.RequestCheckTx) (*types
return r0, r1
}
// CheckTxAsync provides a mock function with given fields: _a0, _a1
func (_m *Client) CheckTxAsync(_a0 context.Context, _a1 types.RequestCheckTx) (*abciclient.ReqRes, error) {
ret := _m.Called(_a0, _a1)
var r0 *abciclient.ReqRes
if rf, ok := ret.Get(0).(func(context.Context, types.RequestCheckTx) *abciclient.ReqRes); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*abciclient.ReqRes)
}
}
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 *Client) Commit(_a0 context.Context) (*types.ResponseCommit, error) {
ret := _m.Called(_a0)
@ -206,29 +180,6 @@ func (_m *Client) Flush(_a0 context.Context) error {
return r0
}
// FlushAsync provides a mock function with given fields: _a0
func (_m *Client) FlushAsync(_a0 context.Context) (*abciclient.ReqRes, error) {
ret := _m.Called(_a0)
var r0 *abciclient.ReqRes
if rf, ok := ret.Get(0).(func(context.Context) *abciclient.ReqRes); ok {
r0 = rf(_a0)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*abciclient.ReqRes)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Info provides a mock function with given fields: _a0, _a1
func (_m *Client) Info(_a0 context.Context, _a1 types.RequestInfo) (*types.ResponseInfo, error) {
ret := _m.Called(_a0, _a1)
@ -427,11 +378,6 @@ func (_m *Client) Query(_a0 context.Context, _a1 types.RequestQuery) (*types.Res
return r0, r1
}
// SetResponseCallback provides a mock function with given fields: _a0
func (_m *Client) SetResponseCallback(_a0 abciclient.Callback) {
_m.Called(_a0)
}
// Start provides a mock function with given fields: _a0
func (_m *Client) Start(_a0 context.Context) error {
ret := _m.Called(_a0)
@ -446,20 +392,6 @@ func (_m *Client) Start(_a0 context.Context) error {
return r0
}
// String provides a mock function with given fields:
func (_m *Client) String() string {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
// VerifyVoteExtension provides a mock function with given fields: _a0, _a1
func (_m *Client) VerifyVoteExtension(_a0 context.Context, _a1 types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) {
ret := _m.Called(_a0, _a1)


+ 87
- 229
abci/client/socket_client.go View File

@ -8,7 +8,6 @@ import (
"fmt"
"io"
"net"
"reflect"
"sync"
"time"
@ -24,11 +23,6 @@ const (
reqQueueSize = 256
)
type reqResWithContext struct {
R *ReqRes
C context.Context // if context.Err is not nil, reqRes will be thrown away (ignored)
}
// This is goroutine-safe, but users should beware that the application in
// general is not meant to be interfaced with concurrent callers.
type socketClient struct {
@ -39,12 +33,11 @@ type socketClient struct {
mustConnect bool
conn net.Conn
reqQueue chan *reqResWithContext
reqQueue chan *requestAndResponse
mtx sync.Mutex
err error
reqSent *list.List // list of requests sent, waiting for response
resCb func(*types.Request, *types.Response) // called on all requests, if set.
reqSent *list.List // list of requests sent, waiting for response
}
var _ Client = (*socketClient)(nil)
@ -55,11 +48,10 @@ var _ Client = (*socketClient)(nil)
func NewSocketClient(logger log.Logger, addr string, mustConnect bool) Client {
cli := &socketClient{
logger: logger,
reqQueue: make(chan *reqResWithContext, reqQueueSize),
reqQueue: make(chan *requestAndResponse, reqQueueSize),
mustConnect: mustConnect,
addr: addr,
reqSent: list.New(),
resCb: nil,
}
cli.BaseService = *service.NewBaseService(logger, "socketClient", cli)
return cli
@ -99,7 +91,10 @@ func (cli *socketClient) OnStop() {
cli.conn.Close()
}
cli.drainQueue()
// this timeout is arbitrary.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cli.drainQueue(ctx)
}
// Error returns an error if the client was stopped abruptly.
@ -109,16 +104,6 @@ func (cli *socketClient) Error() error {
return cli.err
}
// SetResponseCallback sets a callback, which will be executed for each
// non-error & non-empty response from the server.
//
// NOTE: callback may get internally generated flush responses.
func (cli *socketClient) SetResponseCallback(resCb Callback) {
cli.mtx.Lock()
defer cli.mtx.Unlock()
cli.resCb = resCb
}
//----------------------------------------
func (cli *socketClient) sendRequestsRoutine(ctx context.Context, conn io.Writer) {
@ -132,16 +117,13 @@ func (cli *socketClient) sendRequestsRoutine(ctx context.Context, conn io.Writer
return
}
if reqres.C.Err() != nil {
cli.logger.Debug("Request's context is done", "req", reqres.R, "err", reqres.C.Err())
continue
}
cli.willSendReq(reqres.R)
cli.willSendReq(reqres)
if err := types.WriteMessage(reqres.R.Request, bw); err != nil {
if err := types.WriteMessage(reqres.Request, bw); err != nil {
cli.stopForError(fmt.Errorf("write to buffer: %w", err))
return
}
if err := bw.Flush(); err != nil {
cli.stopForError(fmt.Errorf("flush buffer: %w", err))
return
@ -156,23 +138,20 @@ func (cli *socketClient) recvResponseRoutine(ctx context.Context, conn io.Reader
if ctx.Err() != nil {
return
}
var res = &types.Response{}
err := types.ReadMessage(r, res)
if err != nil {
res := &types.Response{}
if err := types.ReadMessage(r, res); err != nil {
cli.stopForError(fmt.Errorf("read message: %w", err))
return
}
// cli.logger.Debug("Received response", "responseType", reflect.TypeOf(res), "response", res)
switch r := res.Value.(type) {
case *types.Response_Exception: // app responded with error
// XXX After setting cli.err, release waiters (e.g. reqres.Done())
cli.stopForError(errors.New(r.Exception.Error))
return
default:
err := cli.didRecvResponse(res)
if err != nil {
if err := cli.didRecvResponse(res); err != nil {
cli.stopForError(err)
return
}
@ -180,7 +159,7 @@ func (cli *socketClient) recvResponseRoutine(ctx context.Context, conn io.Reader
}
}
func (cli *socketClient) willSendReq(reqres *ReqRes) {
func (cli *socketClient) willSendReq(reqres *requestAndResponse) {
cli.mtx.Lock()
defer cli.mtx.Unlock()
cli.reqSent.PushBack(reqres)
@ -193,307 +172,184 @@ func (cli *socketClient) didRecvResponse(res *types.Response) error {
// Get the first ReqRes.
next := cli.reqSent.Front()
if next == nil {
return fmt.Errorf("unexpected %v when nothing expected", reflect.TypeOf(res.Value))
return fmt.Errorf("unexpected %T when nothing expected", res.Value)
}
reqres := next.Value.(*ReqRes)
reqres := next.Value.(*requestAndResponse)
if !resMatchesReq(reqres.Request, res) {
return fmt.Errorf("unexpected %v when response to %v expected",
reflect.TypeOf(res.Value), reflect.TypeOf(reqres.Request.Value))
return fmt.Errorf("unexpected %T when response to %T expected", res.Value, reqres.Request.Value)
}
reqres.Response = res
reqres.Done() // release waiters
reqres.markDone() // release waiters
cli.reqSent.Remove(next) // pop first item from linked list
// Notify client listener if set (global callback).
if cli.resCb != nil {
cli.resCb(reqres.Request, res)
}
// Notify reqRes listener if set (request specific callback).
//
// NOTE: It is possible this callback isn't set on the reqres object. At this
// point, in which case it will be called after, when it is set.
reqres.InvokeCallback()
return nil
}
//----------------------------------------
func (cli *socketClient) FlushAsync(ctx context.Context) (*ReqRes, error) {
return cli.queueRequestAsync(ctx, types.ToRequestFlush())
}
func (cli *socketClient) CheckTxAsync(ctx context.Context, req types.RequestCheckTx) (*ReqRes, error) {
return cli.queueRequestAsync(ctx, types.ToRequestCheckTx(req))
}
//----------------------------------------
func (cli *socketClient) Flush(ctx context.Context) error {
reqRes, err := cli.queueRequest(ctx, types.ToRequestFlush(), true)
_, err := cli.doRequest(ctx, types.ToRequestFlush())
if err != nil {
return queueErr(err)
}
if err := cli.Error(); err != nil {
return err
}
gotResp := make(chan struct{})
go func() {
// NOTE: if we don't flush the queue, its possible to get stuck here
reqRes.Wait()
close(gotResp)
}()
select {
case <-gotResp:
return cli.Error()
case <-ctx.Done():
return ctx.Err()
}
return nil
}
func (cli *socketClient) Echo(ctx context.Context, msg string) (*types.ResponseEcho, error) {
reqres, err := cli.queueRequestAndFlush(ctx, types.ToRequestEcho(msg))
res, err := cli.doRequest(ctx, types.ToRequestEcho(msg))
if err != nil {
return nil, err
}
return reqres.Response.GetEcho(), nil
return res.GetEcho(), nil
}
func (cli *socketClient) Info(
ctx context.Context,
req types.RequestInfo,
) (*types.ResponseInfo, error) {
reqres, err := cli.queueRequestAndFlush(ctx, types.ToRequestInfo(req))
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 reqres.Response.GetInfo(), nil
return res.GetInfo(), nil
}
func (cli *socketClient) CheckTx(
ctx context.Context,
req types.RequestCheckTx,
) (*types.ResponseCheckTx, error) {
reqres, err := cli.queueRequestAndFlush(ctx, types.ToRequestCheckTx(req))
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 reqres.Response.GetCheckTx(), nil
return res.GetCheckTx(), nil
}
func (cli *socketClient) Query(
ctx context.Context,
req types.RequestQuery,
) (*types.ResponseQuery, error) {
reqres, err := cli.queueRequestAndFlush(ctx, types.ToRequestQuery(req))
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 reqres.Response.GetQuery(), nil
return res.GetQuery(), nil
}
func (cli *socketClient) Commit(ctx context.Context) (*types.ResponseCommit, error) {
reqres, err := cli.queueRequestAndFlush(ctx, types.ToRequestCommit())
res, err := cli.doRequest(ctx, types.ToRequestCommit())
if err != nil {
return nil, err
}
return reqres.Response.GetCommit(), nil
return res.GetCommit(), nil
}
func (cli *socketClient) InitChain(
ctx context.Context,
req types.RequestInitChain,
) (*types.ResponseInitChain, error) {
reqres, err := cli.queueRequestAndFlush(ctx, types.ToRequestInitChain(req))
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 reqres.Response.GetInitChain(), nil
return res.GetInitChain(), nil
}
func (cli *socketClient) ListSnapshots(
ctx context.Context,
req types.RequestListSnapshots,
) (*types.ResponseListSnapshots, error) {
reqres, err := cli.queueRequestAndFlush(ctx, types.ToRequestListSnapshots(req))
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 reqres.Response.GetListSnapshots(), nil
return res.GetListSnapshots(), nil
}
func (cli *socketClient) OfferSnapshot(
ctx context.Context,
req types.RequestOfferSnapshot,
) (*types.ResponseOfferSnapshot, error) {
reqres, err := cli.queueRequestAndFlush(ctx, types.ToRequestOfferSnapshot(req))
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 reqres.Response.GetOfferSnapshot(), nil
return res.GetOfferSnapshot(), nil
}
func (cli *socketClient) LoadSnapshotChunk(
ctx context.Context,
req types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) {
reqres, err := cli.queueRequestAndFlush(ctx, types.ToRequestLoadSnapshotChunk(req))
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 reqres.Response.GetLoadSnapshotChunk(), nil
return res.GetLoadSnapshotChunk(), nil
}
func (cli *socketClient) ApplySnapshotChunk(
ctx context.Context,
req types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) {
reqres, err := cli.queueRequestAndFlush(ctx, types.ToRequestApplySnapshotChunk(req))
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 reqres.Response.GetApplySnapshotChunk(), nil
return res.GetApplySnapshotChunk(), nil
}
func (cli *socketClient) PrepareProposal(
ctx context.Context,
req types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) {
reqres, err := cli.queueRequestAndFlush(ctx, types.ToRequestPrepareProposal(req))
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 reqres.Response.GetPrepareProposal(), nil
return res.GetPrepareProposal(), nil
}
func (cli *socketClient) ProcessProposal(
ctx context.Context,
req types.RequestProcessProposal,
) (*types.ResponseProcessProposal, error) {
reqres, err := cli.queueRequestAndFlush(ctx, types.ToRequestProcessProposal(req))
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 reqres.Response.GetProcessProposal(), nil
return res.GetProcessProposal(), nil
}
func (cli *socketClient) ExtendVote(
ctx context.Context,
req types.RequestExtendVote) (*types.ResponseExtendVote, error) {
reqres, err := cli.queueRequestAndFlush(ctx, types.ToRequestExtendVote(req))
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 reqres.Response.GetExtendVote(), nil
return res.GetExtendVote(), nil
}
func (cli *socketClient) VerifyVoteExtension(
ctx context.Context,
req types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) {
reqres, err := cli.queueRequestAndFlush(ctx, types.ToRequestVerifyVoteExtension(req))
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 reqres.Response.GetVerifyVoteExtension(), nil
return res.GetVerifyVoteExtension(), nil
}
func (cli *socketClient) FinalizeBlock(
ctx context.Context,
req types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) {
reqres, err := cli.queueRequestAndFlush(ctx, types.ToRequestFinalizeBlock(req))
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 reqres.Response.GetFinalizeBlock(), nil
return res.GetFinalizeBlock(), nil
}
//----------------------------------------
// queueRequest enqueues req onto the queue. If the queue is full, it ether
// returns an error (sync=false) or blocks (sync=true).
//
// When sync=true, ctx can be used to break early. When sync=false, ctx will be
// used later to determine if request should be dropped (if ctx.Err is
// non-nil).
//
// The caller is responsible for checking cli.Error.
func (cli *socketClient) queueRequest(ctx context.Context, req *types.Request, sync bool) (*ReqRes, error) {
reqres := NewReqRes(req)
if sync {
select {
case cli.reqQueue <- &reqResWithContext{R: reqres, C: context.Background()}:
case <-ctx.Done():
return nil, ctx.Err()
}
} else {
select {
case cli.reqQueue <- &reqResWithContext{R: reqres, C: ctx}:
default:
return nil, errors.New("buffer is full")
}
}
return reqres, nil
}
func (cli *socketClient) queueRequestAsync(
ctx context.Context,
req *types.Request,
) (*ReqRes, error) {
func (cli *socketClient) doRequest(ctx context.Context, req *types.Request) (*types.Response, error) {
reqres := makeReqRes(req)
reqres, err := cli.queueRequest(ctx, req, false)
if err != nil {
return nil, queueErr(err)
select {
case cli.reqQueue <- reqres:
case <-ctx.Done():
return nil, fmt.Errorf("can't queue req: %w", ctx.Err())
}
return reqres, cli.Error()
}
func (cli *socketClient) queueRequestAndFlush(
ctx context.Context,
req *types.Request,
) (*ReqRes, error) {
reqres, err := cli.queueRequest(ctx, req, true)
if err != nil {
return nil, queueErr(err)
}
select {
case <-reqres.signal:
if err := cli.Error(); err != nil {
return nil, err
}
if err := cli.Flush(ctx); err != nil {
return nil, err
return reqres.Response, nil
case <-ctx.Done():
return nil, ctx.Err()
}
return reqres, cli.Error()
}
func queueErr(e error) error {
return fmt.Errorf("can't queue req: %w", e)
}
// drainQueue marks as complete and discards all remaining pending requests
// from the queue.
func (cli *socketClient) drainQueue() {
func (cli *socketClient) drainQueue(ctx context.Context) {
cli.mtx.Lock()
defer cli.mtx.Unlock()
// mark all in-flight messages as resolved (they will get cli.Error())
for req := cli.reqSent.Front(); req != nil; req = req.Next() {
reqres := req.Value.(*ReqRes)
reqres.Done()
reqres := req.Value.(*requestAndResponse)
reqres.markDone()
}
// Mark all queued messages as resolved.
@ -503,8 +359,10 @@ func (cli *socketClient) drainQueue() {
// See https://github.com/tendermint/tendermint/issues/6996.
for {
select {
case <-ctx.Done():
return
case reqres := <-cli.reqQueue:
reqres.R.Done()
reqres.markDone()
default:
return
}
@ -529,6 +387,8 @@ func resMatchesReq(req *types.Request, res *types.Response) (ok bool) {
_, ok = res.Value.(*types.Response_Query)
case *types.Request_InitChain:
_, ok = res.Value.(*types.Response_InitChain)
case *types.Request_ProcessProposal:
_, ok = res.Value.(*types.Response_ProcessProposal)
case *types.Request_PrepareProposal:
_, ok = res.Value.(*types.Response_PrepareProposal)
case *types.Request_ExtendVote:
@ -559,7 +419,5 @@ func (cli *socketClient) stopForError(err error) {
cli.mtx.Unlock()
cli.logger.Info("Stopping abci.socketClient", "reason", err)
if err := cli.Stop(); err != nil {
cli.logger.Error("error stopping abci.socketClient", "err", err)
}
cli.Stop()
}

+ 6
- 6
abci/example/example_test.go View File

@ -31,18 +31,18 @@ func init() {
func TestKVStore(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
logger := log.NewTestingLogger(t)
logger := log.NewNopLogger()
logger.Info("### Testing KVStore")
t.Log("### Testing KVStore")
testBulk(ctx, t, logger, kvstore.NewApplication())
}
func TestBaseApp(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
logger := log.NewTestingLogger(t)
logger := log.NewNopLogger()
logger.Info("### Testing BaseApp")
t.Log("### Testing BaseApp")
testBulk(ctx, t, logger, types.NewBaseApplication())
}
@ -50,9 +50,9 @@ func TestGRPC(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
logger := log.NewTestingLogger(t)
logger := log.NewNopLogger()
logger.Info("### Testing GRPC")
t.Log("### Testing GRPC")
testGRPCSync(ctx, t, logger, types.NewGRPCApplication(types.NewBaseApplication()))
}


+ 282
- 23
abci/example/kvstore/kvstore.go View File

@ -2,14 +2,21 @@ package kvstore
import (
"bytes"
"encoding/base64"
"encoding/binary"
"encoding/json"
"fmt"
"strconv"
"strings"
"sync"
dbm "github.com/tendermint/tm-db"
"github.com/tendermint/tendermint/abci/example/code"
"github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/encoding"
"github.com/tendermint/tendermint/libs/log"
cryptoproto "github.com/tendermint/tendermint/proto/tendermint/crypto"
"github.com/tendermint/tendermint/version"
)
@ -65,17 +72,41 @@ var _ types.Application = (*Application)(nil)
type Application struct {
types.BaseApplication
mu sync.Mutex
state State
RetainBlocks int64 // blocks to retain after commit (via ResponseCommit.RetainHeight)
logger log.Logger
// validator set
ValUpdates []types.ValidatorUpdate
valAddrToPubKeyMap map[string]cryptoproto.PublicKey
}
func NewApplication() *Application {
state := loadState(dbm.NewMemDB())
return &Application{state: state}
return &Application{
logger: log.NewNopLogger(),
state: loadState(dbm.NewMemDB()),
valAddrToPubKeyMap: make(map[string]cryptoproto.PublicKey),
}
}
func (app *Application) Info(req types.RequestInfo) (resInfo types.ResponseInfo) {
func (app *Application) InitChain(req types.RequestInitChain) types.ResponseInitChain {
app.mu.Lock()
defer app.mu.Unlock()
for _, v := range req.Validators {
r := app.updateValidator(v)
if r.IsErr() {
app.logger.Error("error updating validators", "r", r)
panic("problem updating validators")
}
}
return types.ResponseInitChain{}
}
func (app *Application) Info(req types.RequestInfo) types.ResponseInfo {
app.mu.Lock()
defer app.mu.Unlock()
return types.ResponseInfo{
Data: fmt.Sprintf("{\"size\":%v}", app.state.Size),
Version: version.ABCIVersion,
@ -85,8 +116,20 @@ func (app *Application) Info(req types.RequestInfo) (resInfo types.ResponseInfo)
}
}
// tx is either "key=value" or just arbitrary bytes
func (app *Application) HandleTx(tx []byte) *types.ResponseDeliverTx {
// tx is either "val:pubkey!power" or "key=value" or just arbitrary bytes
func (app *Application) handleTx(tx []byte) *types.ResponseDeliverTx {
// if it starts with "val:", update the validator set
// format is "val:pubkey!power"
if isValidatorTx(tx) {
// update validators in the merkle tree
// and in app.ValUpdates
return app.execValidatorTx(tx)
}
if isPrepareTx(tx) {
return app.execPrepareTx(tx)
}
var key, value string
parts := bytes.Split(tx, []byte("="))
if len(parts) == 2 {
@ -116,19 +159,53 @@ func (app *Application) HandleTx(tx []byte) *types.ResponseDeliverTx {
return &types.ResponseDeliverTx{Code: code.CodeTypeOK, Events: events}
}
func (app *Application) Close() error {
app.mu.Lock()
defer app.mu.Unlock()
return app.state.db.Close()
}
func (app *Application) FinalizeBlock(req types.RequestFinalizeBlock) types.ResponseFinalizeBlock {
txs := make([]*types.ResponseDeliverTx, len(req.Txs))
app.mu.Lock()
defer app.mu.Unlock()
// reset valset changes
app.ValUpdates = make([]types.ValidatorUpdate, 0)
// Punish validators who committed equivocation.
for _, ev := range req.ByzantineValidators {
if ev.Type == types.EvidenceType_DUPLICATE_VOTE {
addr := string(ev.Validator.Address)
if pubKey, ok := app.valAddrToPubKeyMap[addr]; ok {
app.updateValidator(types.ValidatorUpdate{
PubKey: pubKey,
Power: ev.Validator.Power - 1,
})
app.logger.Info("Decreased val power by 1 because of the equivocation",
"val", addr)
} else {
panic(fmt.Errorf("wanted to punish val %q but can't find it", addr))
}
}
}
respTxs := make([]*types.ResponseDeliverTx, len(req.Txs))
for i, tx := range req.Txs {
txs[i] = app.HandleTx(tx)
respTxs[i] = app.handleTx(tx)
}
return types.ResponseFinalizeBlock{Txs: txs}
return types.ResponseFinalizeBlock{Txs: respTxs, ValidatorUpdates: app.ValUpdates}
}
func (app *Application) CheckTx(req types.RequestCheckTx) types.ResponseCheckTx {
func (*Application) CheckTx(req types.RequestCheckTx) types.ResponseCheckTx {
return types.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1}
}
func (app *Application) Commit() types.ResponseCommit {
app.mu.Lock()
defer app.mu.Unlock()
// Using a memdb - just return the big endian size of the db
appHash := make([]byte, 8)
binary.PutVarint(appHash, app.state.Size)
@ -144,43 +221,225 @@ func (app *Application) Commit() types.ResponseCommit {
}
// Returns an associated value or nil if missing.
func (app *Application) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) {
func (app *Application) Query(reqQuery types.RequestQuery) types.ResponseQuery {
app.mu.Lock()
defer app.mu.Unlock()
if reqQuery.Path == "/val" {
key := []byte("val:" + string(reqQuery.Data))
value, err := app.state.db.Get(key)
if err != nil {
panic(err)
}
return types.ResponseQuery{
Key: reqQuery.Data,
Value: value,
}
}
if reqQuery.Prove {
value, err := app.state.db.Get(prefixKey(reqQuery.Data))
if err != nil {
panic(err)
}
resQuery := types.ResponseQuery{
Index: -1,
Key: reqQuery.Data,
Value: value,
Height: app.state.Height,
}
if value == nil {
resQuery.Log = "does not exist"
} else {
resQuery.Log = "exists"
}
resQuery.Index = -1 // TODO make Proof return index
resQuery.Key = reqQuery.Data
resQuery.Value = value
resQuery.Height = app.state.Height
return
return resQuery
}
resQuery.Key = reqQuery.Data
value, err := app.state.db.Get(prefixKey(reqQuery.Data))
if err != nil {
panic(err)
}
resQuery := types.ResponseQuery{
Key: reqQuery.Data,
Value: value,
Height: app.state.Height,
}
if value == nil {
resQuery.Log = "does not exist"
} else {
resQuery.Log = "exists"
}
resQuery.Value = value
resQuery.Height = app.state.Height
return resQuery
}
func (app *Application) PrepareProposal(
req types.RequestPrepareProposal) types.ResponsePrepareProposal {
return types.ResponsePrepareProposal{
BlockData: req.BlockData}
func (app *Application) PrepareProposal(req types.RequestPrepareProposal) types.ResponsePrepareProposal {
app.mu.Lock()
defer app.mu.Unlock()
return types.ResponsePrepareProposal{BlockData: app.substPrepareTx(req.BlockData)}
}
func (*Application) ProcessProposal(req types.RequestProcessProposal) types.ResponseProcessProposal {
for _, tx := range req.Txs {
if len(tx) == 0 {
return types.ResponseProcessProposal{Accept: false}
}
}
return types.ResponseProcessProposal{Accept: true}
}
//---------------------------------------------
// update validators
func (app *Application) Validators() (validators []types.ValidatorUpdate) {
app.mu.Lock()
defer app.mu.Unlock()
itr, err := app.state.db.Iterator(nil, nil)
if err != nil {
panic(err)
}
for ; itr.Valid(); itr.Next() {
if isValidatorTx(itr.Key()) {
validator := new(types.ValidatorUpdate)
err := types.ReadMessage(bytes.NewBuffer(itr.Value()), validator)
if err != nil {
panic(err)
}
validators = append(validators, *validator)
}
}
if err = itr.Error(); err != nil {
panic(err)
}
return
}
func MakeValSetChangeTx(pubkey cryptoproto.PublicKey, power int64) []byte {
pk, err := encoding.PubKeyFromProto(pubkey)
if err != nil {
panic(err)
}
pubStr := base64.StdEncoding.EncodeToString(pk.Bytes())
return []byte(fmt.Sprintf("val:%s!%d", pubStr, power))
}
func isValidatorTx(tx []byte) bool {
return strings.HasPrefix(string(tx), ValidatorSetChangePrefix)
}
// format is "val:pubkey!power"
// pubkey is a base64-encoded 32-byte ed25519 key
func (app *Application) execValidatorTx(tx []byte) *types.ResponseDeliverTx {
tx = tx[len(ValidatorSetChangePrefix):]
// get the pubkey and power
pubKeyAndPower := strings.Split(string(tx), "!")
if len(pubKeyAndPower) != 2 {
return &types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Expected 'pubkey!power'. Got %v", pubKeyAndPower)}
}
pubkeyS, powerS := pubKeyAndPower[0], pubKeyAndPower[1]
// decode the pubkey
pubkey, err := base64.StdEncoding.DecodeString(pubkeyS)
if err != nil {
return &types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Pubkey (%s) is invalid base64", pubkeyS)}
}
// decode the power
power, err := strconv.ParseInt(powerS, 10, 64)
if err != nil {
return &types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Power (%s) is not an int", powerS)}
}
// update
return app.updateValidator(types.UpdateValidator(pubkey, power, ""))
}
// add, update, or remove a validator
func (app *Application) updateValidator(v types.ValidatorUpdate) *types.ResponseDeliverTx {
pubkey, err := encoding.PubKeyFromProto(v.PubKey)
if err != nil {
panic(fmt.Errorf("can't decode public key: %w", err))
}
key := []byte("val:" + string(pubkey.Bytes()))
if v.Power == 0 {
// remove validator
hasKey, err := app.state.db.Has(key)
if err != nil {
panic(err)
}
if !hasKey {
pubStr := base64.StdEncoding.EncodeToString(pubkey.Bytes())
return &types.ResponseDeliverTx{
Code: code.CodeTypeUnauthorized,
Log: fmt.Sprintf("Cannot remove non-existent validator %s", pubStr)}
}
if err = app.state.db.Delete(key); err != nil {
panic(err)
}
delete(app.valAddrToPubKeyMap, string(pubkey.Address()))
} else {
// add or update validator
value := bytes.NewBuffer(make([]byte, 0))
if err := types.WriteMessage(&v, value); err != nil {
return &types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("error encoding validator: %v", err)}
}
if err = app.state.db.Set(key, value.Bytes()); err != nil {
panic(err)
}
app.valAddrToPubKeyMap[string(pubkey.Address())] = v.PubKey
}
// we only update the changes array if we successfully updated the tree
app.ValUpdates = append(app.ValUpdates, v)
return &types.ResponseDeliverTx{Code: code.CodeTypeOK}
}
// -----------------------------
// prepare proposal machinery
const PreparePrefix = "prepare"
func isPrepareTx(tx []byte) bool {
return strings.HasPrefix(string(tx), PreparePrefix)
}
// execPrepareTx is noop. tx data is considered as placeholder
// and is substitute at the PrepareProposal.
func (app *Application) execPrepareTx(tx []byte) *types.ResponseDeliverTx {
// noop
return &types.ResponseDeliverTx{}
}
// 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 {
if isPrepareTx(tx) {
blockData[i] = make([]byte, len(tx))
}
}
return blockData
}

+ 10
- 7
abci/example/kvstore/kvstore_test.go View File

@ -6,6 +6,7 @@ import (
"sort"
"testing"
"github.com/fortytw2/leaktest"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/libs/log"
@ -74,7 +75,7 @@ func TestKVStoreKV(t *testing.T) {
func TestPersistentKVStoreKV(t *testing.T) {
dir := t.TempDir()
logger := log.NewTestingLogger(t)
logger := log.NewNopLogger()
kvstore := NewPersistentKVStoreApplication(logger, dir)
key := testKey
@ -89,7 +90,7 @@ func TestPersistentKVStoreKV(t *testing.T) {
func TestPersistentKVStoreInfo(t *testing.T) {
dir := t.TempDir()
logger := log.NewTestingLogger(t)
logger := log.NewNopLogger()
kvstore := NewPersistentKVStoreApplication(logger, dir)
InitKVStore(kvstore)
@ -118,10 +119,7 @@ func TestPersistentKVStoreInfo(t *testing.T) {
// add a validator, remove a validator, update a validator
func TestValUpdates(t *testing.T) {
dir := t.TempDir()
logger := log.NewTestingLogger(t)
kvstore := NewPersistentKVStoreApplication(logger, dir)
kvstore := NewApplication()
// init with some validators
total := 10
@ -210,6 +208,7 @@ func makeApplyBlock(
// order doesn't matter
func valsEqual(t *testing.T, vals1, vals2 []types.ValidatorUpdate) {
t.Helper()
if len(vals1) != len(vals2) {
t.Fatalf("vals dont match in len. got %d, expected %d", len(vals2), len(vals1))
}
@ -231,9 +230,11 @@ func makeSocketClientServer(
app types.Application,
name string,
) (abciclient.Client, service.Service, error) {
t.Helper()
ctx, cancel := context.WithCancel(ctx)
t.Cleanup(cancel)
t.Cleanup(leaktest.Check(t))
// Start the listener
socket := fmt.Sprintf("unix://%s.sock", name)
@ -263,6 +264,8 @@ func makeGRPCClientServer(
) (abciclient.Client, service.Service, error) {
ctx, cancel := context.WithCancel(ctx)
t.Cleanup(cancel)
t.Cleanup(leaktest.Check(t))
// Start the listener
socket := fmt.Sprintf("unix://%s.sock", name)
@ -286,7 +289,7 @@ func makeGRPCClientServer(
func TestClientServer(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
logger := log.NewTestingLogger(t)
logger := log.NewNopLogger()
// set up socket app
kvstore := NewApplication()


+ 13
- 302
abci/example/kvstore/persistent_kvstore.go View File

@ -2,16 +2,10 @@ package kvstore
import (
"bytes"
"encoding/base64"
"fmt"
"strconv"
"strings"
dbm "github.com/tendermint/tm-db"
"github.com/tendermint/tendermint/abci/example/code"
"github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/encoding"
"github.com/tendermint/tendermint/libs/log"
cryptoproto "github.com/tendermint/tendermint/proto/tendermint/crypto"
ptypes "github.com/tendermint/tendermint/proto/tendermint/types"
@ -26,325 +20,42 @@ const (
var _ types.Application = (*PersistentKVStoreApplication)(nil)
type PersistentKVStoreApplication struct {
app *Application
// validator set
ValUpdates []types.ValidatorUpdate
valAddrToPubKeyMap map[string]cryptoproto.PublicKey
logger log.Logger
*Application
}
func NewPersistentKVStoreApplication(logger log.Logger, dbDir string) *PersistentKVStoreApplication {
name := "kvstore"
db, err := dbm.NewGoLevelDB(name, dbDir)
db, err := dbm.NewGoLevelDB("kvstore", dbDir)
if err != nil {
panic(err)
}
state := loadState(db)
return &PersistentKVStoreApplication{
app: &Application{state: state},
valAddrToPubKeyMap: make(map[string]cryptoproto.PublicKey),
logger: logger,
}
}
func (app *PersistentKVStoreApplication) Close() error {
return app.app.state.db.Close()
}
func (app *PersistentKVStoreApplication) Info(req types.RequestInfo) types.ResponseInfo {
res := app.app.Info(req)
res.LastBlockHeight = app.app.state.Height
res.LastBlockAppHash = app.app.state.AppHash
return res
}
// tx is either "val:pubkey!power" or "key=value" or just arbitrary bytes
func (app *PersistentKVStoreApplication) HandleTx(tx []byte) *types.ResponseDeliverTx {
// if it starts with "val:", update the validator set
// format is "val:pubkey!power"
if isValidatorTx(tx) {
// update validators in the merkle tree
// and in app.ValUpdates
return app.execValidatorTx(tx)
}
if isPrepareTx(tx) {
return app.execPrepareTx(tx)
}
// otherwise, update the key-value store
return app.app.HandleTx(tx)
}
func (app *PersistentKVStoreApplication) CheckTx(req types.RequestCheckTx) types.ResponseCheckTx {
return app.app.CheckTx(req)
}
// Commit will panic if InitChain was not called
func (app *PersistentKVStoreApplication) Commit() types.ResponseCommit {
return app.app.Commit()
}
// When path=/val and data={validator address}, returns the validator update (types.ValidatorUpdate) varint encoded.
// For any other path, returns an associated value or nil if missing.
func (app *PersistentKVStoreApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) {
switch reqQuery.Path {
case "/val":
key := []byte("val:" + string(reqQuery.Data))
value, err := app.app.state.db.Get(key)
if err != nil {
panic(err)
}
resQuery.Key = reqQuery.Data
resQuery.Value = value
return
default:
return app.app.Query(reqQuery)
Application: &Application{
valAddrToPubKeyMap: make(map[string]cryptoproto.PublicKey),
state: loadState(db),
logger: logger,
},
}
}
// Save the validators in the merkle tree
func (app *PersistentKVStoreApplication) InitChain(req types.RequestInitChain) types.ResponseInitChain {
for _, v := range req.Validators {
r := app.updateValidator(v)
if r.IsErr() {
app.logger.Error("error updating validators", "r", r)
}
}
return types.ResponseInitChain{}
}
// Track the block hash and header information
// Execute transactions
// Update the validator set
func (app *PersistentKVStoreApplication) FinalizeBlock(req types.RequestFinalizeBlock) types.ResponseFinalizeBlock {
// reset valset changes
app.ValUpdates = make([]types.ValidatorUpdate, 0)
// Punish validators who committed equivocation.
for _, ev := range req.ByzantineValidators {
if ev.Type == types.EvidenceType_DUPLICATE_VOTE {
addr := string(ev.Validator.Address)
if pubKey, ok := app.valAddrToPubKeyMap[addr]; ok {
app.updateValidator(types.ValidatorUpdate{
PubKey: pubKey,
Power: ev.Validator.Power - 1,
})
app.logger.Info("Decreased val power by 1 because of the equivocation",
"val", addr)
} else {
app.logger.Error("Wanted to punish val, but can't find it",
"val", addr)
}
}
}
respTxs := make([]*types.ResponseDeliverTx, len(req.Txs))
for i, tx := range req.Txs {
respTxs[i] = app.HandleTx(tx)
}
return types.ResponseFinalizeBlock{Txs: respTxs, ValidatorUpdates: app.ValUpdates}
}
func (app *PersistentKVStoreApplication) ListSnapshots(
req types.RequestListSnapshots) types.ResponseListSnapshots {
return types.ResponseListSnapshots{}
}
func (app *PersistentKVStoreApplication) LoadSnapshotChunk(
req types.RequestLoadSnapshotChunk) types.ResponseLoadSnapshotChunk {
return types.ResponseLoadSnapshotChunk{}
}
func (app *PersistentKVStoreApplication) OfferSnapshot(
req types.RequestOfferSnapshot) types.ResponseOfferSnapshot {
func (app *PersistentKVStoreApplication) OfferSnapshot(req types.RequestOfferSnapshot) types.ResponseOfferSnapshot {
return types.ResponseOfferSnapshot{Result: types.ResponseOfferSnapshot_ABORT}
}
func (app *PersistentKVStoreApplication) ApplySnapshotChunk(
req types.RequestApplySnapshotChunk) types.ResponseApplySnapshotChunk {
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 (app *PersistentKVStoreApplication) PrepareProposal(
req types.RequestPrepareProposal) types.ResponsePrepareProposal {
return types.ResponsePrepareProposal{BlockData: app.substPrepareTx(req.BlockData)}
}
func (app *PersistentKVStoreApplication) ProcessProposal(
req types.RequestProcessProposal) types.ResponseProcessProposal {
for _, tx := range req.Txs {
if len(tx) == 0 {
return types.ResponseProcessProposal{Result: types.ResponseProcessProposal_REJECT}
}
}
return types.ResponseProcessProposal{Result: types.ResponseProcessProposal_ACCEPT}
}
//---------------------------------------------
// update validators
func (app *PersistentKVStoreApplication) Validators() (validators []types.ValidatorUpdate) {
itr, err := app.app.state.db.Iterator(nil, nil)
if err != nil {
panic(err)
}
for ; itr.Valid(); itr.Next() {
if isValidatorTx(itr.Key()) {
validator := new(types.ValidatorUpdate)
err := types.ReadMessage(bytes.NewBuffer(itr.Value()), validator)
if err != nil {
panic(err)
}
validators = append(validators, *validator)
}
}
if err = itr.Error(); err != nil {
panic(err)
}
return
}
func MakeValSetChangeTx(pubkey cryptoproto.PublicKey, power int64) []byte {
pk, err := encoding.PubKeyFromProto(pubkey)
if err != nil {
panic(err)
}
pubStr := base64.StdEncoding.EncodeToString(pk.Bytes())
return []byte(fmt.Sprintf("val:%s!%d", pubStr, power))
func (app *PersistentKVStoreApplication) ExtendVote(req types.RequestExtendVote) types.ResponseExtendVote {
return types.ResponseExtendVote{VoteExtension: ConstructVoteExtension(req.Vote.ValidatorAddress)}
}
func isValidatorTx(tx []byte) bool {
return strings.HasPrefix(string(tx), ValidatorSetChangePrefix)
}
// format is "val:pubkey!power"
// pubkey is a base64-encoded 32-byte ed25519 key
func (app *PersistentKVStoreApplication) execValidatorTx(tx []byte) *types.ResponseDeliverTx {
tx = tx[len(ValidatorSetChangePrefix):]
// get the pubkey and power
pubKeyAndPower := strings.Split(string(tx), "!")
if len(pubKeyAndPower) != 2 {
return &types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Expected 'pubkey!power'. Got %v", pubKeyAndPower)}
}
pubkeyS, powerS := pubKeyAndPower[0], pubKeyAndPower[1]
// decode the pubkey
pubkey, err := base64.StdEncoding.DecodeString(pubkeyS)
if err != nil {
return &types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Pubkey (%s) is invalid base64", pubkeyS)}
}
// decode the power
power, err := strconv.ParseInt(powerS, 10, 64)
if err != nil {
return &types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Power (%s) is not an int", powerS)}
}
// update
return app.updateValidator(types.UpdateValidator(pubkey, power, ""))
}
// add, update, or remove a validator
func (app *PersistentKVStoreApplication) updateValidator(v types.ValidatorUpdate) *types.ResponseDeliverTx {
pubkey, err := encoding.PubKeyFromProto(v.PubKey)
if err != nil {
panic(fmt.Errorf("can't decode public key: %w", err))
}
key := []byte("val:" + string(pubkey.Bytes()))
if v.Power == 0 {
// remove validator
hasKey, err := app.app.state.db.Has(key)
if err != nil {
panic(err)
}
if !hasKey {
pubStr := base64.StdEncoding.EncodeToString(pubkey.Bytes())
return &types.ResponseDeliverTx{
Code: code.CodeTypeUnauthorized,
Log: fmt.Sprintf("Cannot remove non-existent validator %s", pubStr)}
}
if err = app.app.state.db.Delete(key); err != nil {
panic(err)
}
delete(app.valAddrToPubKeyMap, string(pubkey.Address()))
} else {
// add or update validator
value := bytes.NewBuffer(make([]byte, 0))
if err := types.WriteMessage(&v, value); err != nil {
return &types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("error encoding validator: %v", err)}
}
if err = app.app.state.db.Set(key, value.Bytes()); err != nil {
panic(err)
}
app.valAddrToPubKeyMap[string(pubkey.Address())] = v.PubKey
}
// we only update the changes array if we successfully updated the tree
app.ValUpdates = append(app.ValUpdates, v)
return &types.ResponseDeliverTx{Code: code.CodeTypeOK}
func (app *PersistentKVStoreApplication) VerifyVoteExtension(req types.RequestVerifyVoteExtension) types.ResponseVerifyVoteExtension {
return types.RespondVerifyVoteExtension(app.verifyExtension(req.Vote.ValidatorAddress, req.Vote.VoteExtension))
}
// -----------------------------
const PreparePrefix = "prepare"
func isPrepareTx(tx []byte) bool {
return strings.HasPrefix(string(tx), PreparePrefix)
}
// execPrepareTx is noop. tx data is considered as placeholder
// and is substitute at the PrepareProposal.
func (app *PersistentKVStoreApplication) execPrepareTx(tx []byte) *types.ResponseDeliverTx {
// noop
return &types.ResponseDeliverTx{}
}
// substPrepareTx subst all the preparetx in the blockdata
// to null string(could be any arbitrary string).
func (app *PersistentKVStoreApplication) 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 {
if isPrepareTx(tx) {
blockData[i] = make([]byte, len(tx))
}
}
return blockData
}
func ConstructVoteExtension(valAddr []byte) *ptypes.VoteExtension {
return &ptypes.VoteExtension{
AppDataToSign: valAddr,


+ 9
- 15
abci/server/grpc_server.go View File

@ -16,10 +16,9 @@ type GRPCServer struct {
service.BaseService
logger log.Logger
proto string
addr string
listener net.Listener
server *grpc.Server
proto string
addr string
server *grpc.Server
app types.ABCIApplicationServer
}
@ -28,11 +27,10 @@ type GRPCServer struct {
func NewGRPCServer(logger log.Logger, protoAddr string, app types.ABCIApplicationServer) service.Service {
proto, addr := tmnet.ProtocolAndAddress(protoAddr)
s := &GRPCServer{
logger: logger,
proto: proto,
addr: addr,
listener: nil,
app: app,
logger: logger,
proto: proto,
addr: addr,
app: app,
}
s.BaseService = *service.NewBaseService(logger, "ABCIServer", s)
return s
@ -40,13 +38,11 @@ func NewGRPCServer(logger log.Logger, protoAddr string, app types.ABCIApplicatio
// OnStart starts the gRPC service.
func (s *GRPCServer) OnStart(ctx context.Context) error {
ln, err := net.Listen(s.proto, s.addr)
if err != nil {
return err
}
s.listener = ln
s.server = grpc.NewServer()
types.RegisterABCIApplicationServer(s.server, s.app)
@ -57,7 +53,7 @@ func (s *GRPCServer) OnStart(ctx context.Context) error {
s.server.GracefulStop()
}()
if err := s.server.Serve(s.listener); err != nil {
if err := s.server.Serve(ln); err != nil {
s.logger.Error("error serving gRPC server", "err", err)
}
}()
@ -65,6 +61,4 @@ func (s *GRPCServer) OnStart(ctx context.Context) error {
}
// OnStop stops the gRPC server.
func (s *GRPCServer) OnStop() {
s.server.Stop()
}
func (s *GRPCServer) OnStop() { s.server.Stop() }

+ 90
- 120
abci/server/socket_server.go View File

@ -3,6 +3,7 @@ package server
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"net"
@ -26,22 +27,21 @@ type SocketServer struct {
listener net.Listener
connsMtx sync.Mutex
conns map[int]net.Conn
connsClose map[int]func()
nextConnID int
appMtx sync.Mutex
app types.Application
app types.Application
}
func NewSocketServer(logger log.Logger, protoAddr string, app types.Application) service.Service {
proto, addr := tmnet.ProtocolAndAddress(protoAddr)
s := &SocketServer{
logger: logger,
proto: proto,
addr: addr,
listener: nil,
app: app,
conns: make(map[int]net.Conn),
logger: logger,
proto: proto,
addr: addr,
listener: nil,
app: app,
connsClose: make(map[int]func()),
}
s.BaseService = *service.NewBaseService(logger, "ABCIServer", s)
return s
@ -67,44 +67,35 @@ func (s *SocketServer) OnStop() {
s.connsMtx.Lock()
defer s.connsMtx.Unlock()
for id, conn := range s.conns {
delete(s.conns, id)
if err := conn.Close(); err != nil {
s.logger.Error("error closing connection", "id", id, "conn", conn, "err", err)
}
for _, closer := range s.connsClose {
closer()
}
}
func (s *SocketServer) addConn(conn net.Conn) int {
func (s *SocketServer) addConn(closer func()) int {
s.connsMtx.Lock()
defer s.connsMtx.Unlock()
connID := s.nextConnID
s.nextConnID++
s.conns[connID] = conn
s.connsClose[connID] = closer
return connID
}
// deletes conn even if close errs
func (s *SocketServer) rmConn(connID int) error {
func (s *SocketServer) rmConn(connID int) {
s.connsMtx.Lock()
defer s.connsMtx.Unlock()
conn, ok := s.conns[connID]
if !ok {
return fmt.Errorf("connection %d does not exist", connID)
if closer, ok := s.connsClose[connID]; ok {
closer()
delete(s.connsClose, connID)
}
delete(s.conns, connID)
return conn.Close()
}
func (s *SocketServer) acceptConnectionsRoutine(ctx context.Context) {
for {
if ctx.Err() != nil {
return
}
// Accept a connection
@ -118,149 +109,134 @@ func (s *SocketServer) acceptConnectionsRoutine(ctx context.Context) {
continue
}
s.logger.Info("Accepted a new connection")
cctx, ccancel := context.WithCancel(ctx)
connID := s.addConn(ccancel)
connID := s.addConn(conn)
s.logger.Info("Accepted a new connection", "id", connID)
closeConn := make(chan error, 2) // Push to signal connection closed
responses := make(chan *types.Response, 1000) // A channel to buffer responses
// Read requests from conn and deal with them
go s.handleRequests(ctx, closeConn, conn, responses)
// Pull responses from 'responses' and write them to conn.
go s.handleResponses(ctx, closeConn, conn, responses)
// Wait until signal to close connection
go s.waitForClose(ctx, closeConn, connID)
}
}
func (s *SocketServer) waitForClose(ctx context.Context, closeConn chan error, connID int) {
defer func() {
// Close the connection
if err := s.rmConn(connID); err != nil {
s.logger.Error("error closing connection", "err", err)
once := &sync.Once{}
closer := func(err error) {
ccancel()
once.Do(func() {
if cerr := conn.Close(); err != nil {
s.logger.Error("error closing connection",
"id", connID,
"close_err", cerr,
"err", err)
}
s.rmConn(connID)
switch {
case errors.Is(err, context.Canceled):
s.logger.Error("Connection terminated",
"id", connID,
"err", err)
case errors.Is(err, context.DeadlineExceeded):
s.logger.Error("Connection encountered timeout",
"id", connID,
"err", err)
case errors.Is(err, io.EOF):
s.logger.Error("Connection was closed by client",
"id", connID)
case err != nil:
s.logger.Error("Connection error",
"id", connID,
"err", err)
default:
s.logger.Error("Connection was closed",
"id", connID)
}
})
}
}()
select {
case <-ctx.Done():
return
case err := <-closeConn:
switch {
case err == io.EOF:
s.logger.Error("Connection was closed by client")
case err != nil:
s.logger.Error("Connection error", "err", err)
default:
// never happens
s.logger.Error("Connection was closed")
}
// Read requests from conn and deal with them
go s.handleRequests(cctx, closer, conn, responses)
// Pull responses from 'responses' and write them to conn.
go s.handleResponses(cctx, closer, conn, responses)
}
}
// Read requests from conn and deal with them
func (s *SocketServer) handleRequests(
ctx context.Context,
closeConn chan error,
closer func(error),
conn io.Reader,
responses chan<- *types.Response,
) {
var count int
var bufReader = bufio.NewReader(conn)
defer func() {
// make sure to recover from any app-related panics to allow proper socket cleanup
r := recover()
if r != nil {
if r := recover(); r != nil {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
err := fmt.Errorf("recovered from panic: %v\n%s", r, buf)
closeConn <- err
s.appMtx.Unlock()
closer(fmt.Errorf("recovered from panic: %v\n%s", r, buf))
}
}()
for {
if ctx.Err() != nil {
req := &types.Request{}
if err := types.ReadMessage(bufReader, req); err != nil {
closer(fmt.Errorf("error reading message: %w", err))
return
}
var req = &types.Request{}
err := types.ReadMessage(bufReader, req)
if err != nil {
if err == io.EOF {
closeConn <- err
} else {
closeConn <- fmt.Errorf("error reading message: %w", err)
}
resp := s.processRequest(req)
select {
case <-ctx.Done():
closer(ctx.Err())
return
case responses <- resp:
}
s.appMtx.Lock()
count++
s.handleRequest(req, responses)
s.appMtx.Unlock()
}
}
func (s *SocketServer) handleRequest(req *types.Request, responses chan<- *types.Response) {
func (s *SocketServer) processRequest(req *types.Request) *types.Response {
switch r := req.Value.(type) {
case *types.Request_Echo:
responses <- types.ToResponseEcho(r.Echo.Message)
return types.ToResponseEcho(r.Echo.Message)
case *types.Request_Flush:
responses <- types.ToResponseFlush()
return types.ToResponseFlush()
case *types.Request_Info:
res := s.app.Info(*r.Info)
responses <- types.ToResponseInfo(res)
return types.ToResponseInfo(s.app.Info(*r.Info))
case *types.Request_CheckTx:
res := s.app.CheckTx(*r.CheckTx)
responses <- types.ToResponseCheckTx(res)
return types.ToResponseCheckTx(s.app.CheckTx(*r.CheckTx))
case *types.Request_Commit:
res := s.app.Commit()
responses <- types.ToResponseCommit(res)
return types.ToResponseCommit(s.app.Commit())
case *types.Request_Query:
res := s.app.Query(*r.Query)
responses <- types.ToResponseQuery(res)
return types.ToResponseQuery(s.app.Query(*r.Query))
case *types.Request_InitChain:
res := s.app.InitChain(*r.InitChain)
responses <- types.ToResponseInitChain(res)
return types.ToResponseInitChain(s.app.InitChain(*r.InitChain))
case *types.Request_ListSnapshots:
res := s.app.ListSnapshots(*r.ListSnapshots)
responses <- types.ToResponseListSnapshots(res)
return types.ToResponseListSnapshots(s.app.ListSnapshots(*r.ListSnapshots))
case *types.Request_OfferSnapshot:
res := s.app.OfferSnapshot(*r.OfferSnapshot)
responses <- types.ToResponseOfferSnapshot(res)
return types.ToResponseOfferSnapshot(s.app.OfferSnapshot(*r.OfferSnapshot))
case *types.Request_PrepareProposal:
res := s.app.PrepareProposal(*r.PrepareProposal)
responses <- types.ToResponsePrepareProposal(res)
return types.ToResponsePrepareProposal(s.app.PrepareProposal(*r.PrepareProposal))
case *types.Request_ProcessProposal:
res := s.app.ProcessProposal(*r.ProcessProposal)
responses <- types.ToResponseProcessProposal(res)
return types.ToResponseProcessProposal(s.app.ProcessProposal(*r.ProcessProposal))
case *types.Request_LoadSnapshotChunk:
res := s.app.LoadSnapshotChunk(*r.LoadSnapshotChunk)
responses <- types.ToResponseLoadSnapshotChunk(res)
return types.ToResponseLoadSnapshotChunk(s.app.LoadSnapshotChunk(*r.LoadSnapshotChunk))
case *types.Request_ApplySnapshotChunk:
res := s.app.ApplySnapshotChunk(*r.ApplySnapshotChunk)
responses <- types.ToResponseApplySnapshotChunk(res)
return types.ToResponseApplySnapshotChunk(s.app.ApplySnapshotChunk(*r.ApplySnapshotChunk))
case *types.Request_ExtendVote:
res := s.app.ExtendVote(*r.ExtendVote)
responses <- types.ToResponseExtendVote(res)
return types.ToResponseExtendVote(s.app.ExtendVote(*r.ExtendVote))
case *types.Request_VerifyVoteExtension:
res := s.app.VerifyVoteExtension(*r.VerifyVoteExtension)
responses <- types.ToResponseVerifyVoteExtension(res)
return types.ToResponseVerifyVoteExtension(s.app.VerifyVoteExtension(*r.VerifyVoteExtension))
case *types.Request_FinalizeBlock:
res := s.app.FinalizeBlock(*r.FinalizeBlock)
responses <- types.ToResponseFinalizeBlock(res)
return types.ToResponseFinalizeBlock(s.app.FinalizeBlock(*r.FinalizeBlock))
default:
responses <- types.ToResponseException("Unknown request")
return types.ToResponseException("Unknown request")
}
}
// Pull responses from 'responses' and write them to conn.
func (s *SocketServer) handleResponses(
ctx context.Context,
closeConn chan error,
closer func(error),
conn io.Writer,
responses <-chan *types.Response,
) {
@ -268,21 +244,15 @@ func (s *SocketServer) handleResponses(
for {
select {
case <-ctx.Done():
closer(ctx.Err())
return
case res := <-responses:
if err := types.WriteMessage(res, bw); err != nil {
select {
case <-ctx.Done():
case closeConn <- fmt.Errorf("error writing message: %w", err):
}
closer(fmt.Errorf("error writing message: %w", err))
return
}
if err := bw.Flush(); err != nil {
select {
case <-ctx.Done():
case closeConn <- fmt.Errorf("error flushing write buffer: %w", err):
}
closer(fmt.Errorf("error writing message: %w", err))
return
}
}


+ 2
- 2
abci/types/application.go View File

@ -4,6 +4,7 @@ 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,
@ -41,8 +42,7 @@ type Application interface {
var _ Application = (*BaseApplication)(nil)
type BaseApplication struct {
}
type BaseApplication struct{}
func NewBaseApplication() *BaseApplication {
return &BaseApplication{}


+ 209
- 0
abci/types/mocks/application.go View File

@ -0,0 +1,209 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
types "github.com/tendermint/tendermint/abci/types"
)
// Application is an autogenerated mock type for the Application type
type Application struct {
mock.Mock
}
// ApplySnapshotChunk provides a mock function with given fields: _a0
func (_m *Application) ApplySnapshotChunk(_a0 types.RequestApplySnapshotChunk) types.ResponseApplySnapshotChunk {
ret := _m.Called(_a0)
var r0 types.ResponseApplySnapshotChunk
if rf, ok := ret.Get(0).(func(types.RequestApplySnapshotChunk) types.ResponseApplySnapshotChunk); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(types.ResponseApplySnapshotChunk)
}
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)
} else {
r0 = ret.Get(0).(types.ResponseCheckTx)
}
return r0
}
// Commit provides a mock function with given fields:
func (_m *Application) Commit() types.ResponseCommit {
ret := _m.Called()
var r0 types.ResponseCommit
if rf, ok := ret.Get(0).(func() types.ResponseCommit); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(types.ResponseCommit)
}
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)
} else {
r0 = ret.Get(0).(types.ResponseExtendVote)
}
return r0
}
// FinalizeBlock provides a mock function with given fields: _a0
func (_m *Application) FinalizeBlock(_a0 types.RequestFinalizeBlock) types.ResponseFinalizeBlock {
ret := _m.Called(_a0)
var r0 types.ResponseFinalizeBlock
if rf, ok := ret.Get(0).(func(types.RequestFinalizeBlock) types.ResponseFinalizeBlock); ok {
r0 = rf(_a0)
} else {
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)
} else {
r0 = ret.Get(0).(types.ResponseInfo)
}
return r0
}
// InitChain provides a mock function with given fields: _a0
func (_m *Application) InitChain(_a0 types.RequestInitChain) types.ResponseInitChain {
ret := _m.Called(_a0)
var r0 types.ResponseInitChain
if rf, ok := ret.Get(0).(func(types.RequestInitChain) types.ResponseInitChain); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(types.ResponseInitChain)
}
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)
} else {
r0 = ret.Get(0).(types.ResponseListSnapshots)
}
return r0
}
// LoadSnapshotChunk provides a mock function with given fields: _a0
func (_m *Application) LoadSnapshotChunk(_a0 types.RequestLoadSnapshotChunk) types.ResponseLoadSnapshotChunk {
ret := _m.Called(_a0)
var r0 types.ResponseLoadSnapshotChunk
if rf, ok := ret.Get(0).(func(types.RequestLoadSnapshotChunk) types.ResponseLoadSnapshotChunk); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(types.ResponseLoadSnapshotChunk)
}
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)
} else {
r0 = ret.Get(0).(types.ResponseOfferSnapshot)
}
return r0
}
// PrepareProposal provides a mock function with given fields: _a0
func (_m *Application) PrepareProposal(_a0 types.RequestPrepareProposal) types.ResponsePrepareProposal {
ret := _m.Called(_a0)
var r0 types.ResponsePrepareProposal
if rf, ok := ret.Get(0).(func(types.RequestPrepareProposal) types.ResponsePrepareProposal); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(types.ResponsePrepareProposal)
}
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)
} else {
r0 = ret.Get(0).(types.ResponseProcessProposal)
}
return r0
}
// Query provides a mock function with given fields: _a0
func (_m *Application) Query(_a0 types.RequestQuery) types.ResponseQuery {
ret := _m.Called(_a0)
var r0 types.ResponseQuery
if rf, ok := ret.Get(0).(func(types.RequestQuery) types.ResponseQuery); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(types.ResponseQuery)
}
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)
} else {
r0 = ret.Get(0).(types.ResponseVerifyVoteExtension)
}
return r0
}

+ 189
- 0
abci/types/mocks/base.go View File

@ -0,0 +1,189 @@
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
}

+ 0
- 5
abci/types/result.go View File

@ -58,11 +58,6 @@ func (r ResponseVerifyVoteExtension) IsErr() bool {
return r.Result != ResponseVerifyVoteExtension_ACCEPT
}
// IsOK returns true if Code is OK
func (r ResponseProcessProposal) IsOK() bool {
return r.Result == ResponseProcessProposal_ACCEPT
}
//---------------------------------------------------------------------------
// override JSON marshaling so we emit defaults (ie. disable omitempty)


+ 3034
- 566
abci/types/types.pb.go
File diff suppressed because it is too large
View File


+ 14
- 0
buf.gen.yaml View File

@ -0,0 +1,14 @@
# The version of the generation template (required).
# The only currently-valid value is v1beta1.
version: v1beta1
# The plugins to run.
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

+ 16
- 0
buf.yaml View File

@ -0,0 +1,16 @@
version: v1beta1
build:
roots:
- proto
- third_party/proto
lint:
use:
- BASIC
- FILE_LOWER_SNAKE_CASE
- UNARY_RPC
ignore:
- gogoproto
breaking:
use:
- FILE

+ 13
- 22
cmd/tendermint/commands/debug/debug.go View File

@ -2,38 +2,29 @@ package debug
import (
"github.com/spf13/cobra"
"github.com/tendermint/tendermint/libs/log"
)
var (
nodeRPCAddr string
profAddr string
frequency uint
const (
flagNodeRPCAddr = "rpc-laddr"
flagProfAddr = "pprof-laddr"
flagFrequency = "frequency"
logger = log.MustNewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo)
)
// DebugCmd defines the root command containing subcommands that assist in
// debugging running Tendermint processes.
var DebugCmd = &cobra.Command{
Use: "debug",
Short: "A utility to kill or watch a Tendermint process while aggregating debugging data",
}
func init() {
DebugCmd.PersistentFlags().SortFlags = true
DebugCmd.PersistentFlags().StringVar(
&nodeRPCAddr,
func GetDebugCommand(logger log.Logger) *cobra.Command {
cmd := &cobra.Command{
Use: "debug",
Short: "A utility to kill or watch a Tendermint process while aggregating debugging data",
}
cmd.PersistentFlags().SortFlags = true
cmd.PersistentFlags().String(
flagNodeRPCAddr,
"tcp://localhost:26657",
"the Tendermint node's RPC address (<host>:<port>)",
"the Tendermint node's RPC address <host>:<port>)",
)
DebugCmd.AddCommand(killCmd)
DebugCmd.AddCommand(dumpCmd)
cmd.AddCommand(getKillCmd(logger))
cmd.AddCommand(getDumpCmd(logger))
return cmd
}

+ 79
- 55
cmd/tendermint/commands/debug/dump.go View File

@ -13,78 +13,102 @@ import (
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/libs/cli"
"github.com/tendermint/tendermint/libs/log"
rpchttp "github.com/tendermint/tendermint/rpc/client/http"
)
var dumpCmd = &cobra.Command{
Use: "dump [output-directory]",
Short: "Continuously poll a Tendermint process and dump debugging data into a single location",
Long: `Continuously poll a Tendermint process and dump debugging data into a single
func getDumpCmd(logger log.Logger) *cobra.Command {
cmd := &cobra.Command{
Use: "dump [output-directory]",
Short: "Continuously poll a Tendermint process and dump debugging data into a single location",
Long: `Continuously poll a Tendermint process and dump debugging data into a single
location at a specified frequency. At each frequency interval, an archived and compressed
file will contain node debugging information including the goroutine and heap profiles
if enabled.`,
Args: cobra.ExactArgs(1),
RunE: dumpCmdHandler,
}
func init() {
dumpCmd.Flags().UintVar(
&frequency,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
outDir := args[0]
if outDir == "" {
return errors.New("invalid output directory")
}
frequency, err := cmd.Flags().GetUint(flagFrequency)
if err != nil {
return fmt.Errorf("flag %q not defined: %w", flagFrequency, err)
}
if frequency == 0 {
return errors.New("frequency must be positive")
}
nodeRPCAddr, err := cmd.Flags().GetString(flagNodeRPCAddr)
if err != nil {
return fmt.Errorf("flag %q not defined: %w", flagNodeRPCAddr, err)
}
profAddr, err := cmd.Flags().GetString(flagProfAddr)
if err != nil {
return fmt.Errorf("flag %q not defined: %w", flagProfAddr, err)
}
if _, err := os.Stat(outDir); os.IsNotExist(err) {
if err := os.Mkdir(outDir, os.ModePerm); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}
}
rpc, err := rpchttp.New(nodeRPCAddr)
if err != nil {
return fmt.Errorf("failed to create new http client: %w", err)
}
ctx := cmd.Context()
home := viper.GetString(cli.HomeFlag)
conf := config.DefaultConfig()
conf = conf.SetRoot(home)
config.EnsureRoot(conf.RootDir)
dumpArgs := dumpDebugDataArgs{
conf: conf,
outDir: outDir,
profAddr: profAddr,
}
dumpDebugData(ctx, logger, rpc, dumpArgs)
ticker := time.NewTicker(time.Duration(frequency) * time.Second)
for range ticker.C {
dumpDebugData(ctx, logger, rpc, dumpArgs)
}
return nil
},
}
cmd.Flags().Uint(
flagFrequency,
30,
"the frequency (seconds) in which to poll, aggregate and dump Tendermint debug data",
)
dumpCmd.Flags().StringVar(
&profAddr,
cmd.Flags().String(
flagProfAddr,
"",
"the profiling server address (<host>:<port>)",
)
}
func dumpCmdHandler(cmd *cobra.Command, args []string) error {
outDir := args[0]
if outDir == "" {
return errors.New("invalid output directory")
}
if frequency == 0 {
return errors.New("frequency must be positive")
}
if _, err := os.Stat(outDir); os.IsNotExist(err) {
if err := os.Mkdir(outDir, os.ModePerm); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}
}
rpc, err := rpchttp.New(nodeRPCAddr)
if err != nil {
return fmt.Errorf("failed to create new http client: %w", err)
}
ctx := cmd.Context()
return cmd
home := viper.GetString(cli.HomeFlag)
conf := config.DefaultConfig()
conf = conf.SetRoot(home)
config.EnsureRoot(conf.RootDir)
dumpDebugData(ctx, outDir, conf, rpc)
ticker := time.NewTicker(time.Duration(frequency) * time.Second)
for range ticker.C {
dumpDebugData(ctx, outDir, conf, rpc)
}
}
return nil
type dumpDebugDataArgs struct {
conf *config.Config
outDir string
profAddr string
}
func dumpDebugData(ctx context.Context, outDir string, conf *config.Config, rpc *rpchttp.HTTP) {
func dumpDebugData(ctx context.Context, logger log.Logger, rpc *rpchttp.HTTP, args dumpDebugDataArgs) {
start := time.Now().UTC()
tmpDir, err := os.MkdirTemp(outDir, "tendermint_debug_tmp")
tmpDir, err := os.MkdirTemp(args.outDir, "tendermint_debug_tmp")
if err != nil {
logger.Error("failed to create temporary directory", "dir", tmpDir, "error", err)
return
@ -110,26 +134,26 @@ func dumpDebugData(ctx context.Context, outDir string, conf *config.Config, rpc
}
logger.Info("copying node WAL...")
if err := copyWAL(conf, tmpDir); err != nil {
if err := copyWAL(args.conf, tmpDir); err != nil {
logger.Error("failed to copy node WAL", "error", err)
return
}
if profAddr != "" {
if args.profAddr != "" {
logger.Info("getting node goroutine profile...")
if err := dumpProfile(tmpDir, profAddr, "goroutine", 2); err != nil {
if err := dumpProfile(tmpDir, args.profAddr, "goroutine", 2); err != nil {
logger.Error("failed to dump goroutine profile", "error", err)
return
}
logger.Info("getting node heap profile...")
if err := dumpProfile(tmpDir, profAddr, "heap", 2); err != nil {
if err := dumpProfile(tmpDir, args.profAddr, "heap", 2); err != nil {
logger.Error("failed to dump heap profile", "error", err)
return
}
}
outFile := filepath.Join(outDir, fmt.Sprintf("%s.zip", start.Format(time.RFC3339)))
outFile := filepath.Join(args.outDir, fmt.Sprintf("%s.zip", start.Format(time.RFC3339)))
if err := zipDir(tmpDir, outFile); err != nil {
logger.Error("failed to create and compress archive", "file", outFile, "error", err)
}


+ 79
- 72
cmd/tendermint/commands/debug/kill.go View File

@ -15,89 +15,96 @@ import (
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/libs/cli"
"github.com/tendermint/tendermint/libs/log"
rpchttp "github.com/tendermint/tendermint/rpc/client/http"
)
var killCmd = &cobra.Command{
Use: "kill [pid] [compressed-output-file]",
Short: "Kill a Tendermint process while aggregating and packaging debugging data",
Long: `Kill a Tendermint process while also aggregating Tendermint process data
func getKillCmd(logger log.Logger) *cobra.Command {
cmd := &cobra.Command{
Use: "kill [pid] [compressed-output-file]",
Short: "Kill a Tendermint process while aggregating and packaging debugging data",
Long: `Kill a Tendermint process while also aggregating Tendermint process data
such as the latest node state, including consensus and networking state,
go-routine state, and the node's WAL and config information. This aggregated data
is packaged into a compressed archive.
Example:
$ tendermint debug kill 34255 /path/to/tm-debug.zip`,
Args: cobra.ExactArgs(2),
RunE: killCmdHandler,
}
func killCmdHandler(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
pid, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
return err
}
outFile := args[1]
if outFile == "" {
return errors.New("invalid output file")
}
rpc, err := rpchttp.New(nodeRPCAddr)
if err != nil {
return fmt.Errorf("failed to create new http client: %w", err)
}
home := viper.GetString(cli.HomeFlag)
conf := config.DefaultConfig()
conf = conf.SetRoot(home)
config.EnsureRoot(conf.RootDir)
// Create a temporary directory which will contain all the state dumps and
// relevant files and directories that will be compressed into a file.
tmpDir, err := os.MkdirTemp(os.TempDir(), "tendermint_debug_tmp")
if err != nil {
return fmt.Errorf("failed to create temporary directory: %w", err)
}
defer os.RemoveAll(tmpDir)
logger.Info("getting node status...")
if err := dumpStatus(ctx, rpc, tmpDir, "status.json"); err != nil {
return err
}
logger.Info("getting node network info...")
if err := dumpNetInfo(ctx, rpc, tmpDir, "net_info.json"); err != nil {
return err
}
logger.Info("getting node consensus state...")
if err := dumpConsensusState(ctx, rpc, tmpDir, "consensus_state.json"); err != nil {
return err
}
logger.Info("copying node WAL...")
if err := copyWAL(conf, tmpDir); err != nil {
if !os.IsNotExist(err) {
return err
}
logger.Info("node WAL does not exist; continuing...")
}
logger.Info("copying node configuration...")
if err := copyConfig(home, tmpDir); err != nil {
return err
}
logger.Info("killing Tendermint process")
if err := killProc(int(pid), tmpDir); err != nil {
return err
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
pid, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
return err
}
outFile := args[1]
if outFile == "" {
return errors.New("invalid output file")
}
nodeRPCAddr, err := cmd.Flags().GetString(flagNodeRPCAddr)
if err != nil {
return fmt.Errorf("flag %q not defined: %w", flagNodeRPCAddr, err)
}
rpc, err := rpchttp.New(nodeRPCAddr)
if err != nil {
return fmt.Errorf("failed to create new http client: %w", err)
}
home := viper.GetString(cli.HomeFlag)
conf := config.DefaultConfig()
conf = conf.SetRoot(home)
config.EnsureRoot(conf.RootDir)
// Create a temporary directory which will contain all the state dumps and
// relevant files and directories that will be compressed into a file.
tmpDir, err := os.MkdirTemp(os.TempDir(), "tendermint_debug_tmp")
if err != nil {
return fmt.Errorf("failed to create temporary directory: %w", err)
}
defer os.RemoveAll(tmpDir)
logger.Info("getting node status...")
if err := dumpStatus(ctx, rpc, tmpDir, "status.json"); err != nil {
return err
}
logger.Info("getting node network info...")
if err := dumpNetInfo(ctx, rpc, tmpDir, "net_info.json"); err != nil {
return err
}
logger.Info("getting node consensus state...")
if err := dumpConsensusState(ctx, rpc, tmpDir, "consensus_state.json"); err != nil {
return err
}
logger.Info("copying node WAL...")
if err := copyWAL(conf, tmpDir); err != nil {
if !os.IsNotExist(err) {
return err
}
logger.Info("node WAL does not exist; continuing...")
}
logger.Info("copying node configuration...")
if err := copyConfig(home, tmpDir); err != nil {
return err
}
logger.Info("killing Tendermint process")
if err := killProc(int(pid), tmpDir); err != nil {
return err
}
logger.Info("archiving and compressing debug directory...")
return zipDir(tmpDir, outFile)
},
}
logger.Info("archiving and compressing debug directory...")
return zipDir(tmpDir, outFile)
return cmd
}
// killProc attempts to kill the Tendermint process with a given PID with an


+ 1
- 2
cmd/tendermint/commands/light.go View File

@ -1,7 +1,6 @@
package commands
import (
"context"
"errors"
"fmt"
"net/http"
@ -149,7 +148,7 @@ for applications built w/ Cosmos SDK).
// Initiate the light client. If the trusted store already has blocks in it, this
// will be used else we use the trusted options.
c, err := light.NewHTTPClient(
context.Background(),
cmd.Context(),
chainID,
light.TrustOptions{
Period: trustingPeriod,


+ 4
- 0
cmd/tendermint/commands/root.go View File

@ -52,6 +52,10 @@ 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
}
return nil
},
}


+ 1
- 1
cmd/tendermint/main.go View File

@ -43,7 +43,7 @@ func main() {
commands.MakeInspectCommand(conf, logger),
commands.MakeRollbackStateCommand(conf),
commands.MakeKeyMigrateCommand(conf, logger),
debug.DebugCmd,
debug.GetDebugCommand(logger),
commands.NewCompletionCmd(rcmd, true),
)


+ 41
- 3
config/config.go View File

@ -442,6 +442,33 @@ type RPCConfig struct {
// to the estimated maximum number of broadcast_tx_commit calls per block.
MaxSubscriptionsPerClient int `mapstructure:"max-subscriptions-per-client"`
// 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.
ExperimentalDisableWebsocket bool `mapstructure:"experimental-disable-websocket"`
// 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.
EventLogWindowSize time.Duration `mapstructure:"event-log-window-size"`
// 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.
EventLogMaxItems int `mapstructure:"event-log-max-items"`
// 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.
@ -487,9 +514,14 @@ func DefaultRPCConfig() *RPCConfig {
Unsafe: false,
MaxOpenConnections: 900,
MaxSubscriptionClients: 100,
MaxSubscriptionsPerClient: 5,
TimeoutBroadcastTxCommit: 10 * time.Second,
// Settings for event subscription.
MaxSubscriptionClients: 100,
MaxSubscriptionsPerClient: 5,
ExperimentalDisableWebsocket: false, // compatible with TM v0.35 and earlier
EventLogWindowSize: 0, // disables /events RPC by default
EventLogMaxItems: 0,
TimeoutBroadcastTxCommit: 10 * time.Second,
MaxBodyBytes: int64(1000000), // 1MB
MaxHeaderBytes: 1 << 20, // same as the net/http default
@ -519,6 +551,12 @@ func (cfg *RPCConfig) ValidateBasic() error {
if cfg.MaxSubscriptionsPerClient < 0 {
return errors.New("max-subscriptions-per-client can't be negative")
}
if cfg.EventLogWindowSize < 0 {
return errors.New("event-log-window-size must not be negative")
}
if cfg.EventLogMaxItems < 0 {
return errors.New("event-log-max-items must not be negative")
}
if cfg.TimeoutBroadcastTxCommit < 0 {
return errors.New("timeout-broadcast-tx-commit can't be negative")
}


+ 27
- 0
config/toml.go View File

@ -220,6 +220,33 @@ max-subscription-clients = {{ .RPC.MaxSubscriptionClients }}
# to the estimated maximum number of broadcast_tx_commit calls per block.
max-subscriptions-per-client = {{ .RPC.MaxSubscriptionsPerClient }}
# 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 = {{ .RPC.ExperimentalDisableWebsocket }}
# 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 = "{{ .RPC.EventLogWindowSize }}"
# 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 = {{ .RPC.EventLogMaxItems }}
# 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.


+ 9
- 5
docs/.vuepress/config.js View File

@ -33,10 +33,6 @@ module.exports = {
{
"label": "v0.35",
"key": "v0.35"
},
{
"label": "master",
"key": "master"
}
],
topbar: {
@ -49,8 +45,10 @@ module.exports = {
title: 'Resources',
children: [
{
// TODO(creachadair): Figure out how to make this per-branch.
// See: https://github.com/tendermint/tendermint/issues/7908
title: 'RPC',
path: 'https://docs.tendermint.com/master/rpc/',
path: 'https://docs.tendermint.com/v0.35/rpc/',
static: true
},
]
@ -162,6 +160,12 @@ module.exports = {
{
ga: 'UA-51029217-11'
}
],
[
'@vuepress/plugin-html-redirect',
{
countdown: 0
}
]
]
};

+ 1
- 0
docs/.vuepress/redirects View File

@ -0,0 +1 @@
/master/ /v0.35/

+ 2
- 2
docs/README.md View File

@ -21,10 +21,10 @@ Tendermint?](introduction/what-is-tendermint.md).
To get started quickly with an example application, see the [quick start guide](introduction/quick-start.md).
To learn about application development on Tendermint, see the [Application Blockchain Interface](https://github.com/tendermint/spec/tree/master/spec/abci).
To learn about application development on Tendermint, see the [Application Blockchain Interface](../spec/abci).
For more details on using Tendermint, see the respective documentation for
[Tendermint Core](tendermint-core/), [benchmarking and monitoring](tools/), and [network deployments](networks/).
[Tendermint Core](tendermint-core/), [benchmarking and monitoring](tools/), and [network deployments](nodes/).
To find out about the Tendermint ecosystem you can go [here](https://github.com/tendermint/awesome#ecosystem). If you are a project that is using Tendermint you are welcome to make a PR to add your project to the list.


+ 1
- 1
docs/app-dev/app-architecture.md View File

@ -57,4 +57,4 @@ See the following for more extensive documentation:
- [Interchain Standard for the Light-Client REST API](https://github.com/cosmos/cosmos-sdk/pull/1028)
- [Tendermint RPC Docs](https://docs.tendermint.com/master/rpc/)
- [Tendermint in Production](../tendermint-core/running-in-production.md)
- [ABCI spec](https://github.com/tendermint/spec/tree/95cf253b6df623066ff7cd4074a94e7a3f147c7a/spec/abci)
- [ABCI spec](https://github.com/tendermint/tendermint/tree/95cf253b6df623066ff7cd4074a94e7a3f147c7a/spec/abci)

+ 2
- 2
docs/app-dev/getting-started.md View File

@ -68,7 +68,7 @@ tendermint start
```
If you have used Tendermint, you may want to reset the data for a new
blockchain by running `tendermint unsafe_reset_all`. Then you can run
blockchain by running `tendermint unsafe-reset-all`. Then you can run
`tendermint start` to start Tendermint, and connect to the app. For more
details, see [the guide on using Tendermint](../tendermint-core/using-tendermint.md).
@ -182,7 +182,7 @@ node example/counter.js
In another window, reset and start `tendermint`:
```sh
tendermint unsafe_reset_all
tendermint unsafe-reset-all
tendermint start
```


+ 1
- 1
docs/app-dev/indexing-transactions.md View File

@ -15,7 +15,7 @@ the block itself is never stored.
Each event contains a type and a list of attributes, which are key-value pairs
denoting something about what happened during the method's execution. For more
details on `Events`, see the
[ABCI](https://github.com/tendermint/spec/blob/master/spec/abci/abci.md#events)
[ABCI](https://github.com/tendermint/tendermint/blob/master/spec/abci/abci.md#events)
documentation.
An `Event` has a composite key associated with it. A `compositeKey` is


+ 8
- 1
docs/architecture/README.md View File

@ -67,6 +67,10 @@ Note the context/background should be written in the present tense.
- [ADR-063: Privval-gRPC](./adr-063-privval-grpc.md)
- [ADR-066: E2E-Testing](./adr-066-e2e-testing.md)
- [ADR-072: Restore Requests for Comments](./adr-072-request-for-comments.md)
- [ADR-077: Block Retention](./adr-077-block-retention.md)
- [ADR-078: Non-zero Genesis](./adr-078-nonzero-genesis.md)
- [ADR-079: ED25519 Verification](./adr-079-ed25519-verification.md)
- [ADR-080: Reverse Sync](./adr-080-reverse-sync.md)
### Accepted
@ -82,13 +86,16 @@ Note the context/background should be written in the present tense.
- [ADR-075: RPC Event Subscription Interface](./adr-075-rpc-subscription.md)
- [ADR-076: Combine Spec and Tendermint Repositories](./adr-076-combine-spec-repo.md)
### Deprecated
None
### Rejected
- [ADR-023: ABCI-Propose-tx](./adr-023-ABCI-propose-tx.md)
- [ADR-029: Check-Tx-Consensus](./adr-029-check-tx-consensus.md)
- [ADR-058: Event-Hashing](./adr-058-event-hashing.md)
### Proposed
- [ADR-007: Trust-Metric-Usage](./adr-007-trust-metric-usage.md)


+ 1
- 1
docs/architecture/adr-001-logging.md View File

@ -213,4 +213,4 @@ type Logger interface {
}
```
See [The Hunt for a Logger Interface](https://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide). The advantage is greater composability (check out how go-kit defines colored logging or log-leveled logging on top of this interface https://github.com/go-kit/kit/tree/master/log).
See [The Hunt for a Logger Interface](https://web.archive.org/web/20210902161539/https://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide#1). The advantage is greater composability (check out how go-kit defines colored logging or log-leveled logging on top of this interface https://github.com/go-kit/kit/tree/master/log).

+ 2
- 2
docs/architecture/adr-044-lite-client-with-weak-subjectivity.md View File

@ -84,7 +84,7 @@ The linear verification algorithm requires downloading all headers
between the `TrustHeight` and the `LatestHeight`. The lite client downloads the
full header for the provided `TrustHeight` and then proceeds to download `N+1`
headers and applies the [Tendermint validation
rules](https://docs.tendermint.com/master/spec/blockchain/blockchain.html#validation)
rules](https://github.com/tendermint/tendermint/tree/master/spec/light-client/verification/README.md)
to each block.
### Bisecting Verification
@ -119,7 +119,7 @@ network usage.
---
Check out the formal specification
[here](https://github.com/tendermint/spec/tree/master/spec/light-client).
[here](https://github.com/tendermint/tendermint/tree/master/spec/light-client).
## Status


+ 1
- 1
docs/architecture/adr-045-abci-evidence.md View File

@ -18,7 +18,7 @@ graceful here, but that's for another day.
It's possible to fool lite clients without there being a fork on the
main chain - so called Fork-Lite. See the
[fork accountability](https://docs.tendermint.com/master/spec/light-client/accountability/)
[fork accountability](https://github.com/tendermint/tendermint/blob/master/spec/light-client/accountability/README.md)
document for more details. For a sequential lite client, this can happen via
equivocation or amnesia attacks. For a skipping lite client this can also happen
via lunatic validator attacks. There must be some way for applications to punish


+ 11
- 11
docs/architecture/adr-047-handling-evidence-from-light-client.md View File

@ -108,7 +108,7 @@ This is done with:
func (c *Client) examineConflictingHeaderAgainstTrace(
trace []*types.LightBlock,
targetBlock *types.LightBlock,
source provider.Provider,
source provider.Provider,
now time.Time,
) ([]*types.LightBlock, *types.LightBlock, error)
```
@ -123,17 +123,17 @@ as a sanity check. If this fails we have to drop the witness.
intermediary headers of the primary (In the above example this is A, B, C, D, F, H). If bisection fails
or the witness stops responding then we can call the witness faulty and drop it.
3. We eventually reach a verified header by the witness which is not the same as the intermediary header
3. We eventually reach a verified header by the witness which is not the same as the intermediary header
(In the above example this is E). This is the point of bifurcation (This could also be the last header).
4. There is a unique case where the trace that is being examined against has blocks that have a greater
height than the targetBlock. This can occur as part of a forward lunatic attack where the primary has
provided a light block that has a height greater than the head of the chain (see Appendix B). In this
case, the light client will verify the sources blocks up to the targetBlock and return the block in the
4. There is a unique case where the trace that is being examined against has blocks that have a greater
height than the targetBlock. This can occur as part of a forward lunatic attack where the primary has
provided a light block that has a height greater than the head of the chain (see Appendix B). In this
case, the light client will verify the sources blocks up to the targetBlock and return the block in the
trace that is directly after the targetBlock in height as the `ConflictingBlock`
This function then returns the trace of blocks from the witness node between the common header and the
divergent header of the primary as it is likely, as seen in the example to the right, that multiple
divergent header of the primary as it is likely, as seen in the example to the right, that multiple
headers where required in order to verify the divergent one. This trace will
be used later (as is also described later in this document).
@ -179,7 +179,7 @@ This then ends the process and the verify function that was called at the start
the user.
For a detailed overview of how each of these three attacks can be conducted please refer to the
[fork accountability spec](https://github.com/tendermint/spec/blob/master/spec/consensus/light-client/accountability.md).
[fork accountability spec](https://github.com/tendermint/tendermint/blob/master/spec/consensus/light-client/accountability.md).
## Full Node Verification
@ -212,7 +212,7 @@ clear from the current information which nodes behaved maliciously.
## References
* [Fork accountability spec](https://github.com/tendermint/spec/blob/master/spec/consensus/light-client/accountability.md)
* [Fork accountability spec](https://github.com/tendermint/tendermint/blob/master/spec/consensus/light-client/accountability.md)
* [ADR 056: Light client amnesia attacks](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-056-light-client-amnesia-attacks.md)
* [ADR-059: Evidence Composition and Lifecycle](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-059-evidence-composition-and-lifecycle.md)
* [Informal's Light Client Detector](https://github.com/informalsystems/tendermint-rs/blob/master/docs/spec/lightclient/detection/detection.md)
@ -238,7 +238,7 @@ a phantom validator. Given this, it was removed.
A unique flavor of lunatic attack is a forward lunatic attack. This is where a malicious
node provides a header with a height greater than the height of the blockchain. Thus there
are no witnesses capable of rebutting the malicious header. Such an attack will also
are no witnesses capable of rebutting the malicious header. Such an attack will also
require an accomplice, i.e. at least one other witness to also return the same forged header.
Although such attacks can be any arbitrary height ahead, they must still remain within the
clock drift of the light clients real time. Therefore, to detect such an attack, a light
@ -251,4 +251,4 @@ client will wait for a time
for a witness to provide the latest block it has. Given the time constraints, if the witness
is operating at the head of the blockchain, it will have a header with an earlier height but
a later timestamp. This can be used to prove that the primary has submitted a lunatic header
which violates monotonically increasing time.
which violates monotonically increasing time.

+ 3
- 3
docs/architecture/adr-056-light-client-amnesia-attacks.md View File

@ -10,7 +10,7 @@
## Context
Whilst most created evidence of malicious behavior is self evident such that any individual can verify them independently there are types of evidence, known collectively as global evidence, that require further collaboration from the network in order to accumulate enough information to create evidence that is individually verifiable and can therefore be processed through consensus. [Fork Accountability](https://github.com/tendermint/spec/blob/master/spec/consensus/light-client/accountability.md) has been coined to describe the entire process of detection, proving and punishing of malicious behavior. This ADR addresses specifically what a light client amnesia attack is and how it can be proven and the current decision around handling light client amnesia attacks. For information on evidence handling by the light client, it is recommended to read [ADR 47](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-047-handling-evidence-from-light-client.md).
Whilst most created evidence of malicious behavior is self evident such that any individual can verify them independently there are types of evidence, known collectively as global evidence, that require further collaboration from the network in order to accumulate enough information to create evidence that is individually verifiable and can therefore be processed through consensus. [Fork Accountability](https://github.com/tendermint/tendermint/blob/master/spec/consensus/light-client/accountability.md) has been coined to describe the entire process of detection, proving and punishing of malicious behavior. This ADR addresses specifically what a light client amnesia attack is and how it can be proven and the current decision around handling light client amnesia attacks. For information on evidence handling by the light client, it is recommended to read [ADR 47](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-047-handling-evidence-from-light-client.md).
### Amnesia Attack
@ -65,7 +65,7 @@ Light clients where all witnesses are faulty can be subject to an amnesia attack
## References
- [Fork accountability algorithm](https://docs.google.com/document/d/11ZhMsCj3y7zIZz4udO9l25xqb0kl7gmWqNpGVRzOeyY/edit)
- [Fork accountability spec](https://github.com/tendermint/spec/blob/master/spec/consensus/light-client/accountability.md)
- [Fork accountability spec](https://github.com/tendermint/tendermint/blob/master/spec/consensus/light-client/accountability.md)
## Appendix A: Detailed Walkthrough of Performing a Light Client Amnesia Attack
@ -128,7 +128,7 @@ This trial period will be discussed later.
Returning to the event of an amnesia attack, if we were to examine the behavior of the honest nodes, C1 and C2, in the schematic, C2 will not PRECOMMIT an earlier round, but it is likely, if a node in C1 were to receive +2/3 PREVOTE's or PRECOMMIT's for a higher round, that it would remove the lock and PREVOTE and PRECOMMIT for the later round. Therefore, unfortunately it is not a case of simply punishing all nodes that have double voted in the `PotentialAmnesiaEvidence`.
Instead we use the Proof of Lock Change (PoLC) referred to in the [consensus spec](https://github.com/tendermint/spec/blob/master/spec/consensus/consensus.md#terms). When an honest node votes again for a different block in a later round
Instead we use the Proof of Lock Change (PoLC) referred to in the [consensus spec](https://github.com/tendermint/tendermint/blob/master/spec/consensus/consensus.md#terms). When an honest node votes again for a different block in a later round
(which will only occur in very rare cases), it will generate the PoLC and store it in the evidence reactor for a time equal to the `MaxEvidenceAge`
```golang


+ 4
- 4
docs/architecture/adr-067-mempool-refactor.md View File

@ -106,7 +106,7 @@ invasive in the required set of protocol and implementation changes, which
simply extends the existing `CheckTx` ABCI method. The second candidate essentially
involves the introduction of new ABCI method(s) and would require a higher degree
of complexity in protocol and implementation changes, some of which may either
overlap or conflict with the upcoming introduction of [ABCI++](https://github.com/tendermint/spec/blob/master/rfc/004-abci%2B%2B.md).
overlap or conflict with the upcoming introduction of [ABCI++](https://github.com/tendermint/tendermint/blob/master/docs/rfc/rfc-013-abci%2B%2B.md).
For more information on the various approaches and proposals, please see the
[mempool discussion](https://github.com/tendermint/tendermint/discussions/6295).
@ -171,7 +171,7 @@ message ResponseCheckTx {
```
It is entirely up the application in determining how these fields are populated
and with what values, e.g. the `sender` could be the signer and fee payer
and with what values, e.g. the `sender` could be the signer and fee payer
of the transaction, the `priority` could be the cumulative sum of the fee(s).
Only `sender` is required, while `priority` can be omitted which would result in
@ -289,7 +289,7 @@ non-contentious and backwards compatible manner.
trying again at a later point in time or by ensuring the "child" priority is
lower than the "parent" priority. In other words, if parents always have
priories that are higher than their children, then the new mempool design will
maintain causal ordering.
maintain causal ordering.
### Neutral
@ -299,5 +299,5 @@ non-contentious and backwards compatible manner.
## References
- [ABCI++](https://github.com/tendermint/spec/blob/master/rfc/004-abci%2B%2B.md)
- [ABCI++](https://github.com/tendermint/tendermint/blob/master/docs/rfc/rfc-013-abci%2B%2B.md)
- [Mempool Discussion](https://github.com/tendermint/tendermint/discussions/6295)

+ 5
- 5
docs/architecture/adr-068-reverse-sync.md View File

@ -10,15 +10,15 @@ Accepted
## Context
The advent of state sync and block pruning gave rise to the opportunity for full nodes to participate in consensus without needing complete block history. This also introduced a problem with respect to evidence handling. Nodes that didn't have all the blocks within the evidence age were incapable of validating evidence, thus halting if that evidence was committed on chain.
The advent of state sync and block pruning gave rise to the opportunity for full nodes to participate in consensus without needing complete block history. This also introduced a problem with respect to evidence handling. Nodes that didn't have all the blocks within the evidence age were incapable of validating evidence, thus halting if that evidence was committed on chain.
[RFC005](https://github.com/tendermint/spec/blob/master/rfc/005-reverse-sync.md) was published in response to this problem and modified the spec to add a minimum block history invariant. This predominantly sought to extend state sync so that it was capable of fetching and storing the `Header`, `Commit` and `ValidatorSet` (essentially a `LightBlock`) of the last `n` heights, where `n` was calculated based from the evidence age.
[ADR 068](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-068-reverse-sync.md) was published in response to this problem and modified the spec to add a minimum block history invariant. This predominantly sought to extend state sync so that it was capable of fetching and storing the `Header`, `Commit` and `ValidatorSet` (essentially a `LightBlock`) of the last `n` heights, where `n` was calculated based from the evidence age.
This ADR sets out to describe the design of this state sync extension as well as modifications to the light client provider and the merging of tm store.
## Decision
The state sync reactor will be extended by introducing 2 new P2P messages (and a new channel).
The state sync reactor will be extended by introducing 2 new P2P messages (and a new channel).
```protobuf
message LightBlockRequest {
@ -26,7 +26,7 @@ message LightBlockRequest {
}
message LightBlockResponse {
tendermint.types.LightBlock light_block = 1;
tendermint.types.LightBlock light_block = 1;
}
```
@ -93,5 +93,5 @@ This ADR tries to remain within the scope of extending state sync, however the c
## References
- [Reverse Sync RFC](https://github.com/tendermint/spec/blob/master/rfc/005-reverse-sync.md)
- [Reverse Sync RFC](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-068-reverse-sync.md)
- [Original Issue](https://github.com/tendermint/tendermint/issues/5617)

docs/architecture/adr-069-flexible-node-intitalization.md → docs/architecture/adr-069-flexible-node-initialization.md View File


+ 8
- 8
docs/architecture/adr-071-proposer-based-timestamps.md View File

@ -16,7 +16,7 @@
## Context
Tendermint currently provides a monotonically increasing source of time known as [BFTTime](https://github.com/tendermint/spec/blob/master/spec/consensus/bft-time.md).
Tendermint currently provides a monotonically increasing source of time known as [BFTTime](https://github.com/tendermint/tendermint/blob/master/spec/consensus/bft-time.md).
This mechanism for producing a source of time is reasonably simple.
Each correct validator adds a timestamp to each `Precommit` message it sends.
The timestamp it sends is either the validator's current known Unix time or one millisecond greater than the previous block time, depending on which value is greater.
@ -41,7 +41,7 @@ Proposer-based timestamps alter the current mechanism for producing block timest
1. Correct validators only approve the proposed block timestamp if it is close enough to their own currently known Unix time.
The result of these changes is a more meaningful timestamp that cannot be controlled by `<= 2/3` of the validator voting power.
This document outlines the necessary code changes in Tendermint to implement the corresponding [proposer-based timestamps specification](https://github.com/tendermint/spec/tree/master/spec/consensus/proposer-based-timestamp).
This document outlines the necessary code changes in Tendermint to implement the corresponding [proposer-based timestamps specification](https://github.com/tendermint/tendermint/tree/master/spec/consensus/proposer-based-timestamp).
## Alternative Approaches
@ -58,10 +58,10 @@ We therefore decided not to remove the timestamp.
Applications often wish for some transactions to occur on a certain day, on a regular period, or after some time following a different event.
All of these require some meaningful representation of agreed upon time.
The following protocols and application features require a reliable source of time:
* Tendermint Light Clients [rely on correspondence between their known time](https://github.com/tendermint/spec/blob/master/spec/light-client/verification/README.md#definitions-1) and the block time for block verification.
* Tendermint Evidence validity is determined [either in terms of heights or in terms of time](https://github.com/tendermint/spec/blob/8029cf7a0fcc89a5004e173ec065aa48ad5ba3c8/spec/consensus/evidence.md#verification).
* Tendermint Light Clients [rely on correspondence between their known time](https://github.com/tendermint/tendermint/blob/master/spec/light-client/verification/README.md#definitions-1) and the block time for block verification.
* Tendermint Evidence validity is determined [either in terms of heights or in terms of time](https://github.com/tendermint/tendermint/blob/8029cf7a0fcc89a5004e173ec065aa48ad5ba3c8/spec/consensus/evidence.md#verification).
* Unbonding of staked assets in the Cosmos Hub [occurs after a period of 21 days](https://github.com/cosmos/governance/blob/ce75de4019b0129f6efcbb0e752cd2cc9e6136d3/params-change/Staking.md#unbondingtime).
* IBC packets can use either a [timestamp or a height to timeout packet delivery](https://docs.cosmos.network/v0.43/ibc/overview.html#acknowledgements).
* IBC packets can use either a [timestamp or a height to timeout packet delivery](https://docs.cosmos.network/v0.44/ibc/overview.html#acknowledgements)
Finally, inflation distribution in the Cosmos Hub uses an approximation of time to calculate an annual percentage rate.
This approximation of time is calculated using [block heights with an estimated number of blocks produced in a year](https://github.com/cosmos/governance/blob/master/params-change/Mint.md#blocksperyear).
@ -116,7 +116,7 @@ This timestamp is therefore no longer useful as part of consensus and may option
type Vote struct {
Type tmproto.SignedMsgType `json:"type"`
Height int64 `json:"height"`
Round int32 `json:"round"`
Round int32 `json:"round"`
BlockID BlockID `json:"block_id"` // zero if vote is nil.
-- Timestamp time.Time `json:"timestamp"`
ValidatorAddress Address `json:"validator_address"`
@ -135,7 +135,7 @@ A validator will only Prevote a proposal if the proposal timestamp is considered
A proposal timestamp is considered `timely` if it is within `PRECISION` and `MSGDELAY` of the Unix time known to the validator.
More specifically, a proposal timestamp is `timely` if `proposalTimestamp - PRECISION ≤ validatorLocalTime ≤ proposalTimestamp + PRECISION + MSGDELAY`.
Because the `PRECISION` and `MSGDELAY` parameters must be the same across all validators, they will be added to the [consensus parameters](https://github.com/tendermint/tendermint/blob/master/proto/tendermint/types/params.proto#L13) as [durations](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Duration).
Because the `PRECISION` and `MSGDELAY` parameters must be the same across all validators, they will be added to the [consensus parameters](https://github.com/tendermint/spec/blob/master/proto/tendermint/types/params.proto#L11) as [durations](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Duration).
The consensus parameters will be updated to include this `Synchrony` field as follows:
@ -328,6 +328,6 @@ This skew will be bound by the `PRECISION` value, so it is unlikely to be too la
## References
* [PBTS Spec](https://github.com/tendermint/spec/tree/master/spec/consensus/proposer-based-timestamp)
* [PBTS Spec](https://github.com/tendermint/tendermint/tree/master/spec/consensus/proposer-based-timestamp)
* [BFTTime spec](https://github.com/tendermint/spec/blob/master/spec/consensus/bft-time.md)
* [Issue 371](https://github.com/tendermint/spec/issues/371)

+ 1
- 1
docs/architecture/adr-073-libp2p.md View File

@ -232,4 +232,4 @@ the implementation timeline.
[adr61]: ./adr-061-p2p-refactor-scope.md
[adr62]: ./adr-062-p2p-architecture.md
[rfc]: ../rfc/rfc-000-p2p.rst
[rfc]: ../rfc/rfc-000-p2p-roadmap.rst

+ 7
- 7
docs/architecture/adr-074-timeout-params.md View File

@ -35,7 +35,7 @@ The configurable values are as follows:
* How much the `TimeoutPrevote` increases with each round.
* `TimeoutPrecommit`
* How long the consensus algorithm waits after receiving +2/3 precommits that
do not have a quorum for a value before entering the next round.
do not have a quorum for a value before entering the next round.
(See the [arXiv paper][arxiv-paper], Algorithm 1, Line 47)
* `TimeoutPrecommitDelta`
* How much the `TimeoutPrecommit` increases with each round.
@ -48,7 +48,7 @@ The configurable values are as follows:
### Overview of Change
We will consolidate the timeout parameters and migrate them from the node-local
We will consolidate the timeout parameters and migrate them from the node-local
`config.toml` file into the network-global consensus parameters.
The 8 timeout parameters will be consolidated down to 6. These will be as follows:
@ -84,7 +84,7 @@ a `config.toml` with Tendermint's default values for these parameters.
### Why this parameter consolidation?
Reducing the number of parameters is good for UX. Fewer superfluous parameters makes
running and operating a Tendermint network less confusing.
running and operating a Tendermint network less confusing.
The Prevote and Precommit messages are both similar sizes, require similar amounts
of processing so there is no strong need for them to be configured separately.
@ -125,7 +125,7 @@ would use this exact same set of values.
While Tendermint nodes often run with similar bandwidth and on similar cloud-hosted
machines, there are enough points of variability to make configuring
consensus timeouts meaningful. Namely, Tendermint network topologies are likely to be
very different from chain to chain. Additionally, applications may vary greatly in
very different from chain to chain. Additionally, applications may vary greatly in
how long the `Commit` phase may take. Applications that perform more work during `Commit`
require a longer `TimeoutCommit` to allow the application to complete its work
and be prepared for the next height.
@ -166,10 +166,10 @@ namely, each value must be non-negative.
### Migration
The new `ConsensusParameters` will be added during an upcoming release. In this
release, the old `config.toml` parameters will cease to control the timeouts and
release, the old `config.toml` parameters will cease to control the timeouts and
an error will be logged on nodes that continue to specify these values. The specific
mechanism by which these parameters will added to a chain is being discussed in
[RFC-009][rfc-009] and will be decided ahead of the next release.
mechanism by which these parameters will added to a chain is being discussed in
[RFC-009][rfc-009] and will be decided ahead of the next release.
The specific mechanism for adding these parameters depends on work related to
[soft upgrades][soft-upgrades], which is still ongoing.


+ 35
- 33
docs/architecture/adr-075-rpc-subscription.md View File

@ -2,6 +2,8 @@
## Changelog
- 01-Mar-2022: Update long-polling interface (@creachadair).
- 10-Feb-2022: Updates to reflect implementation.
- 26-Jan-2022: Marked accepted.
- 22-Jan-2022: Updated and expanded (@creachadair).
- 20-Nov-2021: Initial draft (@creachadair).
@ -267,12 +269,12 @@ initial implementation will store the event log in-memory, and the operator
will be given two per-node configuration settings. Note, these names are
provisional:
- `[event-subscription] time-window`: A duration before present during which the
node will retain event items published. Setting this value to zero disables
event subscription.
- `[rpc] event-log-window-size`: A duration before the latest published event,
during which the node will retain event items published. Setting this value
to zero disables event subscription.
- `[event-subscription] max-items`: A maximum number of event items that the
node will retain within the time window. If the number of items exceeds this
- `[rpc] event-log-max-items`: A maximum number of event items that the node
will retain within the time window. If the number of items exceeds this
value, the node discardes the oldest items in the window. Setting this value
to zero means that no limit is imposed on the number of items.
@ -307,11 +309,11 @@ type EventParams struct {
// Return only items after this cursor. If empty, the limit is just
// before the the beginning of the event log.
After string `json:"after_item"`
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_item"`
Before string `json:"before"`
// Wait for up to this long for events to be available.
WaitTime time.Duration `json:"wait_time"`
@ -335,8 +337,8 @@ type Filter struct {
The semantics of the request are as follows: An item in the event log is
**eligible** for a query if:
- It is newer than the `after_item` cursor (if set).
- It is older than the `before_item` cursor (if set).
- It is newer than the `after` cursor (if set).
- It is older than the `before` cursor (if set).
- It matches the filter (if set).
Among the eligible items in the log, the server returns up to `max_results` of
@ -344,13 +346,13 @@ the newest items, in reverse order of cursor. If `max_results` is unset the
server chooses a number to return, and will cap `max_results` at a sensible
limit.
The `wait_time` parameter is used to effect polling. If `before_item` is empty,
the server will wait for up to `wait_time` for additional items, if there are
fewer than `max_results` eligible results in the log. If `wait_time` is zero,
the server will return whatever eligible items are available immediately.
The `wait_time` parameter is used to effect polling. If `before` is empty and
no items are available, the server will wait for up to `wait_time` for matching
items to arrive at the head of the log. If `wait_time` is zero or negative, the
server will wait for a default (positive) interval.
If `before_item` non-empty, `wait_time` is ignored: new results are only added
to the head of the log, so there is no need to wait. This allows the client to
If `before` non-empty, `wait_time` is ignored: new results are only added to
the head of the log, so there is no need to wait. This allows the client to
poll for new data, and "page" backward through matching event items. This is
discussed in more detail below.
@ -372,11 +374,11 @@ type EventReply struct {
// The cursor of the oldest item in the log at the time of this reply,
// or "" if the log is empty.
Oldest string `json:"oldest_item"`
Oldest string `json:"oldest"`
// The cursor of the newest item in the log at the time of this reply,
// or "" if the log is empty.
Newest string `json:"newest_item"`
Newest string `json:"newest"`
}
type EventItem struct {
@ -392,9 +394,9 @@ type EventItem struct {
}
```
The `oldest_item` and `newest_item` fields of the reply report the cursors of
the oldest and newest items (of any kind) recorded in the event log at the time
of the reply, or are `""` if the log is empty.
The `oldest` and `newest` fields of the reply report the cursors of the oldest
and newest items (of any kind) recorded in the event log at the time of the
reply, or are `""` if the log is empty.
The `data` field contains the type-specific event datum. The datum carries any
ABCI events that may have been defined.
@ -412,26 +414,26 @@ The semantics of the reply are as follows:
- If `more` is true, there is at least one additional, older item in the
event log that was not returned (in excess of `max_results`).
In this case the client can fetch the next page by setting `before_item`
in a new request, to the cursor of the oldest item fetched (i.e., the
last one in `items`).
In this case the client can fetch the next page by setting `before` in a
new request, to the cursor of the oldest item fetched (i.e., the last one
in `items`).
- Otherwise (if `more` is false), all the matching results have been
reported (pagination is complete).
- The first element of `items` identifies the newest item considered.
Subsequent poll requests can set `after_item` to this cursor to skip
items that were already retrieved.
Subsequent poll requests can set `after` to this cursor to skip items
that were already retrieved.
- If `items` is empty:
- If the `before_item` was set in the request, there are no further
eligible items for this query in the log (pagination is complete).
- If the `before` was set in the request, there are no further eligible
items for this query in the log (pagination is complete).
This is just a safety case; the client can detect this without issuing
another call by consulting the `more` field of the previous reply.
- If the `before_item` was empty in the request, no eligible items were
- If the `before` was empty in the request, no eligible items were
available before the `wait_time` expired. The client may poll again to
wait for more event items.
@ -453,12 +455,11 @@ crashes and connectivity issues:
1. In ordinary operation, clients will **long-poll** the head of the event
log for new events matching their criteria (by setting a `wait_time` and
no `before_item`).
no `before`).
2. If there are more events than the client requested, or if the client needs
to to read older events to recover from a stall or crash, clients will
**page** backward through the event log (by setting `before_item` and
possibly `after_item`).
**page** backward through the event log (by setting `before` and `after`).
- While the new API requires explicit polling by the client, it makes better
use of the node's existing HTTP infrastructure (e.g., connection pools).
@ -479,7 +480,7 @@ crashes and connectivity issues:
The initial implementation will do this by checking the tail of the event log
after each new item is published. If the number of items in the log exceeds
the item limit, it will delete oldest items until the log is under the limit;
then discard any older than the time window before present.
then discard any older than the time window before the latest.
To minimize coordination interference between the publisher (the event bus)
and the subcribers (the `events` service handlers), the event log will be
@ -664,13 +665,14 @@ The following alternative approaches were considered:
- [rpc: remove duplication of events when querying][i7273] (#7273)
[rfc006]: https://github.com/tendermint/tendermint/blob/master/docs/rfc/rfc-006-event-subscription.md
[rpc-service]: https://docs.tendermint.com/master/rpc
[rpc-service]: https://github.com/tendermint/tendermint/blob/master/rpc/openapi/openapi.yaml
[query-grammar]: https://pkg.go.dev/github.com/tendermint/tendermint@master/internal/pubsub/query/syntax
[ws]: https://datatracker.ietf.org/doc/html/rfc6455
[jsonrpc2]: https://www.jsonrpc.org/specification
[nginx]: https://nginx.org/en/docs/
[fcgi]: http://www.mit.edu/~yandros/doc/specs/fcgi-spec.html
[rp-ws]: https://nginx.org/en/docs/http/websocket.html
<!-- markdown-link-check-disable-next-line -->
[ng-xm]: https://www.nginx.com/resources/wiki/extending/
[abci-event]: https://pkg.go.dev/github.com/tendermint/tendermint/abci/types#Event
[rfc001]: https://github.com/tendermint/tendermint/blob/master/docs/rfc/rfc-001-storage-engine.rst


+ 109
- 0
docs/architecture/adr-077-block-retention.md View File

@ -0,0 +1,109 @@
# ADR 077: Configurable Block Retention
## Changelog
- 2020-03-23: Initial draft (@erikgrinaker)
- 2020-03-25: Use local config for snapshot interval (@erikgrinaker)
- 2020-03-31: Use ABCI commit response for block retention hint
- 2020-04-02: Resolved open questions
- 2021-02-11: Migrate to tendermint repo (Originally [RFC 001](https://github.com/tendermint/spec/pull/84))
## Author(s)
- Erik Grinaker (@erikgrinaker)
## Context
Currently, all Tendermint nodes contain the complete sequence of blocks from genesis up to some height (typically the latest chain height). This will no longer be true when the following features are released:
- [Block pruning](https://github.com/tendermint/tendermint/issues/3652): removes historical blocks and associated data (e.g. validator sets) up to some height, keeping only the most recent blocks.
- [State sync](https://github.com/tendermint/tendermint/issues/828): bootstraps a new node by syncing state machine snapshots at a given height, but not historical blocks and associated data.
To maintain the integrity of the chain, the use of these features must be coordinated such that necessary historical blocks will not become unavailable or lost forever. In particular:
- Some nodes should have complete block histories, for auditability, querying, and bootstrapping.
- The majority of nodes should retain blocks longer than the Cosmos SDK unbonding period, for light client verification.
- Some nodes must take and serve state sync snapshots with snapshot intervals less than the block retention periods, to allow new nodes to state sync and then replay blocks to catch up.
- Applications may not persist their state on commit, and require block replay on restart.
- Only a minority of nodes can be state synced within the unbonding period, for light client verification and to serve block histories for catch-up.
However, it is unclear if and how we should enforce this. It may not be possible to technically enforce all of these without knowing the state of the entire network, but it may also be unrealistic to expect this to be enforced entirely through social coordination. This is especially unfortunate since the consequences of misconfiguration can be permanent chain-wide data loss.
## Proposal
Add a new field `retain_height` to the ABCI `ResponseCommit` message:
```proto
service ABCIApplication {
rpc Commit(RequestCommit) returns (ResponseCommit);
}
message RequestCommit {}
message ResponseCommit {
// reserve 1
bytes data = 2; // the Merkle root hash
uint64 retain_height = 3; // the oldest block height to retain
}
```
Upon ABCI `Commit`, which finalizes execution of a block in the state machine, Tendermint removes all data for heights lower than `retain_height`. This allows the state machine to control block retention, which is preferable since only it can determine the significance of historical blocks. By default (i.e. with `retain_height=0`) all historical blocks are retained.
Removed data includes not only blocks, but also headers, commit info, consensus params, validator sets, and so on. In the first iteration this will be done synchronously, since the number of heights removed for each run is assumed to be small (often 1) in the typical case. It can be made asynchronous at a later time if this is shown to be necessary.
Since `retain_height` is dynamic, it is possible for it to refer to a height which has already been removed. For example, commit at height 100 may return `retain_height=90` while commit at height 101 may return `retain_height=80`. This is allowed, and will be ignored - it is the application's responsibility to return appropriate values.
State sync will eventually support backfilling heights, via e.g. a snapshot metadata field `backfill_height`, but in the initial version it will have a fully truncated block history.
## Cosmos SDK Example
As an example, we'll consider how the Cosmos SDK might make use of this. The specific details should be discussed in a separate SDK proposal.
The returned `retain_height` would be the lowest height that satisfies:
- Unbonding time: the time interval in which validators can be economically punished for misbehavior. Blocks in this interval must be auditable e.g. by the light client.
- IAVL snapshot interval: the block interval at which the underlying IAVL database is persisted to disk, e.g. every 10000 heights. Blocks since the last IAVL snapshot must be available for replay on application restart.
- State sync snapshots: blocks since the _oldest_ available snapshot must be available for state sync nodes to catch up (oldest because a node may be restoring an old snapshot while a new snapshot was taken).
- Local config: archive nodes may want to retain more or all blocks, e.g. via a local config option `min-retain-blocks`. There may also be a need to vary rentention for other nodes, e.g. sentry nodes which do not need historical blocks.
![Cosmos SDK block retention diagram](img/block-retention.png)
## Status
Accepted
## Consequences
### Positive
- Application-specified block retention allows the application to take all relevant factors into account and prevent necessary blocks from being accidentally removed.
- Node operators can independently decide whether they want to provide complete block histories (if local configuration for this is provided) and snapshots.
### Negative
- Social coordination is required to run archival nodes, failure to do so may lead to permanent loss of historical blocks.
- Social coordination is required to run snapshot nodes, failure to do so may lead to inability to run state sync, and inability to bootstrap new nodes at all if no archival nodes are online.
### Neutral
- Reduced block retention requires application changes, and cannot be controlled directly in Tendermint.
- Application-specified block retention may set a lower bound on disk space requirements for all nodes.
## References
- State sync ADR: <https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-053-state-sync-prototype.md>
- State sync issue: <https://github.com/tendermint/tendermint/issues/828>
- Block pruning issue: <https://github.com/tendermint/tendermint/issues/3652>

+ 82
- 0
docs/architecture/adr-078-nonzero-genesis.md View File

@ -0,0 +1,82 @@
# ADR 078: Non-Zero Genesis
## Changelog
- 2020-07-26: Initial draft (@erikgrinaker)
- 2020-07-28: Use weak chain linking, i.e. `predecessor` field (@erikgrinaker)
- 2020-07-31: Drop chain linking (@erikgrinaker)
- 2020-08-03: Add `State.InitialHeight` (@erikgrinaker)
- 2021-02-11: Migrate to tendermint repo (Originally [RFC 002](https://github.com/tendermint/spec/pull/119))
## Author(s)
- Erik Grinaker (@erikgrinaker)
## Context
The recommended upgrade path for block protocol-breaking upgrades is currently to hard fork the
chain (see e.g. [`cosmoshub-3` upgrade](https://blog.cosmos.network/cosmos-hub-3-upgrade-announcement-39c9da941aee).
This is done by halting all validators at a predetermined height, exporting the application
state via application-specific tooling, and creating an entirely new chain using the exported
application state.
As far as Tendermint is concerned, the upgraded chain is a completely separate chain, with e.g.
a new chain ID and genesis file. Notably, the new chain starts at height 1, and has none of the
old chain's block history. This causes problems for integrators, e.g. coin exchanges and
wallets, that assume a monotonically increasing height for a given blockchain. Users also find
it confusing that a given height can now refer to distinct states depending on the chain
version.
An ideal solution would be to always retain block backwards compatibility in such a way that chain
history is never lost on upgrades. However, this may require a significant amount of engineering
work that is not viable for the planned Stargate release (Tendermint 0.34), and may prove too
restrictive for future development.
As a first step, allowing the new chain to start from an initial height specified in the genesis
file would at least provide monotonically increasing heights. There was a proposal to include the
last block header of the previous chain as well, but since the genesis file is not verified and
hashed (only specific fields are) this would not be trustworthy.
External tooling will be required to map historical heights onto e.g. archive nodes that contain
blocks from previous chain version. Tendermint will not include any such functionality.
## Proposal
Tendermint will allow chains to start from an arbitrary initial height:
- A new field `initial_height` is added to the genesis file, defaulting to `1`. It can be set to any
non-negative integer, and `0` is considered equivalent to `1`.
- A new field `InitialHeight` is added to the ABCI `RequestInitChain` message, with the same value
and semantics as the genesis field.
- A new field `InitialHeight` is added to the `state.State` struct, where `0` is considered invalid.
Including the field here simplifies implementation, since the genesis value does not have to be
propagated throughout the code base separately, but it is not strictly necessary.
ABCI applications may have to be updated to handle arbitrary initial heights, otherwise the initial
block may fail.
## Status
Accepted
## Consequences
### Positive
- Heights can be unique throughout the history of a "logical" chain, across hard fork upgrades.
### Negative
- Upgrades still cause loss of block history.
- Integrators will have to map height ranges to specific archive nodes/networks to query history.
### Neutral
- There is no explicit link to the last block of the previous chain.
## References
- [#2543: Allow genesis file to start from non-zero height w/ prev block header](https://github.com/tendermint/tendermint/issues/2543)

+ 57
- 0
docs/architecture/adr-079-ed25519-verification.md View File

@ -0,0 +1,57 @@
# ADR 079: Ed25519 Verification
## Changelog
- 2020-08-21: Initial RFC
- 2021-02-11: Migrate RFC to tendermint repo (Originally [RFC 003](https://github.com/tendermint/spec/pull/144))
## Author(s)
- Marko (@marbar3778)
## Context
Ed25519 keys are the only supported key types for Tendermint validators currently. Tendermint-Go wraps the ed25519 key implementation from the go standard library. As more clients are implemented to communicate with the canonical Tendermint implementation (Tendermint-Go) different implementations of ed25519 will be used. Due to [RFC 8032](https://www.rfc-editor.org/rfc/rfc8032.html) not guaranteeing implementation compatibility, Tendermint clients must to come to an agreement of how to guarantee implementation compatibility. [Zcash](https://z.cash/) has multiple implementations of their client and have identified this as a problem as well. The team at Zcash has made a proposal to address this issue, [Zcash improvement proposal 215](https://zips.z.cash/zip-0215).
## Proposal
- Tendermint-Go would adopt [hdevalence/ed25519consensus](https://github.com/hdevalence/ed25519consensus).
- This library is implements `ed25519.Verify()` in accordance to zip-215. Tendermint-go will continue to use `crypto/ed25519` for signing and key generation.
- Tendermint-rs would adopt [ed25519-zebra](https://github.com/ZcashFoundation/ed25519-zebra)
- related [issue](https://github.com/informalsystems/tendermint-rs/issues/355)
Signature verification is one of the major bottlenecks of Tendermint-go, batch verification can not be used unless it has the same consensus rules, ZIP 215 makes verification safe in consensus critical areas.
This change constitutes a breaking changes, therefore must be done in a major release. No changes to validator keys or operations will be needed for this change to be enabled.
This change has no impact on signature aggregation. To enable this signature aggregation Tendermint will have to use different signature schema (Schnorr, BLS, ...). Secondly, this change will enable safe batch verification for the Tendermint-Go client. Batch verification for the rust client is already supported in the library being used.
As part of the acceptance of this proposal it would be best to contract or discuss with a third party the process of conducting a security review of the go library.
## Status
Proposed
## Consequences
### Positive
- Consistent signature verification across implementations
- Enable safe batch verification
### Negative
#### Tendermint-Go
- Third_party dependency
- library has not gone through a security review.
- unclear maintenance schedule
- Fragmentation of the ed25519 key for the go implementation, verification is done using a third party library while the rest
uses the go standard library
### Neutral
## References
[It’s 255:19AM. Do you know what your validation criteria are?](https://hdevalence.ca/blog/2020-10-04-its-25519am)

+ 203
- 0
docs/architecture/adr-080-reverse-sync.md View File

@ -0,0 +1,203 @@
# ADR 080: ReverseSync - fetching historical data
## Changelog
- 2021-02-11: Migrate to tendermint repo (Originally [RFC 005](https://github.com/tendermint/spec/pull/224))
- 2021-04-19: Use P2P to gossip necessary data for reverse sync.
- 2021-03-03: Simplify proposal to the state sync case.
- 2021-02-17: Add notes on asynchronicity of processes.
- 2020-12-10: Rename backfill blocks to reverse sync.
- 2020-11-25: Initial draft.
## Author(s)
- Callum Waters (@cmwaters)
## Context
Two new features: [Block pruning](https://github.com/tendermint/tendermint/issues/3652)
and [State sync](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-042-state-sync.md)
meant nodes no longer needed a complete history of the blockchain. This
introduced some challenges of its own which were covered and subsequently
tackled with [RFC-001](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-077-block-retention.md).
The RFC allowed applications to set a block retention height; an upper bound on
what blocks would be pruned. However nodes who state sync past this upper bound
(which is necessary as snapshots must be saved within the trusting period for
the assisting light client to verify) have no means of backfilling the blocks
to meet the retention limit. This could be a problem as nodes who state sync and
then eventually switch to consensus (or fast sync) may not have the block and
validator history to verify evidence causing them to panic if they see 2/3
commit on what the node believes to be an invalid block.
Thus, this RFC sets out to instil a minimum block history invariant amongst
honest nodes.
## Proposal
A backfill mechanism can simply be defined as an algorithm for fetching,
verifying and storing, headers and validator sets of a height prior to the
current base of the node's blockchain. In matching the terminology used for
other data retrieving protocols (i.e. fast sync and state sync), we
call this method **ReverseSync**.
We will define the mechanism in four sections:
- Usage
- Design
- Verification
- Termination
### Usage
For now, we focus purely on the case of a state syncing node, whom after
syncing to a height will need to verify historical data in order to be capable
of processing new blocks. We can denote the earliest height that the node will
need to verify and store in order to be able to verify any evidence that might
arise as the `max_historical_height`/`time`. Both height and time are necessary
as this maps to the BFT time used for evidence expiration. After acquiring
`State`, we calculate these parameters as:
```go
max_historical_height = max(state.InitialHeight, state.LastBlockHeight - state.ConsensusParams.EvidenceAgeHeight)
max_historical_time = max(GenesisTime, state.LastBlockTime.Sub(state.ConsensusParams.EvidenceAgeTime))
```
Before starting either fast sync or consensus, we then run the following
synchronous process:
```go
func ReverseSync(max_historical_height int64, max_historical_time time.Time) error
```
Where we fetch and verify blocks until a block `A` where
`A.Height <= max_historical_height` and `A.Time <= max_historical_time`.
Upon successfully reverse syncing, a node can now safely continue. As this
feature is only used as part of state sync, one can think of this as merely an
extension to it.
In the future we may want to extend this functionality to allow nodes to fetch
historical blocks for reasons of accountability or data accessibility.
### Design
This section will provide a high level overview of some of the more important
characteristics of the design, saving the more tedious details as an ADR.
#### P2P
Implementation of this RFC will require the addition of a new channel and two
new messages.
```proto
message LightBlockRequest {
uint64 height = 1;
}
```
```proto
message LightBlockResponse {
Header header = 1;
Commit commit = 2;
ValidatorSet validator_set = 3;
}
```
The P2P path may also enable P2P networked light clients and a state sync that
also doesn't need to rely on RPC.
### Verification
ReverseSync is used to fetch the following data structures:
- `Header`
- `Commit`
- `ValidatorSet`
Nodes will also need to be able to verify these. This can be achieved by first
retrieving the header at the base height from the block store. From this trusted
header, the node hashes each of the three data structures and checks that they are correct.
1. The trusted header's last block ID matches the hash of the new header
```go
header[height].LastBlockID == hash(header[height-1])
```
2. The trusted header's last commit hash matches the hash of the new commit
```go
header[height].LastCommitHash == hash(commit[height-1])
```
3. Given that the node now trusts the new header, check that the header's validator set
hash matches the hash of the validator set
```go
header[height-1].ValidatorsHash == hash(validatorSet[height-1])
```
### Termination
ReverseSync draws a lot of parallels with fast sync. An important consideration
for fast sync that also extends to ReverseSync is termination. ReverseSync will
finish it's task when one of the following conditions have been met:
1. It reaches a block `A` where `A.Height <= max_historical_height` and
`A.Time <= max_historical_time`.
2. None of it's peers reports to have the block at the height below the
processes current block.
3. A global timeout.
This implies that we can't guarantee adequate history and thus the term
"invariant" can't be used in the strictest sense. In the case that the first
condition isn't met, the node will log an error and optimistically attempt
to continue with either fast sync or consensus.
## Alternative Solutions
The need for a minimum block history invariant stems purely from the need to
validate evidence (although there may be some application relevant needs as
well). Because of this, an alternative, could be to simply trust whatever the
2/3+ majority has agreed upon and in the case where a node is at the head of the
blockchain, you simply abstain from voting.
As it stands, if 2/3+ vote on evidence you can't verify, in the same manner if
2/3+ vote on a header that a node sees as invalid (perhaps due to a different
app hash), the node will halt.
Another alternative is the method with which the relevant data is retrieved.
Instead of introducing new messages to the P2P layer, RPC could have been used
instead.
The aforementioned data is already available via the following RPC endpoints:
`/commit` for `Header`'s' and `/validators` for `ValidatorSet`'s'. It was
decided predominantly due to the instability of the current RPC infrastructure
that P2P be used instead.
## Status
Proposed
## Consequences
### Positive
- Ensures a minimum block history invariant for honest nodes. This will allow
nodes to verify evidence.
### Negative
- Statesync will be slower as more processing is required.
### Neutral
- By having validator sets served through p2p, this would make it easier to
extend p2p support to light clients and state sync.
- In the future, it may also be possible to extend this feature to allow for
nodes to freely fetch and verify prior blocks
## References
- [RFC-001: Block retention](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-077-block-retention.md)
- [Original issue](https://github.com/tendermint/tendermint/issues/4629)

+ 23
- 5
docs/architecture/adr-template.md View File

@ -6,12 +6,30 @@
## Status
> A decision may be "proposed" if it hasn't been agreed upon yet, or "accepted"
> once it is agreed upon. Once the ADR has been implemented mark the ADR as
> "implemented". If a later ADR changes or reverses a decision, it may be marked
> as "deprecated" or "superseded" with a reference to its replacement.
> An architecture decision is considered "proposed" when a PR containing the ADR
> is submitted. When merged, an ADR must have a status associated with it, which
> must be one of: "Accepted", "Rejected", "Deprecated" or "Superseded".
>
> An accepted ADR's implementation status must be tracked via a tracking issue,
> milestone or project board (only one of these is necessary). For example:
>
> Accepted
>
> [Tracking issue](https://github.com/tendermint/tendermint/issues/123)
> [Milestone](https://github.com/tendermint/tendermint/milestones/123)
> [Project board](https://github.com/orgs/tendermint/projects/123)
>
> Rejected ADRs are captured as a record of recommendations that we specifically
> do not (and possibly never) want to implement. The ADR itself must, for
> posterity, include reasoning as to why it was rejected.
>
> If an ADR is deprecated, simply write "Deprecated" in this section. If an ADR
> is superseded by one or more other ADRs, provide local a reference to those
> ADRs, e.g.:
>
> Superseded by [ADR 123](./adr-123.md)
{Deprecated|Declined|Accepted|Implemented}
Accepted | Rejected | Deprecated | Superseded by
## Context


BIN
docs/architecture/img/block-retention.png View File

Before After
Width: 2426  |  Height: 1547  |  Size: 52 KiB

+ 1
- 1
docs/nodes/configuration.md View File

@ -606,7 +606,7 @@ There are three new parameters, which are enabled if use-legacy is set to false.
- `max-connections` = is the max amount of allowed inbound and outbound connections.
### Deprecated Parameters
> Note: For Tendermint 0.35, there are two p2p implementations. The old version is used by deafult with the deprecated fields. The new implementation uses different config parameters, explained above.
> Note: For Tendermint 0.35, there are two p2p implementations. The old version is used by default with the deprecated fields. The new implementation uses different config parameters, explained above.
- `max-num-inbound-peers` = is the maximum number of peers you will accept inbound connections from at one time (where they dial your address and initiate the connection). *This was replaced by `max-connections`*
- `max-num-outbound-peers` = is the maximum number of peers you will initiate outbound connects to at one time (where you dial their address and initiate the connection).*This was replaced by `max-connections`*


+ 2
- 2
docs/nodes/logging.md View File

@ -50,7 +50,7 @@ little overview what they do.
they are coming from peers or the application.
- `p2p` Provides an abstraction around peer-to-peer communication. For
more details, please check out the
[README](https://github.com/tendermint/spec/tree/master/spec/p2p).
[README](https://github.com/tendermint/tendermint/tree/master/spec/p2p).
- `rpc-server` RPC server. For implementation details, please read the
[doc.go](https://github.com/tendermint/tendermint/blob/master/rpc/jsonrpc/doc.go).
- `state` Represents the latest state and execution submodule, which
@ -120,7 +120,7 @@ Next follows a standard block creation cycle, where we enter a new
round, propose a block, receive more than 2/3 of prevotes, then
precommits and finally have a chance to commit a block. For details,
please refer to [Byzantine Consensus
Algorithm](https://github.com/tendermint/spec/blob/master/spec/consensus/consensus.md).
Algorithm](https://github.com/tendermint/tendermint/blob/master/spec/consensus/consensus.md).
```sh
I[10-04|13:54:30.393] enterNewRound(91/0). Current: 91/0/RoundStepNewHeight module=consensus


+ 1
- 0
docs/nodes/metrics.md View File

@ -40,6 +40,7 @@ The following metrics are available:
| consensus_fast_syncing | gauge | | either 0 (not fast syncing) or 1 (syncing) |
| consensus_state_syncing | gauge | | either 0 (not state syncing) or 1 (syncing) |
| consensus_block_size_bytes | Gauge | | Block size in bytes |
| evidence_pool_num_evidence | Gauge | | Number of evidence in the evidence pool
| p2p_peers | Gauge | | Number of peers node's connected to |
| p2p_peer_receive_bytes_total | counter | peer_id, chID | number of bytes per channel received from a given peer |
| p2p_peer_send_bytes_total | counter | peer_id, chID | number of bytes per channel sent to a given peer |


+ 1
- 1
docs/nodes/running-in-production.md View File

@ -83,7 +83,7 @@ for more information.
Rate-limiting and authentication are another key aspects to help protect
against DOS attacks. Validators are supposed to use external tools like
[NGINX](https://www.nginx.com/blog/rate-limiting-nginx/) or
[traefik](https://docs.traefik.io/middlewares/ratelimit/)
[traefik](https://doc.traefik.io/traefik/middlewares/http/ratelimit/)
to achieve the same things.
## Debugging Tendermint


+ 3
- 3
docs/nodes/validators.md View File

@ -109,9 +109,9 @@ Currently Tendermint uses [Ed25519](https://ed25519.cr.yp.to/) keys which are wi
> **+2/3 is short for "more than 2/3"**
A block is committed when +2/3 of the validator set sign [precommit
votes](https://github.com/tendermint/spec/blob/953523c3cb99fdb8c8f7a2d21e3a99094279e9de/spec/blockchain/blockchain.md#vote) for that block at the same `round`.
votes](https://github.com/tendermint/tendermint/blob/953523c3cb99fdb8c8f7a2d21e3a99094279e9de/spec/blockchain/blockchain.md#vote) for that block at the same `round`.
The +2/3 set of precommit votes is called a
[_commit_](https://github.com/tendermint/spec/blob/953523c3cb99fdb8c8f7a2d21e3a99094279e9de/spec/blockchain/blockchain.md#commit). While any +2/3 set of
[_commit_](https://github.com/tendermint/tendermint/blob/953523c3cb99fdb8c8f7a2d21e3a99094279e9de/spec/blockchain/blockchain.md#commit). While any +2/3 set of
precommits for the same block at the same height&round can serve as
validation, the canonical commit is included in the next block (see
[LastCommit](https://github.com/tendermint/spec/blob/953523c3cb99fdb8c8f7a2d21e3a99094279e9de/spec/blockchain/blockchain.md#lastcommit)).
[LastCommit](https://github.com/tendermint/tendermint/blob/953523c3cb99fdb8c8f7a2d21e3a99094279e9de/spec/blockchain/blockchain.md#lastcommit)).

+ 14285
- 20
docs/package-lock.json
File diff suppressed because it is too large
View File


+ 1
- 0
docs/package.json View File

@ -7,6 +7,7 @@
"vuepress-theme-cosmos": "^1.0.183"
},
"devDependencies": {
"@vuepress/plugin-html-redirect": "^0.1.4",
"watchpack": "^2.3.1"
},
"scripts": {


+ 1
- 1
docs/pre.sh View File

@ -1,4 +1,4 @@
#!/bin/bash
cp -a ../rpc/openapi/ .vuepress/public/rpc/
git clone https://github.com/tendermint/spec.git specRepo && cp -r specRepo/spec . && rm -rf specRepo
cp -r ../spec .

+ 4
- 1
docs/rfc/README.md View File

@ -45,9 +45,12 @@ sections.
- [RFC-005: Event System](./rfc-005-event-system.rst)
- [RFC-006: Event Subscription](./rfc-006-event-subscription.md)
- [RFC-007: Deterministic Proto Byte Serialization](./rfc-007-deterministic-proto-bytes.md)
- [RFC-008: Don't Panic](./rfc-008-don't-panic.md)
- [RFC-008: Don't Panic](./rfc-008-do-not-panic.md)
- [RFC-009: Consensus Parameter Upgrades](./rfc-009-consensus-parameter-upgrades.md)
- [RFC-010: P2P Light Client](./rfc-010-p2p-light-client.rst)
- [RFC-011: Delete Gas](./rfc-011-delete-gas.md)
- [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-NNN: Title](./rfc-NNN-title.md) -->

BIN
docs/rfc/images/abci++.png View File

Before After
Width: 2536  |  Height: 1424  |  Size: 2.7 MiB

BIN
docs/rfc/images/abci.png View File

Before After
Width: 2532  |  Height: 1430  |  Size: 2.5 MiB

+ 2
- 2
docs/rfc/rfc-002-ipc-ecosystem.md View File

@ -407,7 +407,7 @@ discussed.
## References
[abci]: https://github.com/tendermint/spec/tree/95cf253b6df623066ff7cd4074a94e7a3f147c7a/spec/abci
[abci]: https://github.com/tendermint/tendermint/tree/master/spec/abci
[rpc-service]: https://docs.tendermint.com/master/rpc/
[light-client]: https://docs.tendermint.com/master/tendermint-core/light-client.html
[tm-cli]: https://github.com/tendermint/tendermint/tree/master/cmd/tendermint
@ -416,5 +416,5 @@ discussed.
[socket-server]: https://github.com/tendermint/tendermint/blob/master/abci/server/socket_server.go
[sdk-grpc]: https://pkg.go.dev/github.com/cosmos/cosmos-sdk/types/tx#ServiceServer
[json-rpc]: https://www.jsonrpc.org/specification
[abci-conn]: https://github.com/tendermint/spec/blob/master/spec/abci/apps.md#state
[abci-conn]: https://github.com/tendermint/tendermint/blob/master/spec/abci/apps.md#state
[adr-57]: https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-057-RPC.md

+ 8
- 8
docs/rfc/rfc-003-performance-questions.md View File

@ -1,4 +1,4 @@
# RFC 003: Taxonomy of potential performance issues in Tendermint
# RFC 003: Taxonomy of potential performance issues in Tendermint
## Changelog
@ -35,7 +35,7 @@ This section attempts to delineate the different sections of Tendermint function
that are often cited as having performance issues. It raises questions and suggests
lines of inquiry that may be valuable for better understanding Tendermint's performance issues.
As a note: We should avoid quickly adding many microbenchmarks or package level benchmarks.
As a note: We should avoid quickly adding many microbenchmarks or package level benchmarks.
These are prone to being worse than useless as they can obscure what _should_ be
focused on: performance of the system from the perspective of a user. We should,
instead, tune performance with an eye towards user needs and actions users make. These users comprise
@ -116,7 +116,7 @@ the Tendermint node.
ABCI delivers blocks in several methods: `BeginBlock`, `DeliverTx`, `EndBlock`, `Commit`.
Tendermint delivers transactions one-by-one via the `DeliverTx` call. Most of the
Tendermint delivers transactions one-by-one via the `DeliverTx` call. Most of the
transaction delivery in Tendermint occurs asynchronously and therefore appears unlikely to
form a bottleneck in ABCI.
@ -153,7 +153,7 @@ slow during queries, as ABCI is no longer able to make progress. This is known
to be causing issue in the cosmos-sdk and is being addressed [in the sdk][sdk-query-fix]
but a more robust solution may be required. Adding metrics to each ABCI client connection
and message as described in the Application section of this document would allow us
to further introspect the issue here.
to further introspect the issue here.
#### Claim: RPC Serialization may cause slowdown
@ -169,8 +169,8 @@ The other JSON-RPC methods are much less critical to the core functionality of T
While there may other points of performance consideration within the RPC, methods that do not
receive high volumes of requests should not be prioritized for performance consideration.
NOTE: Previous discussion of the RPC framework was done in [ADR 57][adr-57] and
there is ongoing work to inspect and alter the JSON-RPC framework in [RFC 002][rfc-002].
NOTE: Previous discussion of the RPC framework was done in [ADR 57][adr-57] and
there is ongoing work to inspect and alter the JSON-RPC framework in [RFC 002][rfc-002].
Much of these RPC-related performance considerations can either wait until the work of RFC 002 work is done or be
considered concordantly with the in-flight changes to the JSON-RPC.
@ -207,7 +207,7 @@ in Tendermint. Namely, it is being considered as part of the [modular hashing pr
It is currently unknown if hashing transactions in the Mempool forms a significant bottleneck.
Although it does not appear to be documented as slow, there are a few open github
issues that indicate a possible user preference for a faster hashing algorithm,
including [issue 2187][issue-2187] and [issue 2186][issue-2186].
including [issue 2187][issue-2187] and [issue 2186][issue-2186].
It is likely worth investigating what order of magnitude Tx hashing takes in comparison to other
aspects of adding a Tx to the mempool. It is not currently clear if the rate of adding Tx
@ -272,7 +272,7 @@ event sends. The following metrics would be a good start for tracking this perfo
[rfc-002]: https://github.com/tendermint/tendermint/pull/6913
[adr-57]: https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-057-RPC.md
[issue-1319]: https://github.com/tendermint/tendermint/issues/1319
[abci-commit-description]: https://github.com/tendermint/spec/blob/master/spec/abci/apps.md#commit
[abci-commit-description]: https://github.com/tendermint/tendermint/blob/master/spec/abci/apps.md#commit
[abci-local-client-code]: https://github.com/tendermint/tendermint/blob/511bd3eb7f037855a793a27ff4c53c12f085b570/abci/client/local_client.go#L84
[hub-signature]: https://github.com/cosmos/gaia/blob/0ecb6ed8a244d835807f1ced49217d54a9ca2070/docs/resources/genesis.md#consensus-parameters
[ed25519-bench]: https://github.com/oasisprotocol/curve25519-voi/blob/d2e7fc59fe38c18ca990c84c4186cba2cc45b1f9/PERFORMANCE.md


docs/rfc/rfc-008-don't-panic.md → docs/rfc/rfc-008-do-not-panic.md View File


+ 352
- 0
docs/rfc/rfc-012-custom-indexing.md View File

@ -0,0 +1,352 @@
# RFC 012: Event Indexing Revisited
## Changelog
- 11-Feb-2022: Add terminological notes.
- 10-Feb-2022: Updated from review feedback.
- 07-Feb-2022: Initial draft (@creachadair)
## Abstract
A Tendermint node allows ABCI events associated with block and transaction
processing to be "indexed" into persistent storage. The original Tendermint
implementation provided a fixed, built-in [proprietary indexer][kv-index] for
such events.
In response to user requests to customize indexing, [ADR 065][adr065]
introduced an "event sink" interface that allows developers (at least in
theory) to plug in alternative index storage.
Although ADR-065 was a good first step toward customization, its implementation
model does not satisfy all the user requirements. Moreover, this approach
leaves some existing technical issues with indexing unsolved.
This RFC documents these concerns, and discusses some potential approaches to
solving them. This RFC does _not_ propose a specific technical decision. It is
meant to unify and focus some of the disparate discussions of the topic.
## Background
We begin with some important terminological context. The term "event" in
Tendermint can be confusing, as the same word is used for multiple related but
distinct concepts:
1. **ABCI Events** refer to the key-value metadata attached to blocks and
transactions by the application. These values are represented by the ABCI
`Event` protobuf message type.
2. **Consensus Events** refer to the data published by the Tendermint node to
its pubsub bus in response to various consensus state transitions and other
important activities, such as round updates, votes, transaction delivery,
and block completion.
This confusion is compounded because some "consensus event" values also have
"ABCI event" metadata attached to them. Notably, block and transaction items
typically have ABCI metadata assigned by the application.
Indexers and RPC clients subscribed to the pubsub bus receive **consensus
events**, but they identify which ones to care about using query expressions
that match against the **ABCI events** associated with them.
In the discussion that follows, we will use the term **event item** to refer to
a datum published to or received from the pubsub bus, and **ABCI event** or
**event metadata** to refer to the key/value annotations.
**Indexing** in this context means recording the association between certain
ABCI metadata and the blocks or transactions they're attached to. The ABCI
metadata typically carry application-specific details like sender and recipient
addresses, catgory tags, and so forth, that are not part of consensus but are
used by UI tools to find and display transactions of interest.
The consensus node records the blocks and transactions as part of its block
store, but does not persist the application metadata. Metadata persistence is
the task of the indexer, which can be (optionally) enabled by the node
operator.
### History
The [original indexer][kv-index] built in to Tendermint stored index data in an
embedded [`tm-db` database][tmdb] with a proprietary key layout.
In [ADR 065][adr065], we noted that this implementation has both performance
and scaling problems under load. Moreover, the only practical way to query the
index data is via the [query filter language][query] used for event
subscription. [Issue #1161][i1161] appears to be a motivational context for that ADR.
To mitigate both of these concerns, we introduced the [`EventSink`][esink]
interface, combining the original transaction and block indexer interfaces
along with some service plumbing. Using this interface, a developer can plug
in an indexer that uses a more efficient storage engine, and provides a more
expressive query language. As a proof-of-concept, we built a [PostgreSQL event
sink][psql] that exports data to a [PostgreSQL database][postgres].
Although this approach addressed some of the immediate concerns, there are
several issues for custom indexing that have not been fully addressed. Here we
will discuss them in more detail.
For further context, including links to user reports and related work, see also
the [Pluggable custom event indexing tracking issue][i7135] issue.
### Issue 1: Tight Coupling
The `EventSink` interface supports multiple implementations, but plugging in
implementations still requires tight integration with the node. In particular:
- Any custom indexer must either be written in Go and compiled in to the
Tendermint binary, or the developer must write a Go shim to communicate with
the implementation and build that into the Tendermint binary.
- This means to support a custom indexer, it either has to be integrated into
the Tendermint core repository, or every installation that uses that indexer
must fetch or build a patched version of Tendermint.
The problem with integrating indexers into Tendermint Core is that every user
of Tendermint Core takes a dependency on all supported indexers, including
those they never use. Even if the unused code is disabled with build tags,
users have to remember to do this or potentially be exposed to security issues
that may arise in any of the custom indexers. This is a risk for Tendermint,
which is a trust-critical component of all applications built on it.
The problem with _not_ integrating indexers into Tendermint Core is that any
developer who wants to use a particular indexer must now fetch or build a
patched version of the core code that includes the custom indexer. Besides
being inconvenient, this makes it harder for users to upgrade their node, since
they need to either re-apply their patches directly or wait for an intermediary
to do it for them.
Even for developers who have written their applications in Go and link with the
consensus node directly (e.g., using the [Cosmos SDK][sdk]), these issues add a
potentially significant complication to the build process.
### Issue 2: Legacy Compatibility
The `EventSink` interface retains several limitations of the original
proprietary indexer. These include:
- The indexer has no control over which event items are reported. Only the
exact block and transaction events that were reported to the original indexer
are reported to a custom indexer.
- The interface requires the implementation to define methods for the legacy
search and query API. This requirement comes from the integation with the
[event subscription RPC API][event-rpc], but actually supporting these
methods is not trivial.
At present, only the original KV indexer implements the query methods. Even the
proof-of-concept PostgreSQL implementation simply reports errors for all calls
to these methods.
Even for a plugin written in Go, implementing these methods "correctly" would
require parsing and translating the custom query language over whatever storage
platform the indexer uses.
For a plugin _not_ written in Go, even beyond the cost of integration the
developer would have to re-implement the entire query language.
### Issue 3: Indexing Delays Consensus
Within the node, indexing hooks in to the same internal pubsub dispatcher that
is used to export event items to the [event subscription RPC API][event-rpc].
In contrast with RPC subscribers, however, indexing is a "privileged"
subscriber: If an RPC subscriber is "too slow", the node may terminate the
subscription and disconnect the client. That means that RPC subscribers may
lose (miss) event items. The indexer, however, is "unbuffered", and the
publisher will never drop or disconnect from it. If the indexer is slow, the
publisher will block until it returns, to ensure that no event items are lost.
In practice, this means that the performance of the indexer has a direct effect
on the performance of the consensus node: If the indexer is slow or stalls, it
will slow or halt the progress of consensus. Users have already reported this
problem even with the built-in indexer (see, for example, [#7247][i7247]).
Extending this concern to arbitrary user-defined custom indexers gives that
risk a much larger surface area.
## Discussion
It is not possible to simultaneously guarantee that publishing event items will
not delay consensus, and also that all event items of interest are always
completely indexed.
Therefore, our choice is between eliminating delay (and minimizing loss) or
eliminating loss (and minimizing delay). Currently, we take the second
approach, which has led to user complaints about consensus delays due to
indexing and subscription overhead.
- If we agree that consensus performance supersedes index completeness, our
design choices are to constrain the likelihood and frequency of missing event
items.
- If we decide that consensus performance is more important than index
completeness, our option is to minimize overhead on the event delivery path
and document that indexer plugins constrain the rate of consensus.
Since we have user reports requesting both properties, we have to choose one or
the other. Since the primary job of the consensus engine is to correctly,
robustly, reliablly, and efficiently replicate application state across the
network, I believe the correct choice is to favor consensus performance.
An important consideration for this decision is that a node does not index
application metadata separately: If indexing is disabled, there is no built-in
mechanism to go back and replay or reconstruct the data that an indexer would
have stored. The node _does_ store the blockchain itself (i.e., the blocks and
their transactions), so potentially some use cases currently handled by the
indexer could be handled by the node. For example, allowing clients to ask
whether a given transaction ID has been committed to a block could in principle
be done without an indexer, since it does not depend on application metadata.
Inevitably, a question will arise whether we could implement both strategies
and toggle between them with a flag. That would be a worst-case scenario,
requiring us to maintain the complexity of two very-different operational
concerns. If our goal is that Tendermint should be as simple, efficient, and
trustworthy as posible, there is not a strong case for making these options
configurable: We should pick a side and commit to it.
### Design Principles
Although there is no unique "best" solution to the issues described above,
there are some specific principles that a solution should include:
1. **A custom indexer should not require integration into Tendermint core.** A
developer or node operator can create, build, deploy, and use a custom
indexer with a stock build of the Tendermint consensus node.
2. **Custom indexers cannot stall consensus.** An indexer that is slow or
stalls cannot slow down or prevent core consensus from making progress.
The plugin interface must give node operators control over the tolerances
for acceptable indexer performance, and the means to detect when indexers
are falling outside those tolerances, but indexer failures should "fail
safe" with respect to consensus (even if that means the indexer may miss
some data, in sufficiently-extreme circumstances).
3. **Custom indexers control which event items they index.** A custom indexer
is not limited to only the current transaction and block events, but can
observe any event item published by the node.
4. **Custom indexing is forward-compatible.** Adding new event item types or
metadata to the consensus node should not require existing custom indexers
to be rebuilt or modified, unless they want to take advantage of the new
data.
5. **Indexers are responsible for answering queries.** An indexer plugin is not
required to support the legacy query filter language, nor to be compatible
with the legacy RPC endpoints for accessing them. Any APIs for clients to
query a custom index are the responsibility of the indexer, not the node.
### Open Questions
Given the constraints outlined above, there are important design questions we
must answer to guide any specific changes:
1. **What is an acceptable probability that, given sufficiently extreme
operational issues, an indexer might miss some number of events?**
There are two parts to this question: One is what constitutes an extreme
operational problem, the other is how likely we are to miss some number of
events items.
- If the consensus is that no event item must ever be missed, no matter how
bad the operational circumstances, then we _must_ accept that indexing can
slow or halt consensus arbitrarily. It is impossible to guarantee complete
index coverage without potentially unbounded delays.
- Otherwise, how much data can we afford to lose and how often? For example,
if we can ensure no event item will be lost unless the indexer halts for
at least five minutes, is that acceptable? What probabilities and time
ranges are reasonable for real production environments?
2. **What level of operational overhead is acceptable to impose on node
operators to support indexing?**
Are node operators willing to configure and run custom indexers as sidecar
type processes alongside a node? How much indexer setup above and beyond the
work of setting up the underlying node in isolation is tractable in
production networks?
The answer to this question also informs the question of whether we should
keep an "in-process" indexing option, and to what extent that option needs
to satisfy the suggested design principles.
Relatedly, to what extent do we need to be concerned about the cost of
encoding and sending event items to an external process (e.g., as JSON blobs
or protobuf wire messages)? Given that the node already encodes event items
as JSON for subscription purposes, the overhead would be negligible for the
node itself, but the indexer would have to decode to process the results.
3. **What (if any) query APIs does the consensus node need to export,
independent of the indexer implementation?**
One typical example is whether the node should be able to answer queries
like "is this transaction ID in a block?" Currently, a node cannot answer
this query _unless_ it runs the built-in KV indexer. Does the node need to
continue to support that query even for nodes that disable the KV indexer,
or which use a custom indexer?
### Informal Design Intent
The design principles described above implicate several components of the
Tendermint node, beyond just the indexer. In the context of [ADR 075][adr075],
we are re-working the RPC event subscription API to improve some of the UX
issues discussed above for RPC clients. It is our expectation that a solution
for pluggable custom indexing will take advantage of some of the same work.
On that basis, the design approach I am considering for custom indexing looks
something like this (subject to refinement):
1. A custom indexer runs as a separate process from the node.
2. The indexer subscribes to event items via the ADR 075 events API.
This means indexers would receive event payloads as JSON rather than
protobuf, but since we already have to support JSON encoding for the RPC
interface anyway, that should not increase complexity for the node.
3. The existing PostgreSQL indexer gets reworked to have this form, and no
longer built as part of the Tendermint core binary.
We can retain the code in the core repository as a proof-of-concept, or
perhaps create a separate repository with contributed indexers and move it
there.
4. (Possibly) Deprecate and remove the legacy KV indexer, or disable it by
default. If we decide to remove it, we can also remove the legacy RPC
endpoints for querying the KV indexer.
If we plan to do this, we should also investigate providing a way for
clients to query whether a given transaction ID has landed in a block. That
serves a common need, and currently _only_ works if the KV indexer is
enabled, but could be addressed more simply using the other data a node
already has stored, without having to answer more general queries.
## References
- [ADR 065: Custom Event Indexing][adr065]
- [ADR 075: RPC Event Subscription Interface][adr075]
- [Cosmos SDK][sdk]
- [Event subscription RPC][event-rpc]
- [KV transaction indexer][kv-index]
- [Pluggable custom event indexing][i7135] (#7135)
- [PostgreSQL event sink][psql]
- [PostgreSQL database][postgres]
- [Query filter language][query]
- [Stream events to postgres for indexing][i1161] (#1161)
- [Unbuffered event subscription slow down the consensus][i7247] (#7247)
- [`EventSink` interface][esink]
- [`tm-db` library][tmdb]
[adr065]: https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-065-custom-event-indexing.md
[adr075]: https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-075-rpc-subscription.md
[esink]: https://pkg.go.dev/github.com/tendermint/tendermint/internal/state/indexer#EventSink
[event-rpc]: https://docs.tendermint.com/master/rpc/#/Websocket/subscribe
[i1161]: https://github.com/tendermint/tendermint/issues/1161
[i7135]: https://github.com/tendermint/tendermint/issues/7135
[i7247]: https://github.com/tendermint/tendermint/issues/7247
[kv-index]: https://github.com/tendermint/tendermint/blob/master/internal/state/indexer/tx/kv
[postgres]: https://postgresql.org/
[psql]: https://github.com/tendermint/tendermint/blob/master/internal/state/indexer/sink/psql
[psql]: https://github.com/tendermint/tendermint/blob/master/internal/state/indexer/sink/psql
[query]: https://pkg.go.dev/github.com/tendermint/tendermint/internal/pubsub/query/syntax
[sdk]: https://github.com/cosmos/cosmos-sdk
[tmdb]: https://pkg.go.dev/github.com/tendermint/tm-db#DB

+ 253
- 0
docs/rfc/rfc-013-abci++.md View File

@ -0,0 +1,253 @@
# RFC 013: ABCI++
## Changelog
- 2020-01-11: initialized
- 2021-02-11: Migrate RFC to tendermint repo (Originally [RFC 004](https://github.com/tendermint/spec/pull/254))
## Author(s)
- Dev (@valardragon)
- Sunny (@sunnya97)
## Context
ABCI is the interface between the consensus engine and the application.
It defines when the application can talk to consensus during the execution of a blockchain.
At the moment, the application can only act at one phase in consensus, immediately after a block has been finalized.
This restriction on the application prohibits 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 do more than just finalize txs.
This includes features such as threshold cryptography, and guaranteed IBC connection attempts.
We propose introducing three new phases to ABCI to enable these new features, and renaming the existing methods for block execution.
#### Prepare Proposal phase
This phase aims to allow the block proposer to perform more computation, to reduce load on all other full nodes, and light clients in the network.
It is intended to enable features such as batch optimizations on the transaction data (e.g. signature aggregation, zk rollup style validity proofs, etc.), enabling stateless blockchains with validator provided authentication paths, etc.
This new phase will only be executed by the block proposer. The application will take in the block header and raw transaction data output by the consensus engine's mempool. It will then return block data that is prepared for gossip on the network, and additional fields to include into the block header.
#### Process Proposal Phase
This phase aims to allow applications to determine validity of a new block proposal, and execute computation on the block data, prior to the blocks finalization.
It is intended to enable applications to reject block proposals with invalid data, and to enable alternate pipelined execution models. (Such as Ethereum-style immediate execution)
This phase will be executed by all full nodes upon receiving a block, though on the application side it can do more work in the even that the current node is a validator.
#### Vote Extension Phase
This phase aims to allow applications to require their validators do more than just validate blocks.
Example usecases of this include validator determined price oracles, validator guaranteed IBC connection attempts, and validator based threshold crypto.
This adds an app-determined data field that every validator must include with their vote, and these will thus appear in the header.
#### Rename {BeginBlock, [DeliverTx], EndBlock} to FinalizeBlock
The prior phases gives the application more flexibility in their execution model for a block, and they obsolete the current methods for how the consensus engine relates the block data to the state machine. Thus we refactor the existing methods to better reflect what is happening in the new ABCI model.
This rename doesn't on its own enable anything new, but instead improves naming to clarify the expectations from the application in this new communication model. The existing ABCI methods `BeginBlock, [DeliverTx], EndBlock` are renamed to a single method called `FinalizeBlock`.
#### Summary
We include a more detailed list of features / scaling improvements that are blocked, and which new phases resolve them at the end of this document.
<image src="images/abci.png" style="float: left; width: 40%;" /> <image src="images/abci++.png" style="float: right; width: 40%;" />
On the top is the existing definition of ABCI, and on the bottom is the proposed ABCI++.
## Proposal
Below we suggest an API to add these three new phases.
In this document, sometimes the final round of voting is referred to as precommit for clarity in how it acts in the Tendermint case.
### Prepare Proposal
*Note, APIs in this section will change after Vote Extensions, we list the adjusted APIs further in the proposal.*
The Prepare Proposal phase allows the block proposer to perform application-dependent work in a block, to lower the amount of work the rest of the network must do. This enables batch optimizations to a block, which has been empirically demonstrated to be a key component for scaling. This phase introduces the following ABCI method
```rust
fn PrepareProposal(Block) -> BlockData
```
where `BlockData` is a type alias for however data is internally stored within the consensus engine. In Tendermint Core today, this is `[]Tx`.
The application may read the entire block proposal, and mutate the block data fields. Mutated transactions will still get removed from the mempool later on, as the mempool rechecks all transactions after a block is executed.
The `PrepareProposal` API will be modified in the vote extensions section, for allowing the application to modify the header.
### Process Proposal
The Process Proposal phase sends the block data to the state machine, prior to running the last round of votes on the state machine. This enables features such as allowing validators to reject a block according to whether state machine deems it valid, and changing block execution pipeline.
We introduce three new methods,
```rust
fn VerifyHeader(header: Header, isValidator: bool) -> ResponseVerifyHeader {...}
fn ProcessProposal(block: Block) -> ResponseProcessProposal {...}
fn RevertProposal(height: usize, round: usize) {...}
```
where
```rust
struct ResponseVerifyHeader {
accept_header: bool,
evidence: Vec<Evidence>
}
struct ResponseProcessProposal {
accept_block: bool,
evidence: Vec<Evidence>
}
```
Upon receiving a block header, every validator runs `VerifyHeader(header, isValidator)`. The reason for why `VerifyHeader` is split from `ProcessProposal` is due to the later sections for Preprocess Proposal and Vote Extensions, where there may be application dependent data in the header that must be verified before accepting the header.
If the returned `ResponseVerifyHeader.accept_header` is false, then the validator must precommit nil on this block, and reject all other precommits on this block. `ResponseVerifyHeader.evidence` is appended to the validators local `EvidencePool`.
Upon receiving an entire block proposal (in the current implementation, all "block parts"), every validator runs `ProcessProposal(block)`. If the returned `ResponseProcessProposal.accept_block` is false, then the validator must precommit nil on this block, and reject all other precommits on this block. `ResponseProcessProposal.evidence` is appended to the validators local `EvidencePool`.
Once a validator knows that consensus has failed to be achieved for a given block, it must run `RevertProposal(block.height, block.round)`, in order to signal to the application to revert any potentially mutative state changes it may have made. In Tendermint, this occurs when incrementing rounds.
**RFC**: How do we handle the scenario where honest node A finalized on round x, and honest node B finalized on round x + 1? (e.g. when 2f precommits are publicly known, and a validator precommits themself but doesn't broadcast, but they increment rounds) Is this a real concern? The state root derived could change if everyone finalizes on round x+1, not round x, as the state machine can depend non-uniformly on timestamp.
The application is expected to cache the block data for later execution.
The `isValidator` flag is set according to whether the current node is a validator or a full node. This is intended to allow for beginning validator-dependent computation that will be included later in vote extensions. (An example of this is threshold decryptions of ciphertexts.)
### DeliverTx rename to FinalizeBlock
After implementing `ProcessProposal`, txs no longer need to be delivered during the block execution phase. Instead, they are already in the state machine. Thus `BeginBlock, DeliverTx, EndBlock` can all be replaced with a single ABCI method for `ExecuteBlock`. Internally the application may still structure its method for executing the block as `BeginBlock, DeliverTx, EndBlock`. However, it is overly restrictive to enforce that the block be executed after it is finalized. There are multiple other, very reasonable pipelined execution models one can go for. So instead we suggest calling this succession of methods `FinalizeBlock`. We propose the following API
Replace the `BeginBlock, DeliverTx, EndBlock` ABCI methods with the following method
```rust
fn FinalizeBlock() -> ResponseFinalizeBlock
```
where `ResponseFinalizeBlock` has the following API, in terms of what already exists
```rust
struct ResponseFinalizeBlock {
updates: ResponseEndBlock,
tx_results: Vec<ResponseDeliverTx>
}
```
`ResponseEndBlock` should then be renamed to `ConsensusUpdates` and `ResponseDeliverTx` should be renamed to `ResponseTx`.
### Vote Extensions
The Vote Extensions phase allow applications to force their validators to do more than just validate within consensus. This is done by allowing the application to add more data to their votes, in the final round of voting. (Namely the precommit)
This additional application data will then appear in the block header.
First we discuss the API changes to the vote struct directly
```rust
fn ExtendVote(height: u64, round: u64) -> (UnsignedAppVoteData, SelfAuthenticatingAppData)
fn VerifyVoteExtension(signed_app_vote_data: Vec<u8>, self_authenticating_app_vote_data: Vec<u8>) -> bool
```
There are two types of data that the application can enforce validators to include with their vote.
There is data that the app needs the validator to sign over in their vote, and there can be self-authenticating vote data. Self-authenticating here means that the application upon seeing these bytes, knows its valid, came from the validator and is non-malleable. We give an example of each type of vote data here, to make their roles clearer.
- Unsigned app vote data: A use case of this is if you wanted validator backed oracles, where each validator independently signs some oracle data in their vote, and the median of these values is used on chain. Thus we leverage consensus' signing process for convenience, and use that same key to sign the oracle data.
- Self-authenticating vote data: A use case of this is in threshold random beacons. Every validator produces a threshold beacon share. This threshold beacon share can be verified by any node in the network, given the share and the validators public key (which is not the same as its consensus public key). However, this decryption share will not make it into the subsequent block's header. They will be aggregated by the subsequent block proposer to get a single random beacon value that will appear in the subsequent block's header. Everyone can then verify that this aggregated value came from the requisite threshold of the validator set, without increasing the bandwidth for full nodes or light clients. To achieve this goal, the self-authenticating vote data cannot be signed over by the consensus key along with the rest of the vote, as that would require all full nodes & light clients to know this data in order to verify the vote.
The `CanonicalVote` struct will acommodate the `UnsignedAppVoteData` field by adding another string to its encoding, after the `chain-id`. This should not interfere with existing hardware signing integrations, as it does not affect the constant offset for the `height` and `round`, and the vote size does not have an explicit upper bound. (So adding this unsigned app vote data field is equivalent from the HSM's perspective as having a superlong chain-ID)
**RFC**: Please comment if you think it will be fine to have elongate the message the HSM signs, or if we need to explore pre-hashing the app vote data.
The flow of these methods is that when a validator has to precommit, Tendermint will first produce a precommit canonical vote without the application vote data. It will then pass it to the application, which will return unsigned application vote data, and self authenticating application vote data. It will bundle the `unsigned_application_vote_data` into the canonical vote, and pass it to the HSM to sign. Finally it will package the self-authenticating app vote data, and the `signed_vote_data` together, into one final Vote struct to be passed around the network.
#### Changes to Prepare Proposal Phase
There are many use cases where the additional data from vote extensions can be batch optimized.
This is mainly of interest when the votes include self-authenticating app vote data that be batched together, or the unsigned app vote data is the same across all votes.
To allow for this, we change the PrepareProposal API to the following
```rust
fn PrepareProposal(Block, UnbatchedHeader) -> (BlockData, Header)
```
where `UnbatchedHeader` essentially contains a "RawCommit", the `Header` contains a batch-optimized `commit` and an additional "Application Data" field in its root. This will involve a number of changes to core data structures, which will be gone over in the ADR.
The `Unbatched` header and `rawcommit` will never be broadcasted, they will be completely internal to consensus.
#### Inter-process communication (IPC) effects
For brevity in exposition above, we did not discuss the trade-offs that may occur in interprocess communication delays that these changs will introduce.
These new ABCI methods add more locations where the application must communicate with the consensus engine.
In most configurations, we expect that the consensus engine and the application will be either statically or dynamically linked, so all communication is a matter of at most adjusting the memory model the data is layed out within.
This memory model conversion is typically considered negligible, as delay here is measured on the order of microseconds at most, whereas we face milisecond delays due to cryptography and network overheads.
Thus we ignore the overhead in the case of linked libraries.
In the case where the consensus engine and the application are ran in separate processes, and thus communicate with a form of Inter-process communication (IPC), the delays can easily become on the order of miliseconds based upon the data sent. Thus its important to consider whats happening here.
We go through this phase by phase.
##### Prepare proposal IPC overhead
This requires a round of IPC communication, where both directions are quite large. Namely the proposer communicating an entire block to the application.
However, this can be mitigated by splitting up `PrepareProposal` into two distinct, async methods, one for the block IPC communication, and one for the Header IPC communication.
Then for chains where the block data does not depend on the header data, the block data IPC communication can proceed in parallel to the prior block's voting phase. (As a node can know whether or not its the leader in the next round)
Furthermore, this IPC communication is expected to be quite low relative to the amount of p2p gossip time it takes to send the block data around the network, so this is perhaps a premature concern until more sophisticated block gossip protocols are implemented.
##### Process Proposal IPC overhead
This phase changes the amount of time available for the consensus engine to deliver a block's data to the state machine.
Before, the block data for block N would be delivered to the state machine upon receiving a commit for block N and then be executed.
The state machine would respond after executing the txs and before prevoting.
The time for block delivery from the consensus engine to the state machine after this change is the time of receiving block proposal N to the to time precommit on proposal N.
It is expected that this difference is unimportant in practice, as this time is in parallel to one round of p2p communication for prevoting, which is expected to be significantly less than the time for the consensus engine to deliver a block to the state machine.
##### Vote Extension IPC overhead
This has a small amount of data, but does incur an IPC round trip delay. This IPC round trip delay is pretty negligible as compared the variance in vote gossip time. (the IPC delay is typically on the order of 10 microseconds)
## Status
Proposed
## Consequences
### Positive
- Enables a large number of new features for applications
- Supports both immediate and delayed execution models
- Allows application specific data from each validator
- Allows for batch optimizations across txs, and votes
### Negative
- This is a breaking change to all existing ABCI clients, however the application should be able to have a thin wrapper to replicate existing ABCI behavior.
- PrepareProposal - can be a no-op
- Process Proposal - has to cache the block, but can otherwise be a no-op
- Vote Extensions - can be a no-op
- Finalize Block - Can black-box call BeginBlock, DeliverTx, EndBlock given the cached block data
- Vote Extensions adds more complexity to core Tendermint Data Structures
- Allowing alternate alternate execution models will lead to a proliferation of new ways for applications to violate expected guarantees.
### Neutral
- IPC overhead considerations change, but mostly for the better
## References
Reference for IPC delay constants: <http://pages.cs.wisc.edu/~adityav/Evaluation_of_Inter_Process_Communication_Mechanisms.pdf>
### Short list of blocked features / scaling improvements with required ABCI++ Phases
| Feature | PrepareProposal | ProcessProposal | Vote Extensions |
| :--- | :---: | :---: | :---: |
| Tx based signature aggregation | X | | |
| SNARK proof of valid state transition | X | | |
| Validator provided authentication paths in stateless blockchains | X | | |
| Immediate Execution | | X | |
| Simple soft forks | | X | |
| Validator guaranteed IBC connection attempts | | | X |
| Validator based price oracles | | | X |
| Immediate Execution with increased time for block execution | X | X | X |
| Threshold Encrypted txs | X | X | X |

+ 94
- 0
docs/rfc/rfc-014-semantic-versioning.md View File

@ -0,0 +1,94 @@
# RFC 014: Semantic Versioning
## Changelog
- 2021-11-19: Initial Draft
- 2021-02-11: Migrate RFC to tendermint repo (Originally [RFC 006](https://github.com/tendermint/spec/pull/365))
## Author(s)
- Callum Waters @cmwaters
## Context
We use versioning as an instrument to hold a set of promises to users and signal when such a set changes and how. In the conventional sense of a Go library, major versions signal that the public Go API’s have changed in a breaking way and thus require the users of such libraries to change their usage accordingly. Tendermint is a bit different in that there are multiple users: application developers (both in-process and out-of-process), node operators, and external clients. More importantly, both how these users interact with Tendermint and what's important to these users differs from how users interact and what they find important in a more conventional library.
This document attempts to encapsulate the discussions around versioning in Tendermint and draws upon them to propose a guide to how Tendermint uses versioning to make promises to its users.
For a versioning policy to make sense, we must also address the intended frequency of breaking changes. The strictest guarantees in the world will not help users if we plan to break them with every release.
Finally I would like to remark that this RFC only addresses the "what", as in what are the rules for versioning. The "how" of Tendermint implementing the versioning rules we choose, will be addressed in a later RFC on Soft Upgrades.
## Discussion
We first begin with a round up of the various users and a set of assumptions on what these users expect from Tendermint in regards to versioning:
1. **Application Developers**, those that use the ABCI to build applications on top of Tendermint, are chiefly concerned with that API. Breaking changes will force developers to modify large portions of their codebase to accommodate for the changes. Some ABCI changes such as introducing priority for the mempool don't require any effort and can be lazily adopted whilst changes like ABCI++ may force applications to redesign their entire execution system. It's also worth considering that the API's for go developers differ to developers of other languages. The former here can use the entire Tendermint library, most notably the local RPC methods, and so the team must be wary of all public Go API's.
2. **Node Operators**, those running node infrastructure, are predominantly concerned with downtime, complexity and frequency of upgrading, and avoiding data loss. They may be also concerned about changes that may break the scripts and tooling they use to supervise their nodes.
3. **External Clients** are those that perform any of the following:
- consume the RPC endpoints of nodes like `/block`
- subscribe to the event stream
- make queries to the indexer
This set are concerned with chain upgrades which will impact their ability to query state and block data as well as broadcast transactions. Examples include wallets and block explorers.
4. **IBC module and relayers**. The developers of IBC and consumers of their software are concerned about changes that may affect a chain's ability to send arbitrary messages to another chain. Specifically, these users are affected by any breaking changes to the light client verification algorithm.
Although we present them here as having different concerns, in a broader sense these user groups share a concern for the end users of applications. A crucial principle guiding this RFC is that **the ability for chains to provide continual service is more important than the actual upgrade burden put on the developers of these chains**. This means some extra burden for application developers is tolerable if it minimizes or substantially reduces downtime for the end user.
### Modes of Interprocess Communication
Tendermint has two primary mechanisms to communicate with other processes: RPC and P2P. The division marks the boundary between the internal and external components of the network:
- The P2P layer is used in all cases that nodes (of any type) need to communicate with one another.
- The RPC interface is for any outside process that wants to communicate with a node.
The design principle here is that **communication via RPC is to a trusted source** and thus the RPC service prioritizes inspection rather than verification. The P2P interface is the primary medium for verification.
As an example, an in-browser light client would verify headers (and perhaps application state) via the p2p layer, and then pass along information on to the client via RPC (or potentially directly via a separate API).
The main exceptions to this are the IBC module and relayers, which are external to the node but also require verifiable data. Breaking changes to the light client verification path mean that all neighbouring chains that are connected will no longer be able to verify state transitions and thus pass messages back and forward.
## Proposal
Tendermint version labels will follow the syntax of [Semantic Versions 2.0.0](https://semver.org/) with a major, minor and patch version. The version components will be interpreted according to these rules:
For the entire cycle of a **major version** in Tendermint:
- All blocks and state data in a blockchain can be queried. All headers can be verified even across minor version changes. Nodes can both block sync and state sync from genesis to the head of the chain.
- Nodes in a network are able to communicate and perform BFT state machine replication so long as the agreed network version is the lowest of all nodes in a network. For example, nodes using version 1.5.x and 1.2.x can operate together so long as the network version is 1.2 or lower (but still within the 1.x range). This rule essentially captures the concept of network backwards compatibility.
- Node RPC endpoints will remain compatible with existing external clients:
- New endpoints may be added, but old endpoints may not be removed.
- Old endpoints may be extended to add new request and response fields, but requests not using those fields must function as before the change.
- Migrations should be automatic. Upgrading of one node can happen asynchronously with respect to other nodes (although agreement of a network-wide upgrade must still occur synchronously via consensus).
For the entire cycle of a **minor version** in Tendermint:
- Public Go API's, for example in `node` or `abci` packages will not change in a way that requires any consumer (not just application developers) to modify their code.
- No breaking changes to the block protocol. This means that all block related data structures should not change in a way that breaks any of the hashes, the consensus engine or light client verification.
- Upgrades between minor versions may not result in any downtime (i.e., no migrations are required), nor require any changes to the config files to continue with the existing behavior. A minor version upgrade will require only stopping the existing process, swapping the binary, and starting the new process.
A new **patch version** of Tendermint will only contain bug fixes and updates that impact the security and stability of Tendermint.
These guarantees will come into effect at release 1.0.
## Status
Proposed
## Consequences
### Positive
- Clearer communication of what versioning means to us and the effect they have on our users.
### Negative
- Can potentially incur greater engineering effort to uphold and follow these guarantees.
### Neutral
## References
- [SemVer](https://semver.org/)
- [Tendermint Tracking Issue](https://github.com/tendermint/tendermint/issues/5680)

+ 3
- 3
docs/roadmap/roadmap.md View File

@ -8,7 +8,7 @@ order: 1
This document endeavours to inform the wider Tendermint community about development plans and priorities for Tendermint Core, and when we expect features to be delivered. It is intended to broadly inform all users of Tendermint, including application developers, node operators, integrators, and the engineering and research teams.
Anyone wishing to propose work to be a part of this roadmap should do so by opening an [issue](https://github.com/tendermint/spec/issues/new/choose) in the spec. Bug reports and other implementation concerns should be brought up in the [core repository](https://github.com/tendermint/tendermint).
Anyone wishing to propose work to be a part of this roadmap should do so by opening an [issue](https://github.com/tendermint/tendermint/issues/new/choose). Bug reports and other implementation concerns should be brought up in the [core repository](https://github.com/tendermint/tendermint).
This roadmap should be read as a high-level guide to plans and priorities, rather than a commitment to schedules and deliverables. Features earlier on the roadmap will generally be more specific and detailed than those later on. We will update this document periodically to reflect the current status.
@ -43,11 +43,11 @@ Added a new `EventSink` interface to allow alternatives to Tendermint's propriet
### ABCI++
An overhaul of the existing interface between the application and consensus, to give the application more control over block construction. ABCI++ adds new hooks allowing modification of transactions before they get into a block, verification of a block before voting, and complete delivery of blocks after agreement (to allow for concurrent execution). It enables both immediate and delayed agreement. [More](https://github.com/tendermint/spec/blob/master/spec/abci++/README.md)
An overhaul of the existing interface between the application and consensus, to give the application more control over block construction. ABCI++ adds new hooks allowing modification of transactions before they get into a block, verification of a block before voting, and complete delivery of blocks after agreement (to allow for concurrent execution). It enables both immediate and delayed agreement. [More](https://github.com/tendermint/tendermint/blob/master/spec/abci++/README.md)
### Proposer-Based Timestamps
Proposer-based timestamps are a replacement of [BFT time](https://docs.tendermint.com/master/spec/consensus/bft-time.html), whereby the proposer chooses a timestamp and validators vote on the block only if the timestamp is considered *timely*. This increases reliance on an accurate local clock, but in exchange makes block time more reliable and resistant to faults. This has important use cases in light clients, IBC relayers, CosmosHub inflation and enabling signature aggregation. [More](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-071-proposer-based-timestamps.md)
Proposer-based timestamps are a replacement of [BFT time](https://github.com/tendermint/tendermint/blob/master/spec/consensus/bft-time.md), whereby the proposer chooses a timestamp and validators vote on the block only if the timestamp is considered *timely*. This increases reliance on an accurate local clock, but in exchange makes block time more reliable and resistant to faults. This has important use cases in light clients, IBC relayers, CosmosHub inflation and enabling signature aggregation. [More](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-071-proposer-based-timestamps.md)
### RPC Event Subscription


+ 1
- 1
docs/tendermint-core/block-structure.md View File

@ -11,6 +11,6 @@ nodes. This blockchain is accessible via various RPC endpoints, mainly
`/blockchain?minHeight=_&maxHeight=_` to get a list of headers. But what
exactly is stored in these blocks?
The [specification](https://github.com/tendermint/spec/blob/8dd2ed4c6fe12459edeb9b783bdaaaeb590ec15c/spec/core/data_structures.md) contains a detailed description of each component - that's the best place to get started.
The [specification](https://github.com/tendermint/tendermint/tree/master/spec/core/data_structures.md) contains a detailed description of each component - that's the best place to get started.
To dig deeper, check out the [types package documentation](https://godoc.org/github.com/tendermint/tendermint/types).

+ 1
- 1
docs/tendermint-core/consensus/README.md View File

@ -23,7 +23,7 @@ explained in a forthcoming document.
For efficiency reasons, validators in Tendermint consensus protocol do not agree directly on the
block as the block size is big, i.e., they don't embed the block inside `Proposal` and
`VoteMessage`. Instead, they reach agreement on the `BlockID` (see `BlockID` definition in
[Blockchain](https://github.com/tendermint/spec/blob/master/spec/core/data_structures.md#blockid) section)
[Blockchain](https://github.com/tendermint/tendermint/blob/master/spec/core/data_structures.md#blockid) section)
that uniquely identifies each block. The block itself is
disseminated to validator processes using peer-to-peer gossiping protocol. It starts by having a
proposer first splitting a block into a number of block parts, that are then gossiped between


+ 2
- 2
docs/tendermint-core/light-client.md View File

@ -17,7 +17,7 @@ The light client protocol verifies headers by retrieving a chain of headers,
commits and validator sets from a trusted height to the target height, verifying
the signatures of each of these intermediary signed headers till it reaches the
target height. From there, all the application state is verifiable with
[merkle proofs](https://github.com/tendermint/spec/blob/953523c3cb99fdb8c8f7a2d21e3a99094279e9de/spec/blockchain/encoding.md#iavl-tree).
[merkle proofs](https://github.com/tendermint/tendermint/blob/953523c3cb99fdb8c8f7a2d21e3a99094279e9de/spec/blockchain/encoding.md#iavl-tree).
## Properties
@ -38,7 +38,7 @@ a provider and a set of witnesses. This sets the trust period: the period that
full nodes should be accountable for faulty behavior and a trust level: the
fraction of validators in a validator set with which we trust that at least one
is correct. As Tendermint consensus can withstand 1/3 byzantine faults, this is
the default trust level, however, for greater security you can increase it (max:
the default trust level, however, for greater security you can increase it (max:
1).
Similar to a full node, light clients can also be subject to byzantine attacks.


+ 2
- 2
docs/tendermint-core/rpc.md View File

@ -8,13 +8,13 @@ The RPC documentation is hosted here:
- [https://docs.tendermint.com/master/rpc/](https://docs.tendermint.com/master/rpc/)
To update the documentation, edit the relevant `godoc` comments in the [rpc/core directory](https://github.com/tendermint/tendermint/tree/master/rpc/core).
To update the documentation, edit the relevant `godoc` comments in the [rpc directory](https://github.com/tendermint/tendermint/tree/master/rpc).
If you are using Tendermint in-process, you will need to set the version to be displayed in the RPC.
If you are using a makefile with your go project, this can be done by using sed and `ldflags`.
Example:
Example:
```
VERSION := $(shell go list -m github.com/tendermint/tendermint | sed 's:.* ::')


+ 1
- 1
docs/tendermint-core/subscription.md View File

@ -43,7 +43,7 @@ transactions](../app-dev/indexing-transactions.md) for details.
When validator set changes, ValidatorSetUpdates event is published. The
event carries a list of pubkey/power pairs. The list is the same
Tendermint receives from ABCI application (see [EndBlock
section](https://github.com/tendermint/spec/blob/master/spec/abci/abci.md#endblock) in
section](https://github.com/tendermint/tendermint/blob/master/spec/abci/abci.md#endblock) in
the ABCI spec).
Response:


+ 1
- 1
docs/tendermint-core/using-tendermint.md View File

@ -49,7 +49,7 @@ definition](https://github.com/tendermint/tendermint/blob/master/types/genesis.g
chain IDs, you will have a bad time. The ChainID must be less than 50 symbols.
- `initial_height`: Height at which Tendermint should begin. If a blockchain is conducting a network upgrade,
starting from the stopped height brings uniqueness to previous heights.
- `consensus_params` [spec](https://github.com/tendermint/spec/blob/master/spec/core/state.md#consensusparams)
- `consensus_params` [spec](https://github.com/tendermint/tendermint/blob/master/spec/core/state.md#consensusparams)
- `block`
- `max_bytes`: Max block size, in bytes.
- `max_gas`: Max gas per block.


+ 2
- 2
docs/tutorials/go-built-in.md View File

@ -212,7 +212,7 @@ etc.) by Tendermint Core.
Valid transactions will eventually be committed given they are not too big and
have enough gas. To learn more about gas, check out ["the
specification"](https://docs.tendermint.com/master/spec/abci/apps.html#gas).
specification"](https://github.com/tendermint/tendermint/blob/master/spec/abci/apps.md#gas).
For the underlying key-value store we'll use
[badger](https://github.com/dgraph-io/badger), which is an embeddable,
@ -331,7 +331,7 @@ func (app *KVStoreApplication) Query(reqQuery abcitypes.RequestQuery) (resQuery
```
The complete specification can be found
[here](https://docs.tendermint.com/master/spec/abci/).
[here](https://github.com/tendermint/tendermint/tree/master/spec/abci/).
## 1.4 Starting an application and a Tendermint Core instance in the same process


+ 2
- 4
docs/tutorials/go.md View File

@ -210,7 +210,7 @@ etc.) by Tendermint Core.
Valid transactions will eventually be committed given they are not too big and
have enough gas. To learn more about gas, check out ["the
specification"](https://docs.tendermint.com/master/spec/abci/apps.html#gas).
specification"](https://github.com/tendermint/tendermint/blob/master/spec/abci/apps.md#gas).
For the underlying key-value store we'll use
[badger](https://github.com/dgraph-io/badger), which is an embeddable,
@ -328,7 +328,7 @@ func (app *KVStoreApplication) Query(reqQuery abcitypes.RequestQuery) (resQuery
```
The complete specification can be found
[here](https://docs.tendermint.com/master/spec/abci/).
[here](https://github.com/tendermint/tendermint/tree/master/spec/abci/).
## 1.4 Starting an application and a Tendermint Core instances
@ -384,7 +384,6 @@ func main() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
<-c
os.Exit(0)
}
```
@ -425,7 +424,6 @@ defer server.Stop()
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
<-c
os.Exit(0)
```
## 1.5 Getting Up and Running


+ 0
- 1
docs/versions View File

@ -1,4 +1,3 @@
master master
v0.33.x v0.33
v0.34.x v0.34
v0.35.x v0.35

+ 14
- 12
go.mod View File

@ -11,10 +11,10 @@ require (
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.0
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.4.2
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
@ -55,10 +55,10 @@ require (
github.com/ashanbrown/makezero v1.1.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bkielbasa/cyclop v1.2.0 // indirect
github.com/blizzy78/varnamelen v0.5.0 // indirect
github.com/blizzy78/varnamelen v0.6.0 // indirect
github.com/bombsimon/wsl/v3 v3.3.0 // indirect
github.com/breml/bidichk v0.2.1 // indirect
github.com/breml/errchkjson v0.2.1 // indirect
github.com/breml/bidichk v0.2.2 // indirect
github.com/breml/errchkjson v0.2.3 // indirect
github.com/butuzov/ireturn v0.1.1 // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/cespare/xxhash v1.1.0 // indirect
@ -66,7 +66,8 @@ 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.2.9 // indirect
github.com/daixiang0/gci v0.3.1-0.20220208004058-76d765e3ab48 // indirect
github.com/creachadair/atomicfile v0.2.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/denis-tingajkin/go-header v0.4.2 // indirect
github.com/dgraph-io/badger/v2 v2.2007.2 // indirect
@ -106,7 +107,7 @@ require (
github.com/golangci/revgrep v0.0.0-20210930125155-c22e5001d4f2 // indirect
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect
github.com/google/btree v1.0.0 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/go-cmp v0.5.7 // indirect
github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8 // indirect
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
github.com/gostaticanalysis/comment v1.4.2 // indirect
@ -115,6 +116,7 @@ require (
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // 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
github.com/jgautheron/goconst v1.5.1 // indirect
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
@ -123,11 +125,11 @@ require (
github.com/julz/importas v0.1.0 // indirect
github.com/kisielk/errcheck v1.6.0 // indirect
github.com/kisielk/gotool v1.0.0 // indirect
github.com/kulti/thelper v0.5.0 // indirect
github.com/kulti/thelper v0.5.1 // indirect
github.com/kunwardeep/paralleltest v1.0.3 // indirect
github.com/kyoh86/exportloopref v0.1.8 // indirect
github.com/ldez/gomoddirectives v0.2.2 // indirect
github.com/ldez/tagliatelle v0.3.0 // 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/maratori/testpackage v1.0.1 // indirect
@ -138,7 +140,7 @@ require (
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.3 // 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
github.com/moricho/tparallel v0.2.1 // indirect
@ -187,7 +189,7 @@ require (
github.com/tomarrell/wrapcheck/v2 v2.4.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.4 // indirect
github.com/ultraware/whitespace v0.0.5 // indirect
github.com/uudashr/gocognit v1.0.5 // indirect
github.com/yagipy/maintidx v1.0.0 // indirect
github.com/yeya24/promlinter v0.1.1-0.20210918184747-d757024714a1 // indirect
@ -197,7 +199,7 @@ require (
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.9-0.20211228192929-ee1ca4ffc4da // 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


+ 31
- 26
go.sum View File

@ -140,14 +140,14 @@ 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.5.0 h1:v9LpMwxzTqAJC4lsD/jR7zWb8a66trcqhTEH4Mk6Fio=
github.com/blizzy78/varnamelen v0.5.0/go.mod h1:Mc0nLBKI1/FP0Ga4kqMOgBig0eS5QtR107JnMAb1Wuc=
github.com/blizzy78/varnamelen v0.6.0 h1:TOIDk9qRIMspALZKX8x+5hQfAjuvAFogppnxtvuNmBo=
github.com/blizzy78/varnamelen v0.6.0/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.1 h1:SRNtZuLdfkxtocj+xyHXKC1Uv3jVi6EPYx+NHSTNQvE=
github.com/breml/bidichk v0.2.1/go.mod h1:zbfeitpevDUGI7V91Uzzuwrn4Vls8MoBMrwtt78jmso=
github.com/breml/errchkjson v0.2.1 h1:QCToXnY9BNngrbJoW3qfCTt3BdtbnsI6wyP/WGrxxSE=
github.com/breml/errchkjson v0.2.1/go.mod h1:jZEATw/jF69cL1iy7//Yih8yp/mXp2CBoBr9GJwCAsY=
github.com/breml/bidichk v0.2.2 h1:w7QXnpH0eCBJm55zGCTJveZEkQBt6Fs5zThIdA6qQ9Y=
github.com/breml/bidichk v0.2.2/go.mod h1:zbfeitpevDUGI7V91Uzzuwrn4Vls8MoBMrwtt78jmso=
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=
@ -217,11 +217,13 @@ 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/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.2.9 h1:iwJvwQpBZmMg31w+QQ6jsyZ54KEATn6/nfARbBNW294=
github.com/daixiang0/gci v0.2.9/go.mod h1:+4dZ7TISfSmqfAGv59ePaHfNzgGtIkHAhhdKggP1JAc=
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/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=
@ -398,8 +400,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.0 h1:YJPouGNQEdK+x2KsCpWMIBy0q6MSuxHjkWMxJMNj/DU=
github.com/golangci/golangci-lint v1.44.0/go.mod h1:aBolpzNkmYogKPynGKdOWDCEc8LlwnxZC6w/SJ1TaEs=
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/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=
@ -426,8 +428,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-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/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=
@ -477,8 +480,8 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/gostaticanalysis/analysisutil v0.1.0/go.mod h1:dMhHRU9KTiDcuLGdy87/2gTR8WruwYZrKdRq9m1O6uw=
@ -549,6 +552,8 @@ 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/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=
github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
@ -622,8 +627,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kulti/thelper v0.5.0 h1:CiEKStgoG4K9bjf/zk3eNX0D0J2iFWzxEY+h9UXmlJg=
github.com/kulti/thelper v0.5.0/go.mod h1:vMu2Cizjy/grP+jmsvOFDx1kYP6+PD1lqg4Yu5exl2U=
github.com/kulti/thelper v0.5.1 h1:Uf4CUekH0OvzQTFPrWkstJvXgm6pnNEtQu3HiqEkpB0=
github.com/kulti/thelper v0.5.1/go.mod h1:vMu2Cizjy/grP+jmsvOFDx1kYP6+PD1lqg4Yu5exl2U=
github.com/kunwardeep/paralleltest v1.0.3 h1:UdKIkImEAXjR1chUWLn+PNXqWUGs//7tzMeWuP7NhmI=
github.com/kunwardeep/paralleltest v1.0.3/go.mod h1:vLydzomDFpk7yu5UX02RmP0H8QfRPOV/oFhWN85Mjb4=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
@ -631,8 +636,8 @@ github.com/kyoh86/exportloopref v0.1.8 h1:5Ry/at+eFdkX9Vsdw3qU4YkvGtzuVfzT4X7S77
github.com/kyoh86/exportloopref v0.1.8/go.mod h1:1tUcJeiioIs7VWe5gcOObrux3lb66+sBqGZrRkMwPgg=
github.com/ldez/gomoddirectives v0.2.2 h1:p9/sXuNFArS2RLc+UpYZSI4KQwGMEDWC/LbtF5OPFVg=
github.com/ldez/gomoddirectives v0.2.2/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0=
github.com/ldez/tagliatelle v0.3.0 h1:Aubm2ZsrsjIGFvdxemMPJaXrSJ5Cys6VWyTQFt9k2dI=
github.com/ldez/tagliatelle v0.3.0/go.mod h1:8s6WJQwEYHbKZDsp/LjArytKOG8qaMrKQQ3mFukHs88=
github.com/ldez/tagliatelle v0.3.1 h1:3BqVVlReVUZwafJUwQ+oxbx2BEX2vUG4Yu/NOfMiKiM=
github.com/ldez/tagliatelle v0.3.1/go.mod h1:8s6WJQwEYHbKZDsp/LjArytKOG8qaMrKQQ3mFukHs88=
github.com/leonklingele/grouper v1.1.0 h1:tC2y/ygPbMFSBOs3DcyaEMKnnwH7eYKzohOtRrf0SAg=
github.com/leonklingele/grouper v1.1.0/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY=
github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag=
@ -684,8 +689,8 @@ github.com/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwg
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.3 h1:6tBZacs2/uv9UOpkBQhCtXh2NGgu2Ry97ZyjcN6uDCM=
github.com/mgechev/revive v1.1.3/go.mod h1:jMzDa13teAuv/KLeqgJw79NDe+1IT0ZO3Mht0vN1Yls=
github.com/mgechev/revive v1.1.4 h1:sZOjY6GU35Kr9jKa/wsKSHgrFz8eASIB5i3tqWZMp0A=
github.com/mgechev/revive v1.1.4/go.mod h1:ZZq2bmyssGh8MSPz3VVziqRNIMYTJXzP8MUKG90vZ9A=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
@ -857,7 +862,7 @@ github.com/quasilyte/go-ruleguard v0.3.15/go.mod h1:NhuWhnlVEM1gT1A4VJHYfy9MuYSx
github.com/quasilyte/go-ruleguard/dsl v0.3.0/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
github.com/quasilyte/go-ruleguard/dsl v0.3.12-0.20220101150716-969a394a9451/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
github.com/quasilyte/go-ruleguard/dsl v0.3.12/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
github.com/quasilyte/go-ruleguard/dsl v0.3.15/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
github.com/quasilyte/go-ruleguard/dsl v0.3.17/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
github.com/quasilyte/go-ruleguard/rules v0.0.0-20201231183845-9e62ed36efe1/go.mod h1:7JTjp89EGyU1d6XfBiXihJNG37wB2VRkd125Q1u7Plc=
github.com/quasilyte/go-ruleguard/rules v0.0.0-20211022131956-028d6511ab71/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50=
github.com/quasilyte/gogrep v0.0.0-20220103110004-ffaa07af02e3 h1:P4QPNn+TK49zJjXKERt/vyPbv/mCHB/zQ4flDYOMN+M=
@ -897,7 +902,7 @@ github.com/securego/gosec/v2 v2.9.6/go.mod h1:EESY9Ywxo/Zc5NyF/qIj6Cop+4PSWM0F0O
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.21.12/go.mod h1:BToYZVTlSVlfazpDDYFnsVZLaoRG+g8ufT6fPQLdJzA=
github.com/shirou/gopsutil/v3 v3.22.1/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=
@ -1004,8 +1009,8 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ultraware/funlen v0.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iLA=
github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
github.com/ultraware/whitespace v0.0.4 h1:If7Va4cM03mpgrNH9k49/VOicWpGoG70XPBFFODYDsg=
github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
github.com/ultraware/whitespace v0.0.5 h1:hh+/cpIcopyMYbZNVov9iSxvJU3OYQg78Sfaqzi/CzI=
github.com/ultraware/whitespace v0.0.5/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/uudashr/gocognit v1.0.5 h1:rrSex7oHr3/pPLQ0xoWq108XMU8s678FJcQ+aSfOHa4=
@ -1332,7 +1337,6 @@ golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -1340,6 +1344,7 @@ golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
@ -1443,7 +1448,6 @@ golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20201028025901-8cd080b735b3/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201114224030-61ea331ec02b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201118003311-bd56c0adb394/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
@ -1461,8 +1465,9 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.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 h1:Tno72dYE94v/7SyyIj9iBsc7OOjFu2PyNcl7yxxeZD8=
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/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=


+ 14
- 21
internal/blocksync/pool.go View File

@ -235,9 +235,7 @@ func (pool *BlockPool) PopRequest() {
defer pool.mtx.Unlock()
if r := pool.requesters[pool.height]; r != nil {
if err := r.Stop(); err != nil {
pool.logger.Error("error stopping requester", "err", err)
}
r.Stop()
delete(pool.requesters, pool.height)
pool.height++
pool.lastAdvance = time.Now()
@ -333,8 +331,16 @@ func (pool *BlockPool) SetPeerRange(peerID types.NodeID, base int64, height int6
peer.base = base
peer.height = height
} else {
peer = newBPPeer(pool, peerID, base, height)
peer.logger = pool.logger.With("peer", peerID)
peer = &bpPeer{
pool: pool,
id: peerID,
base: base,
height: height,
numPending: 0,
logger: pool.logger.With("peer", peerID),
startAt: time.Now(),
}
pool.peers[peerID] = peer
}
@ -492,24 +498,13 @@ type bpPeer struct {
recvMonitor *flowrate.Monitor
timeout *time.Timer
startAt time.Time
logger log.Logger
}
func newBPPeer(pool *BlockPool, peerID types.NodeID, base int64, height int64) *bpPeer {
peer := &bpPeer{
pool: pool,
id: peerID,
base: base,
height: height,
numPending: 0,
logger: log.NewNopLogger(),
}
return peer
}
func (peer *bpPeer) resetMonitor() {
peer.recvMonitor = flowrate.New(time.Second, time.Second*40)
peer.recvMonitor = flowrate.New(peer.startAt, time.Second, time.Second*40)
initialValue := float64(minRecvRate) * math.E
peer.recvMonitor.SetREMA(initialValue)
}
@ -676,9 +671,7 @@ OUTER_LOOP:
case <-ctx.Done():
return
case <-bpr.pool.exitedCh:
if err := bpr.Stop(); err != nil {
bpr.logger.Error("error stopped requester", "err", err)
}
bpr.Stop()
return
case peerID := <-bpr.redoCh:
if peerID == bpr.peerID {


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save