Browse Source

spec: merge spec repo into tendermint repo (#7804)

pull/7839/head
Callum Waters 2 years ago
committed by GitHub
parent
commit
94b409e407
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
229 changed files with 46681 additions and 37 deletions
  1. +37
    -0
      .github/ISSUE_TEMPLATE/proposal.md
  2. +1
    -1
      .github/workflows/lint.yml
  3. +1
    -1
      .github/workflows/linter.yml
  4. +18
    -0
      .github/workflows/markdown-links.yml
  5. +24
    -0
      .github/workflows/proto-check.yml
  6. +64
    -0
      .github/workflows/proto-dockerfile.yml
  7. +8
    -7
      .gitignore
  8. +11
    -0
      .markdownlint.yml
  9. +1
    -1
      CHANGELOG_PENDING.md
  10. +24
    -3
      Makefile
  11. +14
    -0
      buf.gen.yaml
  12. +16
    -0
      buf.yaml
  13. +109
    -0
      docs/architecture/adr-077-block-retention.md
  14. +82
    -0
      docs/architecture/adr-078-nonzero-genesis.md
  15. +57
    -0
      docs/architecture/adr-079-ed25519-verification.md
  16. +203
    -0
      docs/architecture/adr-080-reverse-sync.md
  17. BIN
      docs/architecture/img/block-retention.png
  18. +1
    -1
      docs/pre.sh
  19. +2
    -0
      docs/rfc/README.md
  20. BIN
      docs/rfc/images/abci++.png
  21. BIN
      docs/rfc/images/abci.png
  22. +257
    -0
      docs/rfc/rfc-011-abci++.md
  23. +98
    -0
      docs/rfc/rfc-012-semantic-versioning.md
  24. +253
    -0
      docs/rfc/rfc-013-abci++.md
  25. +94
    -0
      docs/rfc/rfc-014-semantic-versioning.md
  26. +2
    -2
      docs/roadmap/roadmap.md
  27. +1
    -1
      docs/tendermint-core/block-structure.md
  28. +1
    -1
      docs/tendermint-core/consensus/README.md
  29. +1
    -1
      docs/tendermint-core/subscription.md
  30. +0
    -3
      internal/consensus/README.md
  31. +1
    -1
      internal/evidence/doc.go
  32. +1
    -1
      light/client.go
  33. +2
    -2
      light/doc.go
  34. +20
    -0
      proto/Dockerfile
  35. +21
    -0
      proto/README.md
  36. +468
    -0
      proto/tendermint/abci/types.proto
  37. +39
    -0
      proto/tendermint/blocksync/types.proto
  38. +97
    -0
      proto/tendermint/consensus/types.proto
  39. +0
    -2
      proto/tendermint/consensus/wal.proto
  40. +16
    -0
      proto/tendermint/crypto/keys.proto
  41. +39
    -0
      proto/tendermint/crypto/proof.proto
  42. +7
    -0
      proto/tendermint/libs/bits/types.proto
  43. +12
    -0
      proto/tendermint/mempool/types.proto
  44. +28
    -0
      proto/tendermint/p2p/conn.proto
  45. +24
    -0
      proto/tendermint/p2p/pex.proto
  46. +42
    -0
      proto/tendermint/p2p/types.proto
  47. +1
    -1
      proto/tendermint/privval/service.proto
  48. +61
    -0
      proto/tendermint/statesync/types.proto
  49. +13
    -0
      proto/tendermint/types/block.proto
  50. +0
    -3
      proto/tendermint/types/canonical.proto
  51. +0
    -2
      proto/tendermint/types/events.proto
  52. +40
    -0
      proto/tendermint/types/evidence.proto
  53. +127
    -0
      proto/tendermint/types/params.proto
  54. +171
    -0
      proto/tendermint/types/types.proto
  55. +23
    -0
      proto/tendermint/types/validator.proto
  56. +14
    -0
      proto/tendermint/version/types.proto
  57. +99
    -0
      spec/README.md
  58. +45
    -0
      spec/abci++/README.md
  59. +158
    -0
      spec/abci++/abci++_app_requirements_002_draft.md
  60. +401
    -0
      spec/abci++/abci++_basic_concepts_002_draft.md
  61. +878
    -0
      spec/abci++/abci++_methods_002_draft.md
  62. +211
    -0
      spec/abci++/abci++_tmint_expected_behavior_002_draft.md
  63. +156
    -0
      spec/abci++/v0.md
  64. +162
    -0
      spec/abci++/v1.md
  65. +180
    -0
      spec/abci++/v2.md
  66. +201
    -0
      spec/abci++/v3.md
  67. +199
    -0
      spec/abci++/v4.md
  68. +27
    -0
      spec/abci/README.md
  69. +777
    -0
      spec/abci/abci.md
  70. +672
    -0
      spec/abci/apps.md
  71. +113
    -0
      spec/abci/client-server.md
  72. +55
    -0
      spec/consensus/bft-time.md
  73. +2417
    -0
      spec/consensus/consensus-paper/IEEEtran.bst
  74. +4733
    -0
      spec/consensus/consensus-paper/IEEEtran.cls
  75. +24
    -0
      spec/consensus/consensus-paper/README.md
  76. +195
    -0
      spec/consensus/consensus-paper/algorithmicplus.sty
  77. +16
    -0
      spec/consensus/consensus-paper/conclusion.tex
  78. +397
    -0
      spec/consensus/consensus-paper/consensus.tex
  79. +126
    -0
      spec/consensus/consensus-paper/definitions.tex
  80. +32
    -0
      spec/consensus/consensus-paper/homodel.sty
  81. +138
    -0
      spec/consensus/consensus-paper/intro.tex
  82. +1124
    -0
      spec/consensus/consensus-paper/latex8.bst
  83. +168
    -0
      spec/consensus/consensus-paper/latex8.sty
  84. +1659
    -0
      spec/consensus/consensus-paper/lit.bib
  85. +153
    -0
      spec/consensus/consensus-paper/paper.tex
  86. +280
    -0
      spec/consensus/consensus-paper/proof.tex
  87. +62
    -0
      spec/consensus/consensus-paper/rounddiag.sty
  88. +118
    -0
      spec/consensus/consensus-paper/technote.sty
  89. +352
    -0
      spec/consensus/consensus.md
  90. +43
    -0
      spec/consensus/creating-proposal.md
  91. +199
    -0
      spec/consensus/evidence.md
  92. +9
    -0
      spec/consensus/light-client/README.md
  93. +3
    -0
      spec/consensus/light-client/accountability.md
  94. BIN
      spec/consensus/light-client/assets/light-node-image.png
  95. +3
    -0
      spec/consensus/light-client/detection.md
  96. +3
    -0
      spec/consensus/light-client/verification.md
  97. +157
    -0
      spec/consensus/proposer-based-timestamp/README.md
  98. +148
    -0
      spec/consensus/proposer-based-timestamp/pbts-algorithm_002_draft.md
  99. +258
    -0
      spec/consensus/proposer-based-timestamp/pbts-sysmodel_002_draft.md
  100. +597
    -0
      spec/consensus/proposer-based-timestamp/tla/TendermintPBT_001_draft.tla

+ 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/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:


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

@ -0,0 +1,18 @@
# Currently disabled until all links have been fixed
# 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.12.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

+ 1
- 1
CHANGELOG_PENDING.md View File

@ -20,7 +20,7 @@ Special thanks to external contributors on this release:
- 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).
- P2P Protocol


+ 24
- 3
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 ###
###############################################################################
@ -309,3 +329,4 @@ split-test-packages:$(BUILDDIR)/packages.txt
split -d -n l/$(NUM_SPLIT) $< $<.
test-group-%:split-test-packages
cat $(BUILDDIR)/packages.txt.$* | xargs go test -mod=readonly -timeout=5m -race -coverprofile=$(BUILDDIR)/$*.profile.out

+ 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

+ 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/spec/blob/master/rfc/001-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/spec/blob/master/rfc/001-block-retention.md)
- [Original issue](https://github.com/tendermint/tendermint/issues/4629)

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

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

+ 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 .

+ 2
- 0
docs/rfc/README.md View File

@ -49,5 +49,7 @@ sections.
- [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-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

+ 257
- 0
docs/rfc/rfc-011-abci++.md View File

@ -0,0 +1,257 @@
<<<<<<< HEAD:docs/rfc/rfc-011-abci++.md
# RFC 011: ABCI++
=======
# RFC 013: ABCI++
>>>>>>> a895a8ea5f (Rename and renumber imported RFCs.):docs/rfc/rfc-013-abci++.md
## 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 |

+ 98
- 0
docs/rfc/rfc-012-semantic-versioning.md View File

@ -0,0 +1,98 @@
<<<<<<< HEAD:docs/rfc/rfc-012-semantic-versioning.md
# RFC 012: Semantic Versioning
=======
# RFC 014: Semantic Versioning
>>>>>>> a895a8ea5f (Rename and renumber imported RFCs.):docs/rfc/rfc-014-semantic-versioning.md
## 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)

+ 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)

+ 2
- 2
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,7 +43,7 @@ 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


+ 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


+ 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:


+ 0
- 3
internal/consensus/README.md View File

@ -1,3 +0,0 @@
# Consensus
See the [consensus spec](https://github.com/tendermint/spec/tree/master/spec/consensus).

+ 1
- 1
internal/evidence/doc.go View File

@ -1,7 +1,7 @@
/*
Package evidence handles all evidence storage and gossiping from detection to block proposal.
For the different types of evidence refer to the `evidence.go` file in the types package
or https://github.com/tendermint/spec/blob/master/spec/consensus/light-client/accountability.md.
or https://github.com/tendermint/tendermint/blob/master/spec/consensus/light-client/accountability.md.
Gossiping


+ 1
- 1
light/client.go View File

@ -444,7 +444,7 @@ func (c *Client) VerifyLightBlockAtHeight(ctx context.Context, height int64, now
// headers are not adjacent, verifySkipping is performed and necessary (not all)
// intermediate headers will be requested. See the specification for details.
// Intermediate headers are not saved to database.
// https://github.com/tendermint/spec/blob/master/spec/consensus/light-client.md
// https://github.com/tendermint/tendermint/blob/master/spec/light-client/README.md
//
// If the header, which is older than the currently trusted header, is
// requested and the light client does not have it, VerifyHeader will perform:


+ 2
- 2
light/doc.go View File

@ -94,7 +94,7 @@ Check out other examples in example_test.go
## 2. Pure functions to verify a new header (see verifier.go)
Verify function verifies a new header against some trusted header. See
https://github.com/tendermint/spec/blob/master/spec/light-client/verification/README.md
https://github.com/tendermint/tendermint/blob/master/spec/light-client/verification/README.md
for details.
There are two methods of verification: sequential and bisection
@ -118,7 +118,7 @@ as a wrapper, which verifies all the headers, using a light client connected to
some other node.
See
https://github.com/tendermint/spec/tree/master/spec/light-client
https://github.com/tendermint/tendermint/tree/master/spec/light-client
for the light client specification.
*/
package light

+ 20
- 0
proto/Dockerfile View File

@ -0,0 +1,20 @@
# This Dockerfile defines an image containing tools for linting, formatting,
# and compiling the Tendermint protos.
FROM golang:1.17-alpine
# Install a commonly used set of programs for use with our protos.
# clang-extra-tools is included here because it provides clang-format,
# used to format the .proto files.
RUN apk add --no-cache build-base clang-extra-tools curl git
ENV GOLANG_PROTOBUF_VERSION=1.3.1 \
GOGO_PROTOBUF_VERSION=1.3.2
# Retrieve the go protoc programs and copy them into the PATH
RUN go install github.com/golang/protobuf/protoc-gen-go@v${GOLANG_PROTOBUF_VERSION} && \
go install github.com/gogo/protobuf/protoc-gen-gogo@v${GOGO_PROTOBUF_VERSION} && \
go install github.com/gogo/protobuf/protoc-gen-gogofaster@v${GOGO_PROTOBUF_VERSION} && \
mv "$(go env GOPATH)"/bin/* /usr/local/bin/
# Copy the 'buf' program out of the buildbuf/buf container.
COPY --from=bufbuild/buf:latest /usr/local/bin/* /usr/local/bin/

+ 21
- 0
proto/README.md View File

@ -0,0 +1,21 @@
# Protocol Buffers
This sections defines the protocol buffers used in Tendermint. This is split into two directories: `spec`, the types required for all implementations and `tendermint`, a set of types internal to the Go implementation. All generated go code is also stored in `tendermint`.
More descriptions of the data structures are located in the spec directory as follows:
- [Block](../spec/core/data_structures.md)
- [ABCI](../spec/abci/README.md)
- [P2P](../spec/p2p/messages/README.md)
## Process to generate protos
The `.proto` files within this section are core to the protocol and updates must be treated as such.
### Steps
1. Make an issue with the proposed change.
- Within the issue members, from the Tendermint team will leave comments. If there is not consensus on the change an [RFC](../rfc/README.md) may be requested.
1a. Submission of an RFC as a pull request should be made to facilitate further discussion.
1b. Merge the RFC.
2. Make the necessary changes to the `.proto` file(s), [core data structures](../spec/core/data_structures.md) and/or [ABCI protocol](../spec/abci/apps.md).
3. Rebuild the Go protocol buffers by running `make proto-gen`. Ensure that the project builds correctly by running `make build`.

+ 468
- 0
proto/tendermint/abci/types.proto View File

@ -0,0 +1,468 @@
syntax = "proto3";
package tendermint.abci;
option go_package = "github.com/tendermint/tendermint/abci/types";
// For more information on gogo.proto, see:
// https://github.com/gogo/protobuf/blob/master/extensions.md
import "tendermint/crypto/proof.proto";
import "tendermint/types/types.proto";
import "tendermint/crypto/keys.proto";
import "tendermint/types/params.proto";
import "google/protobuf/timestamp.proto";
import "gogoproto/gogo.proto";
// This file is copied from http://github.com/tendermint/abci
// NOTE: When using custom types, mind the warnings.
// https://github.com/gogo/protobuf/blob/master/custom_types.md#warnings-and-issues
//----------------------------------------
// Request types
message Request {
oneof value {
RequestEcho echo = 1;
RequestFlush flush = 2;
RequestInfo info = 3;
RequestInitChain init_chain = 4;
RequestQuery query = 5;
RequestBeginBlock begin_block = 6 [deprecated = true];
RequestCheckTx check_tx = 7;
RequestDeliverTx deliver_tx = 8 [deprecated = true];
RequestEndBlock end_block = 9 [deprecated = true];
RequestCommit commit = 10;
RequestListSnapshots list_snapshots = 11;
RequestOfferSnapshot offer_snapshot = 12;
RequestLoadSnapshotChunk load_snapshot_chunk = 13;
RequestApplySnapshotChunk apply_snapshot_chunk = 14;
RequestPrepareProposal prepare_proposal = 15;
RequestProcessProposal process_proposal = 16;
RequestFinalizeBlock finalize_block = 19;
}
reserved 17; // Placeholder for RequestExtendVote in v0.37
reserved 18; // Placeholder for RequestVerifyVoteExtension in v0.37
}
message RequestEcho {
string message = 1;
}
message RequestFlush {}
message RequestInfo {
string version = 1;
uint64 block_version = 2;
uint64 p2p_version = 3;
string abci_version = 4;
}
message RequestInitChain {
google.protobuf.Timestamp time = 1 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
string chain_id = 2;
tendermint.types.ConsensusParams consensus_params = 3;
repeated ValidatorUpdate validators = 4 [(gogoproto.nullable) = false];
bytes app_state_bytes = 5;
int64 initial_height = 6;
}
message RequestQuery {
bytes data = 1;
string path = 2;
int64 height = 3;
bool prove = 4;
}
message RequestBeginBlock {
bytes hash = 1;
tendermint.types.Header header = 2 [(gogoproto.nullable) = false];
LastCommitInfo last_commit_info = 3 [(gogoproto.nullable) = false];
repeated Evidence byzantine_validators = 4 [(gogoproto.nullable) = false];
}
enum CheckTxType {
NEW = 0 [(gogoproto.enumvalue_customname) = "New"];
RECHECK = 1 [(gogoproto.enumvalue_customname) = "Recheck"];
}
message RequestCheckTx {
bytes tx = 1;
CheckTxType type = 2;
}
message RequestDeliverTx {
bytes tx = 1;
}
message RequestEndBlock {
int64 height = 1;
}
message RequestCommit {}
// lists available snapshots
message RequestListSnapshots {}
// offers a snapshot to the application
message RequestOfferSnapshot {
Snapshot snapshot = 1; // snapshot offered by peers
bytes app_hash = 2; // light client-verified app hash for snapshot height
}
// loads a snapshot chunk
message RequestLoadSnapshotChunk {
uint64 height = 1;
uint32 format = 2;
uint32 chunk = 3;
}
// Applies a snapshot chunk
message RequestApplySnapshotChunk {
uint32 index = 1;
bytes chunk = 2;
string sender = 3;
}
message RequestPrepareProposal {
bytes hash = 1;
tendermint.types.Header header = 2 [(gogoproto.nullable) = false];
// txs is an array of transactions that will be included in a block,
// sent to the app for possible modifications.
repeated bytes txs = 3;
LastCommitInfo last_commit_info = 4 [(gogoproto.nullable) = false];
repeated Evidence byzantine_validators = 5 [(gogoproto.nullable) = false];
// the modified transactions cannot exceed this size.
int64 max_tx_bytes = 6;
}
message RequestProcessProposal {
bytes hash = 1;
tendermint.types.Header header = 2 [(gogoproto.nullable) = false];
repeated bytes txs = 3;
LastCommitInfo last_commit_info = 4 [(gogoproto.nullable) = false];
repeated Evidence byzantine_validators = 5 [(gogoproto.nullable) = false];
}
message RequestFinalizeBlock {
bytes hash = 1;
tendermint.types.Header header = 2 [(gogoproto.nullable) = false];
repeated bytes txs = 3;
LastCommitInfo last_commit_info = 4 [(gogoproto.nullable) = false];
repeated Evidence byzantine_validators = 5 [(gogoproto.nullable) = false];
}
//----------------------------------------
// Response types
message Response {
oneof value {
ResponseException exception = 1;
ResponseEcho echo = 2;
ResponseFlush flush = 3;
ResponseInfo info = 4;
ResponseInitChain init_chain = 5;
ResponseQuery query = 6;
ResponseBeginBlock begin_block = 7 [deprecated = true];
ResponseCheckTx check_tx = 8;
ResponseDeliverTx deliver_tx = 9 [deprecated = true];
ResponseEndBlock end_block = 10 [deprecated = true];
ResponseCommit commit = 11;
ResponseListSnapshots list_snapshots = 12;
ResponseOfferSnapshot offer_snapshot = 13;
ResponseLoadSnapshotChunk load_snapshot_chunk = 14;
ResponseApplySnapshotChunk apply_snapshot_chunk = 15;
ResponsePrepareProposal prepare_proposal = 16;
ResponseProcessProposal process_proposal = 17;
ResponseFinalizeBlock finalize_block = 20;
}
reserved 18; // Placeholder for ResponseExtendVote in v0.37
reserved 19; // Placeholder for ResponseVerifyVoteExtension in v0.37
}
// nondeterministic
message ResponseException {
string error = 1;
}
message ResponseEcho {
string message = 1;
}
message ResponseFlush {}
message ResponseInfo {
string data = 1;
// this is the software version of the application. TODO: remove?
string version = 2;
uint64 app_version = 3;
int64 last_block_height = 4;
bytes last_block_app_hash = 5;
}
message ResponseInitChain {
tendermint.types.ConsensusParams consensus_params = 1;
repeated ValidatorUpdate validators = 2 [(gogoproto.nullable) = false];
bytes app_hash = 3;
}
message ResponseQuery {
uint32 code = 1;
// bytes data = 2; // use "value" instead.
string log = 3; // nondeterministic
string info = 4; // nondeterministic
int64 index = 5;
bytes key = 6;
bytes value = 7;
tendermint.crypto.ProofOps proof_ops = 8;
int64 height = 9;
string codespace = 10;
}
message ResponseBeginBlock {
repeated Event events = 1 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"];
}
message ResponseCheckTx {
uint32 code = 1;
bytes data = 2;
string log = 3; // nondeterministic
string info = 4; // nondeterministic
int64 gas_wanted = 5;
int64 gas_used = 6;
repeated Event events = 7 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"];
string codespace = 8;
string sender = 9;
int64 priority = 10;
// mempool_error is set by Tendermint.
// ABCI applications creating a ResponseCheckTX should not set mempool_error.
string mempool_error = 11;
}
message ResponseDeliverTx {
uint32 code = 1;
bytes data = 2;
string log = 3; // nondeterministic
string info = 4; // nondeterministic
int64 gas_wanted = 5 [json_name = "gas_wanted"];
int64 gas_used = 6 [json_name = "gas_used"];
repeated Event events = 7
[(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; // nondeterministic
string codespace = 8;
}
message ResponseEndBlock {
repeated ValidatorUpdate validator_updates = 1 [(gogoproto.nullable) = false];
tendermint.types.ConsensusParams consensus_param_updates = 2;
repeated Event events = 3 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"];
}
message ResponseCommit {
// reserve 1
bytes data = 2;
int64 retain_height = 3;
}
message ResponseListSnapshots {
repeated Snapshot snapshots = 1;
}
message ResponseOfferSnapshot {
Result result = 1;
enum Result {
UNKNOWN = 0; // Unknown result, abort all snapshot restoration
ACCEPT = 1; // Snapshot accepted, apply chunks
ABORT = 2; // Abort all snapshot restoration
REJECT = 3; // Reject this specific snapshot, try others
REJECT_FORMAT = 4; // Reject all snapshots of this format, try others
REJECT_SENDER = 5; // Reject all snapshots from the sender(s), try others
}
}
message ResponseLoadSnapshotChunk {
bytes chunk = 1;
}
message ResponseApplySnapshotChunk {
Result result = 1;
repeated uint32 refetch_chunks = 2; // Chunks to refetch and reapply
repeated string reject_senders = 3; // Chunk senders to reject and ban
enum Result {
UNKNOWN = 0; // Unknown result, abort all snapshot restoration
ACCEPT = 1; // Chunk successfully accepted
ABORT = 2; // Abort all snapshot restoration
RETRY = 3; // Retry chunk (combine with refetch and reject)
RETRY_SNAPSHOT = 4; // Retry snapshot (combine with refetch and reject)
REJECT_SNAPSHOT = 5; // Reject this snapshot, try others
}
}
message ResponsePrepareProposal {
bool modified_tx = 1;
repeated TxRecord tx_records = 2;
bytes app_hash = 3;
repeated ExecTxResult tx_results = 4;
repeated ValidatorUpdate validator_updates = 5;
tendermint.types.ConsensusParams consensus_param_updates = 6;
reserved 7; // Placeholder for app_signed_updates in v0.37
}
message ResponseProcessProposal {
bool accept = 1;
bytes app_hash = 2;
repeated ExecTxResult tx_results = 3;
repeated ValidatorUpdate validator_updates = 4;
tendermint.types.ConsensusParams consensus_param_updates = 5;
}
message ResponseFinalizeBlock {
repeated Event block_events = 1
[(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"];
repeated ExecTxResult tx_results = 2;
repeated ValidatorUpdate validator_updates = 3;
tendermint.types.ConsensusParams consensus_param_updates = 4;
bytes app_hash = 5;
int64 retain_height = 6;
}
//----------------------------------------
// Misc.
message LastCommitInfo {
int32 round = 1;
repeated VoteInfo votes = 2 [(gogoproto.nullable) = false];
}
// Event allows application developers to attach additional information to
// ResponseBeginBlock, ResponseEndBlock, ResponseCheckTx and ResponseDeliverTx.
// Later, transactions may be queried using these events.
message Event {
string type = 1;
repeated EventAttribute attributes = 2 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "attributes,omitempty"];
}
// EventAttribute is a single key-value pair, associated with an event.
message EventAttribute {
string key = 1;
string value = 2;
bool index = 3; // nondeterministic
}
// ExecTxResult contains results of executing one individual transaction.
//
// * Its structure is equivalent to #ResponseDeliverTx which will be deprecated/deleted
message ExecTxResult {
uint32 code = 1;
bytes data = 2;
string log = 3; // nondeterministic
string info = 4; // nondeterministic
int64 gas_wanted = 5;
int64 gas_used = 6;
repeated Event tx_events = 7
[(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; // nondeterministic
string codespace = 8;
}
// TxResult contains results of executing the transaction.
//
// One usage is indexing transaction results.
message TxResult {
int64 height = 1;
uint32 index = 2;
bytes tx = 3;
ResponseDeliverTx result = 4 [(gogoproto.nullable) = false];
}
message TxRecord {
TxAction action = 1;
bytes tx = 2;
repeated bytes new_hashes = 3;
// TxAction contains App-provided information on what to do with a transaction that is part of a raw proposal
enum TxAction {
UNKNOWN = 0; // Unknown action
UNMODIFIED = 1; // The Application did not modify this transaction. Ignore new_hashes field
ADDED = 2; // The Application added this transaction. Ignore new_hashes field
REMOVED = 3; // The Application wants this transaction removed from the proposal and the mempool.
// Use #new_hashes field if the transaction was modified
}
}
//----------------------------------------
// Blockchain Types
// Validator
message Validator {
bytes address = 1; // The first 20 bytes of SHA256(public key)
// PubKey pub_key = 2 [(gogoproto.nullable)=false];
int64 power = 3; // The voting power
}
// ValidatorUpdate
message ValidatorUpdate {
tendermint.crypto.PublicKey pub_key = 1 [(gogoproto.nullable) = false];
int64 power = 2;
}
// VoteInfo
message VoteInfo {
Validator validator = 1 [(gogoproto.nullable) = false];
bool signed_last_block = 2;
reserved 3; // Placeholder for tendermint_signed_extension in v0.37
reserved 4; // Placeholder for app_signed_extension in v0.37
}
enum EvidenceType {
UNKNOWN = 0;
DUPLICATE_VOTE = 1;
LIGHT_CLIENT_ATTACK = 2;
}
message Evidence {
EvidenceType type = 1;
// The offending validator
Validator validator = 2 [(gogoproto.nullable) = false];
// The height when the offense occurred
int64 height = 3;
// The corresponding time where the offense occurred
google.protobuf.Timestamp time = 4 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
// Total voting power of the validator set in case the ABCI application does
// not store historical validators.
// https://github.com/tendermint/tendermint/issues/4581
int64 total_voting_power = 5;
}
//----------------------------------------
// State Sync Types
message Snapshot {
uint64 height = 1; // The height at which the snapshot was taken
uint32 format = 2; // The application-specific snapshot format
uint32 chunks = 3; // Number of chunks in the snapshot
bytes hash = 4; // Arbitrary snapshot hash, equal only if identical
bytes metadata = 5; // Arbitrary application metadata
}
//----------------------------------------
// Service Definition
service ABCIApplication {
rpc Echo(RequestEcho) returns (ResponseEcho);
rpc Flush(RequestFlush) returns (ResponseFlush);
rpc Info(RequestInfo) returns (ResponseInfo);
rpc CheckTx(RequestCheckTx) returns (ResponseCheckTx);
rpc Query(RequestQuery) returns (ResponseQuery);
rpc Commit(RequestCommit) returns (ResponseCommit);
rpc InitChain(RequestInitChain) returns (ResponseInitChain);
rpc ListSnapshots(RequestListSnapshots) returns (ResponseListSnapshots);
rpc OfferSnapshot(RequestOfferSnapshot) returns (ResponseOfferSnapshot);
rpc LoadSnapshotChunk(RequestLoadSnapshotChunk) returns (ResponseLoadSnapshotChunk);
rpc ApplySnapshotChunk(RequestApplySnapshotChunk) returns (ResponseApplySnapshotChunk);
rpc PrepareProposal(RequestPrepareProposal) returns (ResponsePrepareProposal);
rpc ProcessProposal(RequestProcessProposal) returns (ResponseProcessProposal);
rpc FinalizeBlock(RequestFinalizeBlock) returns (ResponseFinalizeBlock);
}

+ 39
- 0
proto/tendermint/blocksync/types.proto View File

@ -0,0 +1,39 @@
syntax = "proto3";
package tendermint.blocksync;
import "tendermint/types/block.proto";
// BlockRequest requests a block for a specific height
message BlockRequest {
int64 height = 1;
}
// NoBlockResponse informs the node that the peer does not have block at the
// requested height
message NoBlockResponse {
int64 height = 1;
}
// BlockResponse returns block to the requested
message BlockResponse {
tendermint.types.Block block = 1;
}
// StatusRequest requests the status of a peer.
message StatusRequest {}
// StatusResponse is a peer response to inform their status.
message StatusResponse {
int64 height = 1;
int64 base = 2;
}
message Message {
oneof sum {
BlockRequest block_request = 1;
NoBlockResponse no_block_response = 2;
BlockResponse block_response = 3;
StatusRequest status_request = 4;
StatusResponse status_response = 5;
}
}

+ 97
- 0
proto/tendermint/consensus/types.proto View File

@ -0,0 +1,97 @@
syntax = "proto3";
package tendermint.consensus;
import "gogoproto/gogo.proto";
import "tendermint/types/types.proto";
import "tendermint/libs/bits/types.proto";
// NewRoundStep is sent for every step taken in the ConsensusState.
// For every height/round/step transition
message NewRoundStep {
int64 height = 1;
int32 round = 2;
uint32 step = 3;
int64 seconds_since_start_time = 4;
int32 last_commit_round = 5;
}
// NewValidBlock is sent when a validator observes a valid block B in some round
// r,
// i.e., there is a Proposal for block B and 2/3+ prevotes for the block B in
// the round r.
// In case the block is also committed, then IsCommit flag is set to true.
message NewValidBlock {
int64 height = 1;
int32 round = 2;
tendermint.types.PartSetHeader block_part_set_header = 3
[(gogoproto.nullable) = false];
tendermint.libs.bits.BitArray block_parts = 4;
bool is_commit = 5;
}
// Proposal is sent when a new block is proposed.
message Proposal {
tendermint.types.Proposal proposal = 1 [(gogoproto.nullable) = false];
}
// ProposalPOL is sent when a previous proposal is re-proposed.
message ProposalPOL {
int64 height = 1;
int32 proposal_pol_round = 2;
tendermint.libs.bits.BitArray proposal_pol = 3
[(gogoproto.nullable) = false];
}
// BlockPart is sent when gossipping a piece of the proposed block.
message BlockPart {
int64 height = 1;
int32 round = 2;
tendermint.types.Part part = 3 [(gogoproto.nullable) = false];
}
// Vote is sent when voting for a proposal (or lack thereof).
message Vote {
tendermint.types.Vote vote = 1;
}
// HasVote is sent to indicate that a particular vote has been received.
message HasVote {
int64 height = 1;
int32 round = 2;
tendermint.types.SignedMsgType type = 3;
int32 index = 4;
}
// VoteSetMaj23 is sent to indicate that a given BlockID has seen +2/3 votes.
message VoteSetMaj23 {
int64 height = 1;
int32 round = 2;
tendermint.types.SignedMsgType type = 3;
tendermint.types.BlockID block_id = 4
[(gogoproto.customname) = "BlockID", (gogoproto.nullable) = false];
}
// VoteSetBits is sent to communicate the bit-array of votes seen for the
// BlockID.
message VoteSetBits {
int64 height = 1;
int32 round = 2;
tendermint.types.SignedMsgType type = 3;
tendermint.types.BlockID block_id = 4
[(gogoproto.customname) = "BlockID", (gogoproto.nullable) = false];
tendermint.libs.bits.BitArray votes = 5 [(gogoproto.nullable) = false];
}
message Message {
oneof sum {
NewRoundStep new_round_step = 1;
NewValidBlock new_valid_block = 2;
Proposal proposal = 3;
ProposalPOL proposal_pol = 4;
BlockPart block_part = 5;
Vote vote = 6;
HasVote has_vote = 7;
VoteSetMaj23 vote_set_maj23 = 8;
VoteSetBits vote_set_bits = 9;
}
}

+ 0
- 2
proto/tendermint/consensus/wal.proto View File

@ -1,8 +1,6 @@
syntax = "proto3";
package tendermint.consensus;
option go_package = "github.com/tendermint/tendermint/proto/tendermint/consensus";
import "gogoproto/gogo.proto";
import "tendermint/consensus/types.proto";
import "tendermint/types/events.proto";


+ 16
- 0
proto/tendermint/crypto/keys.proto View File

@ -0,0 +1,16 @@
syntax = "proto3";
package tendermint.crypto;
import "gogoproto/gogo.proto";
// PublicKey defines the keys available for use with Tendermint Validators
message PublicKey {
option (gogoproto.compare) = true;
option (gogoproto.equal) = true;
oneof sum {
bytes ed25519 = 1;
bytes secp256k1 = 2;
bytes sr25519 = 3;
}
}

+ 39
- 0
proto/tendermint/crypto/proof.proto View File

@ -0,0 +1,39 @@
syntax = "proto3";
package tendermint.crypto;
import "gogoproto/gogo.proto";
message Proof {
int64 total = 1;
int64 index = 2;
bytes leaf_hash = 3;
repeated bytes aunts = 4;
}
message ValueOp {
// Encoded in ProofOp.Key.
bytes key = 1;
// To encode in ProofOp.Data
Proof proof = 2;
}
message DominoOp {
string key = 1;
string input = 2;
string output = 3;
}
// ProofOp defines an operation used for calculating Merkle root
// The data could be arbitrary format, providing nessecary data
// for example neighbouring node hash
message ProofOp {
string type = 1;
bytes key = 2;
bytes data = 3;
}
// ProofOps is Merkle proof defined by the list of ProofOps
message ProofOps {
repeated ProofOp ops = 1 [(gogoproto.nullable) = false];
}

+ 7
- 0
proto/tendermint/libs/bits/types.proto View File

@ -0,0 +1,7 @@
syntax = "proto3";
package tendermint.libs.bits;
message BitArray {
int64 bits = 1;
repeated uint64 elems = 2;
}

+ 12
- 0
proto/tendermint/mempool/types.proto View File

@ -0,0 +1,12 @@
syntax = "proto3";
package tendermint.mempool;
message Txs {
repeated bytes txs = 1;
}
message Message {
oneof sum {
Txs txs = 1;
}
}

+ 28
- 0
proto/tendermint/p2p/conn.proto View File

@ -0,0 +1,28 @@
syntax = "proto3";
package tendermint.p2p;
import "gogoproto/gogo.proto";
import "tendermint/crypto/keys.proto";
message PacketPing {}
message PacketPong {}
message PacketMsg {
int32 channel_id = 1 [(gogoproto.customname) = "ChannelID"];
bool eof = 2 [(gogoproto.customname) = "EOF"];
bytes data = 3;
}
message Packet {
oneof sum {
PacketPing packet_ping = 1;
PacketPong packet_pong = 2;
PacketMsg packet_msg = 3;
}
}
message AuthSigMessage {
tendermint.crypto.PublicKey pub_key = 1 [(gogoproto.nullable) = false];
bytes sig = 2;
}

+ 24
- 0
proto/tendermint/p2p/pex.proto View File

@ -0,0 +1,24 @@
syntax = "proto3";
package tendermint.p2p;
import "gogoproto/gogo.proto";
message PexAddress {
string url = 1 [(gogoproto.customname) = "URL"];
reserved 2, 3; // See https://github.com/tendermint/spec/pull/352
}
message PexRequest {}
message PexResponse {
repeated PexAddress addresses = 1 [(gogoproto.nullable) = false];
}
message PexMessage {
reserved 1, 2; // See https://github.com/tendermint/spec/pull/352
oneof sum {
PexRequest pex_request = 3;
PexResponse pex_response = 4;
}
}

+ 42
- 0
proto/tendermint/p2p/types.proto View File

@ -0,0 +1,42 @@
syntax = "proto3";
package tendermint.p2p;
import "gogoproto/gogo.proto";
import "google/protobuf/timestamp.proto";
message ProtocolVersion {
uint64 p2p = 1 [(gogoproto.customname) = "P2P"];
uint64 block = 2;
uint64 app = 3;
}
message NodeInfo {
ProtocolVersion protocol_version = 1 [(gogoproto.nullable) = false];
string node_id = 2 [(gogoproto.customname) = "NodeID"];
string listen_addr = 3;
string network = 4;
string version = 5;
bytes channels = 6;
string moniker = 7;
NodeInfoOther other = 8 [(gogoproto.nullable) = false];
}
message NodeInfoOther {
string tx_index = 1;
string rpc_address = 2 [(gogoproto.customname) = "RPCAddress"];
}
message PeerInfo {
string id = 1 [(gogoproto.customname) = "ID"];
repeated PeerAddressInfo address_info = 2;
google.protobuf.Timestamp last_connected = 3 [(gogoproto.stdtime) = true];
}
message PeerAddressInfo {
string address = 1;
google.protobuf.Timestamp last_dial_success = 2
[(gogoproto.stdtime) = true];
google.protobuf.Timestamp last_dial_failure = 3
[(gogoproto.stdtime) = true];
uint32 dial_failures = 4;
}

+ 1
- 1
proto/tendermint/privval/service.proto View File

@ -1,6 +1,6 @@
syntax = "proto3";
package tendermint.privval;
option go_package = "github.com/tendermint/tendermint/proto/tendermint/privval";
option go_package = "github.com/tendermint/tendermint/proto/tendermint/privval";
import "tendermint/privval/types.proto";


+ 61
- 0
proto/tendermint/statesync/types.proto View File

@ -0,0 +1,61 @@
syntax = "proto3";
package tendermint.statesync;
import "gogoproto/gogo.proto";
import "tendermint/types/types.proto";
import "tendermint/types/params.proto";
message Message {
oneof sum {
SnapshotsRequest snapshots_request = 1;
SnapshotsResponse snapshots_response = 2;
ChunkRequest chunk_request = 3;
ChunkResponse chunk_response = 4;
LightBlockRequest light_block_request = 5;
LightBlockResponse light_block_response = 6;
ParamsRequest params_request = 7;
ParamsResponse params_response = 8;
}
}
message SnapshotsRequest {}
message SnapshotsResponse {
uint64 height = 1;
uint32 format = 2;
uint32 chunks = 3;
bytes hash = 4;
bytes metadata = 5;
}
message ChunkRequest {
uint64 height = 1;
uint32 format = 2;
uint32 index = 3;
}
message ChunkResponse {
uint64 height = 1;
uint32 format = 2;
uint32 index = 3;
bytes chunk = 4;
bool missing = 5;
}
message LightBlockRequest {
uint64 height = 1;
}
message LightBlockResponse {
tendermint.types.LightBlock light_block = 1;
}
message ParamsRequest {
uint64 height = 1;
}
message ParamsResponse {
uint64 height = 1;
tendermint.types.ConsensusParams consensus_params = 2
[(gogoproto.nullable) = false];
}

+ 13
- 0
proto/tendermint/types/block.proto View File

@ -0,0 +1,13 @@
syntax = "proto3";
package tendermint.types;
import "gogoproto/gogo.proto";
import "tendermint/types/types.proto";
import "tendermint/types/evidence.proto";
message Block {
Header header = 1 [(gogoproto.nullable) = false];
Data data = 2 [(gogoproto.nullable) = false];
tendermint.types.EvidenceList evidence = 3 [(gogoproto.nullable) = false];
Commit last_commit = 4;
}

+ 0
- 3
proto/tendermint/types/canonical.proto View File

@ -1,8 +1,6 @@
syntax = "proto3";
package tendermint.types;
option go_package = "github.com/tendermint/tendermint/proto/tendermint/types";
import "gogoproto/gogo.proto";
import "tendermint/types/types.proto";
import "google/protobuf/timestamp.proto";
@ -34,5 +32,4 @@ message CanonicalVote {
CanonicalBlockID block_id = 4 [(gogoproto.customname) = "BlockID"];
google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
string chain_id = 6 [(gogoproto.customname) = "ChainID"];
VoteExtensionToSign vote_extension = 7;
}

+ 0
- 2
proto/tendermint/types/events.proto View File

@ -1,8 +1,6 @@
syntax = "proto3";
package tendermint.types;
option go_package = "github.com/tendermint/tendermint/proto/tendermint/types";
message EventDataRoundState {
int64 height = 1;
int32 round = 2;


+ 40
- 0
proto/tendermint/types/evidence.proto View File

@ -0,0 +1,40 @@
syntax = "proto3";
package tendermint.types;
import "gogoproto/gogo.proto";
import "google/protobuf/timestamp.proto";
import "tendermint/types/types.proto";
import "tendermint/types/validator.proto";
message Evidence {
oneof sum {
DuplicateVoteEvidence duplicate_vote_evidence = 1;
LightClientAttackEvidence light_client_attack_evidence = 2;
}
}
// DuplicateVoteEvidence contains evidence of a validator signed two conflicting
// votes.
message DuplicateVoteEvidence {
tendermint.types.Vote vote_a = 1;
tendermint.types.Vote vote_b = 2;
int64 total_voting_power = 3;
int64 validator_power = 4;
google.protobuf.Timestamp timestamp = 5
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
}
// LightClientAttackEvidence contains evidence of a set of validators attempting
// to mislead a light client.
message LightClientAttackEvidence {
tendermint.types.LightBlock conflicting_block = 1;
int64 common_height = 2;
repeated tendermint.types.Validator byzantine_validators = 3;
int64 total_voting_power = 4;
google.protobuf.Timestamp timestamp = 5
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
}
message EvidenceList {
repeated Evidence evidence = 1 [(gogoproto.nullable) = false];
}

+ 127
- 0
proto/tendermint/types/params.proto View File

@ -0,0 +1,127 @@
syntax = "proto3";
package tendermint.types;
import "gogoproto/gogo.proto";
import "google/protobuf/duration.proto";
option (gogoproto.equal_all) = true;
// ConsensusParams contains consensus critical parameters that determine the
// validity of blocks.
message ConsensusParams {
BlockParams block = 1;
EvidenceParams evidence = 2;
ValidatorParams validator = 3;
VersionParams version = 4;
SynchronyParams synchrony = 5;
TimeoutParams timeout = 6;
}
// BlockParams contains limits on the block size.
message BlockParams {
// Max block size, in bytes.
// Note: must be greater than 0
int64 max_bytes = 1;
// Max gas per block.
// Note: must be greater or equal to -1
int64 max_gas = 2;
}
// EvidenceParams determine how we handle evidence of malfeasance.
message EvidenceParams {
// Max age of evidence, in blocks.
//
// The basic formula for calculating this is: MaxAgeDuration / {average block
// time}.
int64 max_age_num_blocks = 1;
// Max age of evidence, in time.
//
// It should correspond with an app's "unbonding period" or other similar
// mechanism for handling [Nothing-At-Stake
// attacks](https://github.com/ethereum/wiki/wiki/Proof-of-Stake-FAQ#what-is-the-nothing-at-stake-problem-and-how-can-it-be-fixed).
google.protobuf.Duration max_age_duration = 2
[(gogoproto.nullable) = false, (gogoproto.stdduration) = true];
// This sets the maximum size of total evidence in bytes that can be committed
// in a single block. and should fall comfortably under the max block bytes.
// Default is 1048576 or 1MB
int64 max_bytes = 3;
}
// ValidatorParams restrict the public key types validators can use.
// NOTE: uses ABCI pubkey naming, not Amino names.
message ValidatorParams {
repeated string pub_key_types = 1;
}
// VersionParams contains the ABCI application version.
message VersionParams {
uint64 app_version = 1;
}
// HashedParams is a subset of ConsensusParams.
//
// It is hashed into the Header.ConsensusHash.
message HashedParams {
int64 block_max_bytes = 1;
int64 block_max_gas = 2;
}
// SynchronyParams configure the bounds under which a proposed block's timestamp is considered valid.
// These parameters are part of the proposer-based timestamps algorithm. For more information,
// see the specification of proposer-based timestamps:
// https://github.com/tendermint/tendermint/tree/master/spec/consensus/proposer-based-timestamp
message SynchronyParams {
// message_delay bounds how long a proposal message may take to reach all validators on a newtork
// and still be considered valid.
google.protobuf.Duration message_delay = 1 [(gogoproto.stdduration) = true];
// precision bounds how skewed a proposer's clock may be from any validator
// on the network while still producing valid proposals.
google.protobuf.Duration precision = 2 [(gogoproto.stdduration) = true];
}
// TimeoutParams configure the timeouts for the steps of the Tendermint consensus algorithm.
message TimeoutParams {
// These fields configure the timeouts for the propose step of the Tendermint
// consensus algorithm: propose is the initial timeout and propose_delta
// determines how much the timeout grows in subsequent rounds.
// For the first round, this propose timeout is used and for every subsequent
// round, the timeout grows by propose_delta.
//
// For example:
// With propose = 10ms, propose_delta = 5ms, the first round's propose phase
// timeout would be 10ms, the second round's would be 15ms, the third 20ms and so on.
//
// If a node waiting for a proposal message does not receive one matching its
// current height and round before this timeout, the node will issue a
// nil prevote for the round and advance to the next step.
google.protobuf.Duration propose = 1 [(gogoproto.stdduration) = true];
google.protobuf.Duration propose_delta = 2 [(gogoproto.stdduration) = true];
// vote along with vote_delta configure the timeout for both of the prevote and
// precommit steps of the Tendermint consensus algorithm.
//
// These parameters influence the vote step timeouts in the the same way that
// the propose and propose_delta parameters do to the proposal step.
//
// The vote timeout does not begin until a quorum of votes has been received. Once
// a quorum of votes has been seen and this timeout elapses, Tendermint will
// procced to the next step of the consensus algorithm. If Tendermint receives
// all of the remaining votes before the end of the timeout, it will proceed
// to the next step immediately.
google.protobuf.Duration vote = 3 [(gogoproto.stdduration) = true];
google.protobuf.Duration vote_delta = 4 [(gogoproto.stdduration) = true];
// commit configures how long Tendermint will wait after receiving a quorum of
// precommits before beginning consensus for the next height. This can be
// used to allow slow precommits to arrive for inclusion in the next height before progressing.
google.protobuf.Duration commit = 5 [(gogoproto.stdduration) = true];
// enable_commit_timeout_bypass configures the node to proceed immediately to
// the next height once the node has received all precommits for a block, forgoing
// the remaining commit timeout.
// Setting enable_commit_timeout_bypass false (the default) causes Tendermint to wait
// for the full commit.
bool enable_commit_timeout_bypass = 6;
}

+ 171
- 0
proto/tendermint/types/types.proto View File

@ -0,0 +1,171 @@
syntax = "proto3";
package tendermint.types;
import "gogoproto/gogo.proto";
import "google/protobuf/timestamp.proto";
import "tendermint/crypto/proof.proto";
import "tendermint/version/types.proto";
import "tendermint/types/validator.proto";
// BlockIdFlag indicates which BlcokID the signature is for
enum BlockIDFlag {
option (gogoproto.goproto_enum_stringer) = true;
option (gogoproto.goproto_enum_prefix) = false;
BLOCK_ID_FLAG_UNKNOWN = 0
[(gogoproto.enumvalue_customname) = "BlockIDFlagUnknown"];
BLOCK_ID_FLAG_ABSENT = 1
[(gogoproto.enumvalue_customname) = "BlockIDFlagAbsent"];
BLOCK_ID_FLAG_COMMIT = 2
[(gogoproto.enumvalue_customname) = "BlockIDFlagCommit"];
BLOCK_ID_FLAG_NIL = 3 [(gogoproto.enumvalue_customname) = "BlockIDFlagNil"];
}
// SignedMsgType is a type of signed message in the consensus.
enum SignedMsgType {
option (gogoproto.goproto_enum_stringer) = true;
option (gogoproto.goproto_enum_prefix) = false;
SIGNED_MSG_TYPE_UNKNOWN = 0
[(gogoproto.enumvalue_customname) = "UnknownType"];
// Votes
SIGNED_MSG_TYPE_PREVOTE = 1
[(gogoproto.enumvalue_customname) = "PrevoteType"];
SIGNED_MSG_TYPE_PRECOMMIT = 2
[(gogoproto.enumvalue_customname) = "PrecommitType"];
// Proposals
SIGNED_MSG_TYPE_PROPOSAL = 32
[(gogoproto.enumvalue_customname) = "ProposalType"];
}
// PartsetHeader
message PartSetHeader {
uint32 total = 1;
bytes hash = 2;
}
message Part {
uint32 index = 1;
bytes bytes = 2;
tendermint.crypto.Proof proof = 3 [(gogoproto.nullable) = false];
}
// BlockID
message BlockID {
bytes hash = 1;
PartSetHeader part_set_header = 2 [(gogoproto.nullable) = false];
}
// --------------------------------
// Header defines the structure of a Tendermint block header.
message Header {
// basic block info
tendermint.version.Consensus version = 1 [(gogoproto.nullable) = false];
string chain_id = 2 [(gogoproto.customname) = "ChainID"];
int64 height = 3;
google.protobuf.Timestamp time = 4
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
// prev block info
BlockID last_block_id = 5 [(gogoproto.nullable) = false];
// hashes of block data
bytes last_commit_hash = 6; // commit from validators from the last block
bytes data_hash = 7; // transactions
// hashes from the app output from the prev block
bytes validators_hash = 8; // validators for the current block
bytes next_validators_hash = 9; // validators for the next block
bytes consensus_hash = 10; // consensus params for current block
bytes app_hash = 11; // state after txs from the previous block
bytes last_results_hash =
12; // root hash of all results from the txs from the previous block
// consensus info
bytes evidence_hash = 13; // evidence included in the block
bytes proposer_address = 14; // original proposer of the block
}
// Data contains the set of transactions included in the block
message Data {
// Txs that will be applied by state @ block.Height+1.
// NOTE: not all txs here are valid. We're just agreeing on the order first.
// This means that block.AppHash does not include these txs.
repeated bytes txs = 1;
}
// Vote represents a prevote, precommit, or commit vote from validators for
// consensus.
message Vote {
SignedMsgType type = 1;
int64 height = 2;
int32 round = 3;
BlockID block_id = 4 [
(gogoproto.nullable) = false,
(gogoproto.customname) = "BlockID"
]; // zero if vote is nil.
google.protobuf.Timestamp timestamp = 5
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
bytes validator_address = 6;
int32 validator_index = 7;
bytes signature = 8;
}
// Commit contains the evidence that a block was committed by a set of
// validators.
message Commit {
int64 height = 1;
int32 round = 2;
BlockID block_id = 3
[(gogoproto.nullable) = false, (gogoproto.customname) = "BlockID"];
repeated CommitSig signatures = 4 [(gogoproto.nullable) = false];
}
// CommitSig is a part of the Vote included in a Commit.
message CommitSig {
BlockIDFlag block_id_flag = 1;
bytes validator_address = 2;
google.protobuf.Timestamp timestamp = 3
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
bytes signature = 4;
}
message Proposal {
SignedMsgType type = 1;
int64 height = 2;
int32 round = 3;
int32 pol_round = 4;
BlockID block_id = 5
[(gogoproto.customname) = "BlockID", (gogoproto.nullable) = false];
google.protobuf.Timestamp timestamp = 6
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
bytes signature = 7;
}
message SignedHeader {
Header header = 1;
Commit commit = 2;
}
message LightBlock {
SignedHeader signed_header = 1;
tendermint.types.ValidatorSet validator_set = 2;
}
message BlockMeta {
BlockID block_id = 1
[(gogoproto.customname) = "BlockID", (gogoproto.nullable) = false];
int64 block_size = 2;
Header header = 3 [(gogoproto.nullable) = false];
int64 num_txs = 4;
}
// TxProof represents a Merkle proof of the presence of a transaction in the
// Merkle tree.
message TxProof {
bytes root_hash = 1;
bytes data = 2;
tendermint.crypto.Proof proof = 3;
}

+ 23
- 0
proto/tendermint/types/validator.proto View File

@ -0,0 +1,23 @@
syntax = "proto3";
package tendermint.types;
import "gogoproto/gogo.proto";
import "tendermint/crypto/keys.proto";
message ValidatorSet {
repeated Validator validators = 1;
Validator proposer = 2;
int64 total_voting_power = 3;
}
message Validator {
bytes address = 1;
tendermint.crypto.PublicKey pub_key = 2 [(gogoproto.nullable) = false];
int64 voting_power = 3;
int64 proposer_priority = 4;
}
message SimpleValidator {
tendermint.crypto.PublicKey pub_key = 1;
int64 voting_power = 2;
}

+ 14
- 0
proto/tendermint/version/types.proto View File

@ -0,0 +1,14 @@
syntax = "proto3";
package tendermint.version;
import "gogoproto/gogo.proto";
// Consensus captures the consensus rules for processing a block in the
// blockchain, including all blockchain data structures and the rules of the
// application's state transition machine.
message Consensus {
option (gogoproto.equal) = true;
uint64 block = 1;
uint64 app = 2;
}

+ 99
- 0
spec/README.md View File

@ -0,0 +1,99 @@
---
order: 1
title: Overview
parent:
title: Spec
order: 7
---
# Tendermint Spec
This is a Markdown specification of the Tendermint blockchain.
It defines the base data structures, how they are validated,
and how they are communicated over the network.
If you find discrepancies between the spec and the code that
do not have an associated issue or pull request on github,
please submit them to our [bug bounty](https://tendermint.com/security)!
## Contents
- [Overview](#overview)
### Data Structures
- [Encoding and Digests](./core/encoding.md)
- [Blockchain](./core/data_structures.md)
- [State](./core/state.md)
### Consensus Protocol
- [Consensus Algorithm](./consensus/consensus.md)
- [Creating a proposal](./consensus/creating-proposal.md)
- [Time](./consensus/bft-time.md)
- [Light-Client](./consensus/light-client/README.md)
### P2P and Network Protocols
- [The Base P2P Layer](./p2p/node.md): multiplex the protocols ("reactors") on authenticated and encrypted TCP connections
- [Peer Exchange (PEX)](./p2p/messages/pex.md): gossip known peer addresses so peers can find each other
- [Block Sync](./p2p/messages/block-sync.md): gossip blocks so peers can catch up quickly
- [Consensus](./p2p/messages/consensus.md): gossip votes and block parts so new blocks can be committed
- [Mempool](./p2p/messages/mempool.md): gossip transactions so they get included in blocks
- [Evidence](./p2p/messages/evidence.md): sending invalid evidence will stop the peer
### RPC
- [RPC SPEC](./rpc/README.md): Specification of the Tendermint remote procedure call interface.
### Software
- [ABCI](./abci/README.md): Details about interactions between the
application and consensus engine over ABCI
- [ABCI++](./abci++/README.md): Specification of interactions between the
application and consensus engine over ABCI++
- [Write-Ahead Log](./consensus/wal.md): Details about how the consensus
engine preserves data and recovers from crash failures
### Ivy Proofs
- [Ivy Proofs](./ivy-proofs/README.md)
## Overview
Tendermint provides Byzantine Fault Tolerant State Machine Replication using
hash-linked batches of transactions. Such transaction batches are called "blocks".
Hence, Tendermint defines a "blockchain".
Each block in Tendermint has a unique index - its Height.
Height's in the blockchain are monotonic.
Each block is committed by a known set of weighted Validators.
Membership and weighting within this validator set may change over time.
Tendermint guarantees the safety and liveness of the blockchain
so long as less than 1/3 of the total weight of the Validator set
is malicious or faulty.
A commit in Tendermint is a set of signed messages from more than 2/3 of
the total weight of the current Validator set. Validators take turns proposing
blocks and voting on them. Once enough votes are received, the block is considered
committed. These votes are included in the _next_ block as proof that the previous block
was committed - they cannot be included in the current block, as that block has already been
created.
Once a block is committed, it can be executed against an application.
The application returns results for each of the transactions in the block.
The application can also return changes to be made to the validator set,
as well as a cryptographic digest of its latest state.
Tendermint is designed to enable efficient verification and authentication
of the latest state of the blockchain. To achieve this, it embeds
cryptographic commitments to certain information in the block "header".
This information includes the contents of the block (eg. the transactions),
the validator set committing the block, as well as the various results returned by the application.
Note, however, that block execution only occurs _after_ a block is committed.
Thus, application results can only be included in the _next_ block.
Also note that information like the transaction results and the validator set are never
directly included in the block - only their cryptographic digests (Merkle roots) are.
Hence, verification of a block requires a separate data structure to store this information.
We call this the `State`. Block verification also requires access to the previous block.

+ 45
- 0
spec/abci++/README.md View File

@ -0,0 +1,45 @@
---
order: 1
parent:
title: ABCI++
order: 3
---
# ABCI++
## Introduction
ABCI++ is a major evolution of ABCI (**A**pplication **B**lock**c**hain **I**nterface).
Like its predecessor, ABCI++ is the interface between Tendermint (a state-machine
replication engine) and the actual state machine being replicated (i.e., the Application).
The API consists of a set of _methods_, each with a corresponding `Request` and `Response`
message type.
The methods are always initiated by Tendermint. The Application implements its logic
for handling all ABCI++ methods.
Thus, Tendermint always sends the `Request*` messages and receives the `Response*` messages
in return.
All ABCI++ messages and methods are defined in
[protocol buffers](https://github.com/tendermint/tendermint/blob/master/proto/spec/abci/types.proto).
This allows Tendermint to run with applications written in many programming languages.
This specification is split as follows:
- [Basic concepts and definitions](./abci++_basic_concepts_002_draft.md) - definitions and descriptions
of concepts that are needed to understand other parts of this sepcification.
- [Methods](./abci++_methods_002_draft.md) - complete details on all ABCI++ methods
and message types.
- [Requirements for the Application](./abci++_app_requirements_002_draft.md) - formal requirements
on the Application's logic to ensure liveness of Tendermint. These requirements define what
Tendermint expects from the Application.
- [Tendermint's expected behavior](./abci++_tmint_expected_behavior_002_draft.md) - specification of
how the different ABCI++ methods may be called by Tendermint. This explains what the Application
is to expect from Tendermint.
>**TODO** Re-read these and remove redundant info
- [Applications](../abci/apps.md) - how to manage ABCI application state and other
details about building ABCI applications
- [Client and Server](../abci/client-server.md) - for those looking to implement their
own ABCI application servers

+ 158
- 0
spec/abci++/abci++_app_requirements_002_draft.md View File

@ -0,0 +1,158 @@
---
order: 3
title: Application Requirements
---
# Application Requirements
This section specifies what Tendermint expects from the Application. It is structured as a set
of formal requirement that can be used for testing and verification of the Application's logic.
Let $p$ and $q$ be two different correct proposers in rounds $r_p$ and $r_q$ respectively, in height $h$.
Let $s_{p,h-1}$ be $p$'s Application's state committed for height $h-1$.
Let $v_p$ (resp. $v_q$) be the block that $p$'s (resp. $q$'s) Tendermint passes on to the Application
via `RequestPrepareProposal` as proposer of round $r_p$ (resp $r_q$), height $h$, also known as the
raw proposal.
Let $v'_p$ (resp. $v'_q$) the possibly modified block $p$'s (resp. $q$'s) Application returns via
`ResponsePrepareProposal` to Tendermint, also known as the prepared proposal.
Process $p$'s prepared proposal can differ in two different rounds where $p$ is the proposer.
* Requirement 1 [`PrepareProposal`, header-changes] When the blockchain is in same-block execution mode,
$p$'s Application provides values for the following parameters in `ResponsePrepareProposal`:
_AppHash_, _TxResults_, _ConsensusParams_, _ValidatorUpdates_. Provided values for
_ConsensusParams_ and _ValidatorUpdates_ MAY be empty to denote that the Application
wishes to keep the current values.
Parameters _AppHash_, _TxResults_, _ConsensusParams_, and _ValidatorUpdates_ are used by Tendermint to
compute various hashes in the block header that will finally be part of the proposal.
* Requirement 2 [`PrepareProposal`, no-header-changes] When the blockchain is in next-block execution
mode, $p$'s Application does not provide values for the following parameters in `ResponsePrepareProposal`:
_AppHash_, _TxResults_, _ConsensusParams_, _ValidatorUpdates_.
In practical terms, Requirements 1 and 2 imply that Tendermint will (a) panic if the Application is in
same-block execution mode and _does_ _not_ provide values for
_AppHash_, _TxResults_, _ConsensusParams_, and _ValidatorUpdates_, or
(b) log an error if the Application is in next-block execution mode and _does_ provide values for
_AppHash_, _TxResults_, _ConsensusParams_, or _ValidatorUpdates_ (the values provided will be ignored).
* Requirement 3 [`PrepareProposal`, timeliness] If $p$'s Application fully executes prepared blocks in
`PrepareProposal` and the network is in a synchronous period while processes $p$ and $q$ are in $r_p$, then
the value of *TimeoutPropose* at $q$ must be such that $q$'s propose timer does not time out
(which would result in $q$ prevoting *nil* in $r_p$).
Full execution of blocks at `PrepareProposal` time stands on Tendermint's critical path. Thus,
Requirement 3 ensures the Application will set a value for _TimeoutPropose_ such that the time it takes
to fully execute blocks in `PrepareProposal` does not interfere with Tendermint's propose timer.
* Requirement 4 [`PrepareProposal`, `ProcessProposal`, coherence]: For any two correct processes $p$ and $q$,
if $q$'s Tendermint calls `RequestProcessProposal` on $v'_p$,
$q$'s Application returns Accept in `ResponseProcessProposal`.
Requirement 4 makes sure that blocks proposed by correct processes _always_ pass the correct receiving process's
`ProcessProposal` check.
On the other hand, if there is a deterministic bug in `PrepareProposal` or `ProcessProposal` (or in both),
strictly speaking, this makes all processes that hit the bug byzantine. This is a problem in practice,
as very often validators are running the Application from the same codebase, so potentially _all_ would
likely hit the bug at the same time. This would result in most (or all) processes prevoting `nil`, with the
serious consequences on Tendermint's liveness that this entails. Due to its criticality, Requirement 4 is a
target for extensive testing and automated verification.
* Requirement 5 [`ProcessProposal`, determinism-1]: `ProcessProposal` is a (deterministic) function of the current
state and the block that is about to be applied. In other words, for any correct process $p$, and any arbitrary block $v'$,
if $p$'s Tendermint calls `RequestProcessProposal` on $v'$ at height $h$,
then $p$'s Application's acceptance or rejection **exclusively** depends on $v'$ and $s_{p,h-1}$.
* Requirement 6 [`ProcessProposal`, determinism-2]: For any two correct processes $p$ and $q$, and any arbitrary block $v'$,
if $p$'s (resp. $q$'s) Tendermint calls `RequestProcessProposal` on $v'$ at height $h$,
then $p$'s Application accepts $v'$ if and only if $q$'s Application accepts $v'$.
Note that this requirement follows from Requirement 5 and the Agreement property of consensus.
Requirements 5 and 6 ensure that all correct processes will react in the same way to a proposed block, even
if the proposer is Byzantine. However, `ProcessProposal` may contain a bug that renders the
acceptance or rejection of the block non-deterministic, and therefore prevents processes hitting
the bug from fulfilling Requirements 5 or 6 (effectively making those processes Byzantine).
In such a scenario, Tendermint's liveness cannot be guaranteed.
Again, this is a problem in practice if most validators are running the same software, as they are likely
to hit the bug at the same point. There is currently no clear solution to help with this situation, so
the Application designers/implementors must proceed very carefully with the logic/implementation
of `ProcessProposal`. As a general rule `ProcessProposal` _should_ always accept the block.
According to the Tendermint algorithm, a correct process can broadcast at most one precommit message in round $r$, height $h$.
Since, as stated in the [Description](#description) section, `ResponseExtendVote` is only called when Tendermint
is about to broadcast a non-`nil` precommit message, a correct process can only produce one vote extension in round $r$, height $h$.
Let $e^r_p$ be the vote extension that the Application of a correct process $p$ returns via `ResponseExtendVote` in round $r$, height $h$.
Let $w^r_p$ be the proposed block that $p$'s Tendermint passes to the Application via `RequestExtendVote` in round $r$, height $h$.
* Requirement 7 [`ExtendVote`, `VerifyVoteExtension`, coherence]: For any two correct processes $p$ and $q$, if $q$ receives $e^r_p$
from $p$ in height $h$, $q$'s Application returns Accept in `ResponseVerifyVoteExtension`.
Requirement 7 constrains the creation and handling of vote extensions in a similar way as Requirement 4
contrains the creation and handling of proposed blocks.
Requirement 7 ensures that extensions created by correct processes _always_ pass the `VerifyVoteExtension`
checks performed by correct processes receiving those extensions.
However, if there is a (deterministic) bug in `ExtendVote` or `VerifyVoteExtension` (or in both),
we will face the same liveness issues as described for Requirement 4, as Precommit messages with invalid vote
extensions will be discarded.
* Requirement 8 [`VerifyVoteExtension`, determinism-1]: `VerifyVoteExtension` is a (deterministic) function of
the current state, the vote extension received, and the prepared proposal that the extension refers to.
In other words, for any correct process $p$, and any arbitrary vote extension $e$, and any arbitrary
block $w$, if $p$'s (resp. $q$'s) Tendermint calls `RequestVerifyVoteExtension` on $e$ and $w$ at height $h$,
then $p$'s Application's acceptance or rejection **exclusively** depends on $e$, $w$ and $s_{p,h-1}$.
* Requirement 9 [`VerifyVoteExtension`, determinism-2]: For any two correct processes $p$ and $q$,
and any arbitrary vote extension $e$, and any arbitrary block $w$,
if $p$'s (resp. $q$'s) Tendermint calls `RequestVerifyVoteExtension` on $e$ and $w$ at height $h$,
then $p$'s Application accepts $e$ if and only if $q$'s Application accepts $e$.
Note that this requirement follows from Requirement 8 and the Agreement property of consensus.
Requirements 8 and 9 ensure that the validation of vote extensions will be deterministic at all
correct processes.
Requirements 8 and 9 protect against arbitrary vote extension data from Byzantine processes
similarly to Requirements 5 and 6 and proposed blocks.
Requirements 8 and 9 can be violated by a bug inducing non-determinism in
`VerifyVoteExtension`. In this case liveness can be compromised.
Extra care should be put in the implementation of `ExtendVote` and `VerifyVoteExtension` and,
as a general rule, `VerifyVoteExtension` _should_ always accept the vote extension.
* Requirement 10 [_all_, no-side-effects]: $p$'s calls to `RequestPrepareProposal`,
`RequestProcessProposal`, `RequestExtendVote`, and `RequestVerifyVoteExtension` at height $h$ do
not modify $s_{p,h-1}$.
* Requirement 11 [`ExtendVote`, `FinalizeBlock`, non-dependency]: for any correct process $p$,
and any vote extension $e$ that $p$ received at height $h$, the computation of
$s_{p,h}$ does not depend on $e$.
The call to correct process $p$'s `RequestFinalizeBlock` at height $h$, with block $v_{p,h}$
passed as parameter, creates state $s_{p,h}$.
Additionally,
* in next-block execution mode, $p$'s `FinalizeBlock` creates a set of transaction results $T_{p,h}$,
* in same-block execution mode, $p$'s `PrepareProposal` creates a set of transaction results $T_{p,h}$
if $p$ was the proposer of $v_{p,h}$, otherwise `FinalizeBlock` creates $T_{p,h}$.
>**TODO** I have left out all the "events" as they don't have any impact in safety or liveness
>(same for consensus params, and validator set)
* Requirement 12 [`FinalizeBlock`, determinism-1]: For any correct process $p$,
$s_{p,h}$ exclusively depends on $s_{p,h-1}$ and $v_{p,h}$.
* Requirement 13 [`FinalizeBlock`, determinism-2]: For any correct process $p$,
the contents of $T_{p,h}$ exclusively depend on $s_{p,h-1}$ and $v_{p,h}$.
Note that Requirements 12 and 13, combined with Agreement property of consensus ensure
the Application state evolves consistently at all correct processes.
Finally, notice that neither `PrepareProposal` nor `ExtendVote` have determinism-related
requirements associated.
Indeed, `PrepareProposal` is not required to be deterministic:
* $v'_p$ may depend on $v_p$ and $s_{p,h-1}$, but may also depend on other values or operations.
* $v_p = v_q \nRightarrow v'_p = v'_q$.
Likewise, `ExtendVote` can also be non-deterministic:
* $e^r_p$ may depend on $w^r_p$ and $s_{p,h-1}$, but may also depend on other values or operations.
* $w^r_p = w^r_q \nRightarrow e^r_p = e^r_q$

+ 401
- 0
spec/abci++/abci++_basic_concepts_002_draft.md View File

@ -0,0 +1,401 @@
---
order: 1
title: Basic concepts and definitions
---
# Basic concepts and definitions
## Connections
ABCI++ applications can run either within the _same_ process as the Tendermint
state-machine replication engine, or as a _separate_ process from the state-machine
replication engine. When run within the same process, Tendermint will call the ABCI++
application methods directly as Go method calls.
When Tendermint and the ABCI++ application are run as separate processes, Tendermint
opens four connections to the application for ABCI++ methods. The connections each
handle a subset of the ABCI++ method calls. These subsets are defined as follows:
### **Consensus** connection
* Driven by a consensus protocol and is responsible for block execution.
* Handles the `InitChain`, `PrepareProposal`, `ProcessProposal`, `ExtendVote`,
`VerifyVoteExtension`, and `FinalizeBlock` method calls.
### **Mempool** connection
* For validating new transactions, before they're shared or included in a block.
* Handles the `CheckTx` calls.
### **Info** connection
* For initialization and for queries from the user.
* Handles the `Info` and `Query` calls.
### **Snapshot** connection
* For serving and restoring [state sync snapshots](../abci/apps.md#state-sync).
* Handles the `ListSnapshots`, `LoadSnapshotChunk`, `OfferSnapshot`, and `ApplySnapshotChunk` calls.
Additionally, there is a `Flush` method that is called on every connection,
and an `Echo` method that is just for debugging.
>**TODO** Figure out what to do with this.
More details on managing state across connections can be found in the section on
[ABCI Applications](../abci/apps.md).
## Errors
The `Query`, and `CheckTx` methods include a `Code` field in their `Response*`.
The `Code` field is also included in type `TxResult`, used by
method `FinalizeBlock`'s `Response*`.
Field `Code` is meant to contain an application-specific response code.
A response code of `0` indicates no error. Any other response code
indicates to Tendermint that an error occurred.
These methods also return a `Codespace` string to Tendermint. This field is
used to disambiguate `Code` values returned by different domains of the
Application. The `Codespace` is a namespace for the `Code`.
Methods `Echo`, `Info`, and `InitChain` do not return errors.
An error in any of these methods represents a critical issue that Tendermint
has no reasonable way to handle. If there is an error in one
of these methods, the Application must crash to ensure that the error is safely
handled by an operator.
Method `FinalizeBlock` is a special case. It contains a number of
`Code` and `Codespace` fields as part of type `TxResult`. Each of
these codes reports errors related to the transaction it is attached to.
However, `FinalizeBlock` does not return errors at the top level, so the
same considerations on critical issues made for `Echo`, `Info`, and
`InitChain` also apply here.
The handling of non-zero response codes by Tendermint is described below
### `CheckTx`
The `CheckTx` ABCI++ method controls what transactions are considered for inclusion
in a block.
When Tendermint receives a `ResponseCheckTx` with a non-zero `Code`, the associated
transaction will not be added to Tendermint's mempool or it will be removed if
it is already included.
### `TxResult` (as part of `FinalizeBlock`)
The `TxResult` type delivers transactions from Tendermint to the Application.
When Tendermint receives a `ResponseFinalizeBlock` containing a `TxResult`
with a non-zero `Code`, the response code is logged.
The transaction was already included in a block, so the `Code` does not influence
Tendermint consensus.
### `Query`
The `Query` ABCI++ method queries the Application for information about application state.
When Tendermint receives a `ResponseQuery` with a non-zero `Code`, this code is
returned directly to the client that initiated the query.
## Events
Method `CheckTx` includes an `Events` field in its `Response*`.
Method `FinalizeBlock` includes an `Events` field at the top level in its
`Response*`, and one `tx_events` field per transaction included in the block.
Applications may respond to these ABCI++ methods with a set of events.
Events allow applications to associate metadata about ABCI++ method execution with the
transactions and blocks this metadata relates to.
Events returned via these ABCI++ methods do not impact Tendermint consensus in any way
and instead exist to power subscriptions and queries of Tendermint state.
An `Event` contains a `type` and a list of `EventAttributes`, which are key-value
string pairs denoting metadata about what happened during the method's (or transaction's)
execution. `Event` values can be used to index transactions and blocks according to what
happened during their execution.
Each event has a `type` which is meant to categorize the event for a particular
`Response*` or `Tx`. A `Response*` or `Tx` may contain multiple events with duplicate
`type` values, where each distinct entry is meant to categorize attributes for a
particular event. Every key and value in an event's attributes must be UTF-8
encoded strings along with the event type itself.
```protobuf
message Event {
string type = 1;
repeated EventAttribute attributes = 2;
}
```
The attributes of an `Event` consist of a `key`, a `value`, and an `index` flag. The
index flag notifies the Tendermint indexer to index the attribute. The value of
the `index` flag is non-deterministic and may vary across different nodes in the network.
```protobuf
message EventAttribute {
bytes key = 1;
bytes value = 2;
bool index = 3; // nondeterministic
}
```
Example:
```go
abci.ResponseCheckTx{
// ...
Events: []abci.Event{
{
Type: "validator.provisions",
Attributes: []abci.EventAttribute{
abci.EventAttribute{Key: []byte("address"), Value: []byte("..."), Index: true},
abci.EventAttribute{Key: []byte("amount"), Value: []byte("..."), Index: true},
abci.EventAttribute{Key: []byte("balance"), Value: []byte("..."), Index: true},
},
},
{
Type: "validator.provisions",
Attributes: []abci.EventAttribute{
abci.EventAttribute{Key: []byte("address"), Value: []byte("..."), Index: true},
abci.EventAttribute{Key: []byte("amount"), Value: []byte("..."), Index: false},
abci.EventAttribute{Key: []byte("balance"), Value: []byte("..."), Index: false},
},
},
{
Type: "validator.slashed",
Attributes: []abci.EventAttribute{
abci.EventAttribute{Key: []byte("address"), Value: []byte("..."), Index: false},
abci.EventAttribute{Key: []byte("amount"), Value: []byte("..."), Index: true},
abci.EventAttribute{Key: []byte("reason"), Value: []byte("..."), Index: true},
},
},
// ...
},
}
```
## EvidenceType
Tendermint's security model relies on the use of "evidence". Evidence is proof of
malicious behaviour by a network participant. It is the responsibility of Tendermint
to detect such malicious behaviour. When malicious behavior is detected, Tendermint
will gossip evidence of the behavior to other nodes and commit the evidence to
the chain once it is verified by all validators. This evidence will then be
passed on to the Application through ABCI++. It is the responsibility of the
Application to handle the evidence and exercise punishment.
EvidenceType has the following protobuf format:
```protobuf
enum EvidenceType {
UNKNOWN = 0;
DUPLICATE_VOTE = 1;
LIGHT_CLIENT_ATTACK = 2;
}
```
There are two forms of evidence: Duplicate Vote and Light Client Attack. More
information can be found in either [data structures](https://github.com/tendermint/spec/blob/master/spec/core/data_structures.md)
or [accountability](https://github.com/tendermint/spec/blob/master/spec/light-client/accountability/)
## Vote Extensions
According to the Tendermint algorithm, a proposed block needs at least a predefined
number of precommit votes in order to be decided. Tendermint gathers all the valid
precommit votes for the decided block that it receives before the block is decided,
and then includes these votes in the proposed block for the next height whenever
the local process is the proposer of the round.
When Tendermint's consensus is about to send a non-`nil` precommit message, it calls
method `ExtendVote`, which gives the Application the opportunity to include
non-deterministic data, opaque to Tendermint, that will be attached to the precommit
message. The data, called _vote extension_, will also be part of the proposed block
in the next height, along with the vote it is extending.
The vote extension data is split into two parts, one signed by Tendermint as part
of the vote data structure, and the other (optionally) signed by the Application.
The Application may also choose not to include any vote extension.
When another process receives a precommit message with a vote extension, it calls
method `VerifyVoteExtension` so that the Application can validate the data received.
If the validation fails, the precommit message will be deemed invalid and ignored
by Tendermint. This has negative impact on Tendermint's liveness, i.e., if repeatedly vote extensions by correct validators cannot be verified by correct validators, Tendermint may not be able to finalize a block even if sufficiently many (+2/3) of the validators send precommit votes for that block. Thus, `VerifyVoteExtension` should only be used with special care.
As a general rule, an Application that detects an invalid vote extension SHOULD
accept it in `ResponseVerifyVoteExtension` and ignore it in its own logic.
## Determinism
ABCI++ applications must implement deterministic finite-state machines to be
securely replicated by the Tendermint consensus engine. This means block execution
over the Consensus Connection must be strictly deterministic: given the same
ordered set of requests, all nodes will compute identical responses, for all
successive `FinalizeBlock` calls. This is critical, because the
responses are included in the header of the next block, either via a Merkle root
or directly, so all nodes must agree on exactly what they are.
For this reason, it is recommended that applications not be exposed to any
external user or process except via the ABCI connections to a consensus engine
like Tendermint Core. The Application must only change its state based on input
from block execution (`FinalizeBlock` calls), and not through
any other kind of request. This is the only way to ensure all nodes see the same
transactions and compute the same results.
Some Applications may choose to execute the blocks that are about to be proposed
(via `PrepareProposal`), or those that the Application is asked to validate
(via `Processproposal`). However the state changes caused by processing those
proposed blocks must never replace the previous state until `FinalizeBlock` confirms
the block decided.
Additionally, vote extensions or the validation thereof (via `ExtendVote` or
`VerifyVoteExtension`) must _never_ have side effects on the current state.
They can only be used when their data is included in a block.
If there is some non-determinism in the state machine, consensus will eventually
fail as nodes disagree over the correct values for the block header. The
non-determinism must be fixed and the nodes restarted.
Sources of non-determinism in applications may include:
* Hardware failures
* Cosmic rays, overheating, etc.
* Node-dependent state
* Random numbers
* Time
* Underspecification
* Library version changes
* Race conditions
* Floating point numbers
* JSON or protobuf serialization
* Iterating through hash-tables/maps/dictionaries
* External Sources
* Filesystem
* Network calls (eg. some external REST API service)
See [#56](https://github.com/tendermint/abci/issues/56) for original discussion.
Note that some methods (`Query, CheckTx, FinalizeBlock`) return
explicitly non-deterministic data in the form of `Info` and `Log` fields. The `Log` is
intended for the literal output from the Application's logger, while the
`Info` is any additional info that should be returned. These are the only fields
that are not included in block header computations, so we don't need agreement
on them. All other fields in the `Response*` must be strictly deterministic.
## Block Execution
The first time a new blockchain is started, Tendermint calls
`InitChain`. From then on, method `FinalizeBlock` is executed at the end of each
block, resulting in an updated Application state.
During consensus execution of a block height, before method `FinalizeBlock` is
called, methods `PrepareProposal`, `ProcessProposal`, `ExtendVote`, and
`VerifyVoteExtension` may be called a number of times.
See [Tendermint's expected behavior](abci++_tmint_expected_behavior_002_draft.md)
for details on the possible call sequences of these methods.
Method `PrepareProposal` is called every time Tendermint is about to send
a proposal message, but no previous proposal has been locked at Tendermint level.
Tendermint gathers outstanding transactions from the mempool
(see [PrepareProposal](#PrepareProposal)), generates a block header and uses
them to create a block to propose. Then, it calls `RequestPrepareProposal`
with the newly created proposal, called _raw proposal_. The Application can
make changes to the raw proposal, such as modifying transactions, and returns
the (potentially) modified proposal, called _prepared proposal_ in the
`Response*` call. The logic modifying the raw proposal can be non-deterministic.
When Tendermint receives a prepared proposal it uses method `ProcessProposal`
to inform the Application of the proposal just received. The Application cannot
modify the proposal at this point but can reject it if it realises it is invalid.
If that is the case, Tendermint will prevote `nil` on the proposal, which has
strong liveness implications for Tendermint. As a general rule, the Application
SHOULD accept a prepared proposal passed via `ProcessProposal`, even if a part of
the proposal is invalid (e.g., an invalid transaction); the Application can later
ignore the invalid part of the prepared proposal at block execution time.
Cryptographic commitments to the block and transaction results, via the corresponding
parameters in `FinalizeBlockResponse` are included in the header of the next block.
## Next-block execution and same-block execution
With ABCI++ predecessor, ABCI, the only moment when the Application had access to a
block was when it was decided. This led to a block execution model, called _next-block
execution_, where some fields hashed in a block header refer to the execution of the
previous block, namely:
* the merkle root of the Application's state
* the transaction results
* the consensus parameter updates
* the validator updates
With ABCI++, an Application may decide to keep using the next-block execution model;
however the new methods introduced, `PrepareProposal` and `ProcessProposal` allow
for a new execution model, called _same-block execution_. An Application implementing
this execution model, upon receiving a raw proposal via `RequestPrepareProposal`
and potentially modifying its transaction list,
fully executes the resulting prepared proposal as though it was the decided block.
The results of the block execution are used as follows:
* the Application keeps the events generated and provides them if `FinalizeBlock`
is finally called on this prepared proposal.
* the merkle root resulting from executing the prepared proposal is provided in
`ResponsePrepareProposal` and thus refers to the **current block**. Tendermint
will use it in the prepared proposal's header.
* likewise, the transaction results from executing the prepared proposal are
provided in `ResponsePrepareProposal` and refer to the transactions in the
**current block**. Tendermint will use them to calculate the results hash
in the prepared proposal's header.
* the consensus parameter updates and validator updates are also provided in
`ResponsePrepareProposal` and reflect the result of the prepared proposal's
execution. They come into force in height H+1 (as opposed to the H+2 rule
in next-block execution model).
If the Application decides to keep the next-block execution model, it will not
provide any data in `ResponsePrepareProposal`, other than an optionally modified
transaction list.
In the long term, the execution model will be set in a new boolean parameter
*same_block* in `ConsensusParams`.
It should **not** be changed once the blockchain has started, unless the Application
developers _really_ know what they are doing.
However, modifying `ConsensusParams` structure cannot be done lightly if we are to
preserve blockchain compatibility. Therefore we need an interim solution until
soft upgrades are specified and implemented in Tendermint. This somewhat _unsafe_
solution consists in Tendermint assuming same-block execution if the Application
fills the above mentioned fields in `ResponsePrepareProposal`.
## Tendermint timeouts in same-block execution
The new same-block execution mode requires the Application to fully execute the
prepared block at `PrepareProposal` time. This execution is synchronous, so
Tendermint cannot make progress until the Application returns from `PrepareProposal`.
This stands on Tendermint's critical path: if the Application takes a long time
executing the block, the default value of _TimeoutPropose_ might not be sufficient
to accomodate the long block execution time and non-proposer processes might time
out and prevote `nil`, thus starting a further round unnecessarily.
The Application is the best suited to provide a value for _TimeoutPropose_ so
that the block execution time upon `PrepareProposal` fits well in the propose
timeout interval.
Currently, the Application can override the value of _TimeoutPropose_ via the
`config.toml` file. In the future, `ConsensusParams` may have an extra field
with the current _TimeoutPropose_ value so that the Application has the possibility
to adapt it at every height.
## State Sync
State sync allows new nodes to rapidly bootstrap by discovering, fetching, and applying
state machine snapshots instead of replaying historical blocks. For more details, see the
[state sync section](../p2p/messages/state-sync.md).
New nodes will discover and request snapshots from other nodes in the P2P network.
A Tendermint node that receives a request for snapshots from a peer will call
`ListSnapshots` on its Application to retrieve any local state snapshots. After receiving
snapshots from peers, the new node will offer each snapshot received from a peer
to its local Application via the `OfferSnapshot` method.
Snapshots may be quite large and are thus broken into smaller "chunks" that can be
assembled into the whole snapshot. Once the Application accepts a snapshot and
begins restoring it, Tendermint will fetch snapshot "chunks" from existing nodes.
The node providing "chunks" will fetch them from its local Application using
the `LoadSnapshotChunk` method.
As the new node receives "chunks" it will apply them sequentially to the local
application with `ApplySnapshotChunk`. When all chunks have been applied, the
Application's `AppHash` is retrieved via an `Info` query. The `AppHash` is then
compared to the blockchain's `AppHash` which is verified via
[light client verification](../light-client/verification/README.md).

+ 878
- 0
spec/abci++/abci++_methods_002_draft.md View File

@ -0,0 +1,878 @@
---
order: 2
title: Methods
---
# Methods
## Methods existing in ABCI
### Echo
* **Request**:
* `Message (string)`: A string to echo back
* **Response**:
* `Message (string)`: The input string
* **Usage**:
* Echo a string to test an abci client/server implementation
### Flush
* **Usage**:
* Signals that messages queued on the client should be flushed to
the server. It is called periodically by the client
implementation to ensure asynchronous requests are actually
sent, and is called immediately to make a synchronous request,
which returns when the Flush response comes back.
### Info
* **Request**:
| Name | Type | Description | Field Number |
|---------------|--------|------------------------------------------|--------------|
| version | string | The Tendermint software semantic version | 1 |
| block_version | uint64 | The Tendermint Block Protocol version | 2 |
| p2p_version | uint64 | The Tendermint P2P Protocol version | 3 |
| abci_version | string | The Tendermint ABCI semantic version | 4 |
* **Response**:
| Name | Type | Description | Field Number |
|---------------------|--------|--------------------------------------------------|--------------|
| data | string | Some arbitrary information | 1 |
| version | string | The application software semantic version | 2 |
| app_version | uint64 | The application protocol version | 3 |
| last_block_height | int64 | Latest block for which the app has called Commit | 4 |
| last_block_app_hash | bytes | Latest result of Commit | 5 |
* **Usage**:
* Return information about the application state.
* Used to sync Tendermint with the application during a handshake
that happens on startup.
* The returned `app_version` will be included in the Header of every block.
* Tendermint expects `last_block_app_hash` and `last_block_height` to
be updated during `Commit`, ensuring that `Commit` is never
called twice for the same block height.
> Note: Semantic version is a reference to [semantic versioning](https://semver.org/). Semantic versions in info will be displayed as X.X.x.
### InitChain
* **Request**:
| Name | Type | Description | Field Number |
|------------------|--------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------|--------------|
| time | [google.protobuf.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp) | Genesis time | 1 |
| chain_id | string | ID of the blockchain. | 2 |
| consensus_params | [ConsensusParams](#consensusparams) | Initial consensus-critical parameters. | 3 |
| validators | repeated [ValidatorUpdate](#validatorupdate) | Initial genesis validators, sorted by voting power. | 4 |
| app_state_bytes | bytes | Serialized initial application state. JSON bytes. | 5 |
| initial_height | int64 | Height of the initial block (typically `1`). | 6 |
* **Response**:
| Name | Type | Description | Field Number |
|------------------|----------------------------------------------|-------------------------------------------------|--------------|
| consensus_params | [ConsensusParams](#consensusparams) | Initial consensus-critical parameters (optional) | 1 |
| validators | repeated [ValidatorUpdate](#validatorupdate) | Initial validator set (optional). | 2 |
| app_hash | bytes | Initial application hash. | 3 |
* **Usage**:
* Called once upon genesis.
* If ResponseInitChain.Validators is empty, the initial validator set will be the RequestInitChain.Validators
* If ResponseInitChain.Validators is not empty, it will be the initial
validator set (regardless of what is in RequestInitChain.Validators).
* This allows the app to decide if it wants to accept the initial validator
set proposed by tendermint (ie. in the genesis file), or if it wants to use
a different one (perhaps computed based on some application specific
information in the genesis file).
### Query
* **Request**:
| Name | Type | Description | Field Number |
|--------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
| data | bytes | Raw query bytes. Can be used with or in lieu of Path. | 1 |
| path | string | Path field of the request URI. Can be used with or in lieu of `data`. Apps MUST interpret `/store` as a query by key on the underlying store. The key SHOULD be specified in the `data` field. Apps SHOULD allow queries over specific types like `/accounts/...` or `/votes/...` | 2 |
| height | int64 | The block height for which you want the query (default=0 returns data for the latest committed block). Note that this is the height of the block containing the application's Merkle root hash, which represents the state as it was after committing the block at Height-1 | 3 |
| prove | bool | Return Merkle proof with response if possible | 4 |
* **Response**:
| Name | Type | Description | Field Number |
|-----------|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
| code | uint32 | Response code. | 1 |
| log | string | The output of the application's logger. **May be non-deterministic.** | 3 |
| info | string | Additional information. **May be non-deterministic.** | 4 |
| index | int64 | The index of the key in the tree. | 5 |
| key | bytes | The key of the matching data. | 6 |
| value | bytes | The value of the matching data. | 7 |
| proof_ops | [ProofOps](#proofops) | Serialized proof for the value data, if requested, to be verified against the `app_hash` for the given Height. | 8 |
| height | int64 | The block height from which data was derived. Note that this is the height of the block containing the application's Merkle root hash, which represents the state as it was after committing the block at Height-1 | 9 |
| codespace | string | Namespace for the `code`. | 10 |
* **Usage**:
* Query for data from the application at current or past height.
* Optionally return Merkle proof.
* Merkle proof includes self-describing `type` field to support many types
of Merkle trees and encoding formats.
### CheckTx
* **Request**:
| Name | Type | Description | Field Number |
|------|-------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
| tx | bytes | The request transaction bytes | 1 |
| type | CheckTxType | One of `CheckTx_New` or `CheckTx_Recheck`. `CheckTx_New` is the default and means that a full check of the tranasaction is required. `CheckTx_Recheck` types are used when the mempool is initiating a normal recheck of a transaction. | 2 |
* **Response**:
| Name | Type | Description | Field Number |
|------------|-------------------------------------------------------------|-----------------------------------------------------------------------|--------------|
| code | uint32 | Response code. | 1 |
| data | bytes | Result bytes, if any. | 2 |
| log | string | The output of the application's logger. **May be non-deterministic.** | 3 |
| info | string | Additional information. **May be non-deterministic.** | 4 |
| gas_wanted | int64 | Amount of gas requested for transaction. | 5 |
| gas_used | int64 | Amount of gas consumed by transaction. | 6 |
| events | repeated [Event](abci++_basic_concepts_002_draft.md#events) | Type & Key-Value events for indexing transactions (eg. by account). | 7 |
| codespace | string | Namespace for the `code`. | 8 |
| sender | string | The transaction's sender (e.g. the signer) | 9 |
| priority | int64 | The transaction's priority (for mempool ordering) | 10 |
* **Usage**:
* Technically optional - not involved in processing blocks.
* Guardian of the mempool: every node runs `CheckTx` before letting a
transaction into its local mempool.
* The transaction may come from an external user or another node
* `CheckTx` validates the transaction against the current state of the application,
for example, checking signatures and account balances, but does not apply any
of the state changes described in the transaction.
not running code in a virtual machine.
* Transactions where `ResponseCheckTx.Code != 0` will be rejected - they will not be broadcast to
other nodes or included in a proposal block.
* Tendermint attributes no other value to the response code
### ListSnapshots
* **Request**:
| Name | Type | Description | Field Number |
|--------|-------|------------------------------------|--------------|
Empty request asking the application for a list of snapshots.
* **Response**:
| Name | Type | Description | Field Number |
|-----------|--------------------------------|--------------------------------|--------------|
| snapshots | repeated [Snapshot](#snapshot) | List of local state snapshots. | 1 |
* **Usage**:
* Used during state sync to discover available snapshots on peers.
* See `Snapshot` data type for details.
### LoadSnapshotChunk
* **Request**:
| Name | Type | Description | Field Number |
|--------|--------|-----------------------------------------------------------------------|--------------|
| height | uint64 | The height of the snapshot the chunk belongs to. | 1 |
| format | uint32 | The application-specific format of the snapshot the chunk belongs to. | 2 |
| chunk | uint32 | The chunk index, starting from `0` for the initial chunk. | 3 |
* **Response**:
| Name | Type | Description | Field Number |
|-------|-------|-------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
| chunk | bytes | The binary chunk contents, in an arbitray format. Chunk messages cannot be larger than 16 MB _including metadata_, so 10 MB is a good starting point. | 1 |
* **Usage**:
* Used during state sync to retrieve snapshot chunks from peers.
### OfferSnapshot
* **Request**:
| Name | Type | Description | Field Number |
|----------|-----------------------|--------------------------------------------------------------------------|--------------|
| snapshot | [Snapshot](#snapshot) | The snapshot offered for restoration. | 1 |
| app_hash | bytes | The light client-verified app hash for this height, from the blockchain. | 2 |
* **Response**:
| Name | Type | Description | Field Number |
|--------|-------------------|-----------------------------------|--------------|
| result | [Result](#result) | The result of the snapshot offer. | 1 |
#### Result
```protobuf
enum Result {
UNKNOWN = 0; // Unknown result, abort all snapshot restoration
ACCEPT = 1; // Snapshot is accepted, start applying chunks.
ABORT = 2; // Abort snapshot restoration, and don't try any other snapshots.
REJECT = 3; // Reject this specific snapshot, try others.
REJECT_FORMAT = 4; // Reject all snapshots with this `format`, try others.
REJECT_SENDER = 5; // Reject all snapshots from all senders of this snapshot, try others.
}
```
* **Usage**:
* `OfferSnapshot` is called when bootstrapping a node using state sync. The application may
accept or reject snapshots as appropriate. Upon accepting, Tendermint will retrieve and
apply snapshot chunks via `ApplySnapshotChunk`. The application may also choose to reject a
snapshot in the chunk response, in which case it should be prepared to accept further
`OfferSnapshot` calls.
* Only `AppHash` can be trusted, as it has been verified by the light client. Any other data
can be spoofed by adversaries, so applications should employ additional verification schemes
to avoid denial-of-service attacks. The verified `AppHash` is automatically checked against
the restored application at the end of snapshot restoration.
* For more information, see the `Snapshot` data type or the [state sync section](../p2p/messages/state-sync.md).
### ApplySnapshotChunk
* **Request**:
| Name | Type | Description | Field Number |
|--------|--------|-----------------------------------------------------------------------------|--------------|
| index | uint32 | The chunk index, starting from `0`. Tendermint applies chunks sequentially. | 1 |
| chunk | bytes | The binary chunk contents, as returned by `LoadSnapshotChunk`. | 2 |
| sender | string | The P2P ID of the node who sent this chunk. | 3 |
* **Response**:
| Name | Type | Description | Field Number |
|----------------|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
| result | Result (see below) | The result of applying this chunk. | 1 |
| refetch_chunks | repeated uint32 | Refetch and reapply the given chunks, regardless of `result`. Only the listed chunks will be refetched, and reapplied in sequential order. | 2 |
| reject_senders | repeated string | Reject the given P2P senders, regardless of `Result`. Any chunks already applied will not be refetched unless explicitly requested, but queued chunks from these senders will be discarded, and new chunks or other snapshots rejected. | 3 |
```proto
enum Result {
UNKNOWN = 0; // Unknown result, abort all snapshot restoration
ACCEPT = 1; // The chunk was accepted.
ABORT = 2; // Abort snapshot restoration, and don't try any other snapshots.
RETRY = 3; // Reapply this chunk, combine with `RefetchChunks` and `RejectSenders` as appropriate.
RETRY_SNAPSHOT = 4; // Restart this snapshot from `OfferSnapshot`, reusing chunks unless instructed otherwise.
REJECT_SNAPSHOT = 5; // Reject this snapshot, try a different one.
}
```
* **Usage**:
* The application can choose to refetch chunks and/or ban P2P peers as appropriate. Tendermint
will not do this unless instructed by the application.
* The application may want to verify each chunk, e.g. by attaching chunk hashes in
`Snapshot.Metadata` and/or incrementally verifying contents against `AppHash`.
* When all chunks have been accepted, Tendermint will make an ABCI `Info` call to verify that
`LastBlockAppHash` and `LastBlockHeight` matches the expected values, and record the
`AppVersion` in the node state. It then switches to fast sync or consensus and joins the
network.
* If Tendermint is unable to retrieve the next chunk after some time (e.g. because no suitable
peers are available), it will reject the snapshot and try a different one via `OfferSnapshot`.
The application should be prepared to reset and accept it or abort as appropriate.
## New methods introduced in ABCI++
### PrepareProposal
#### Parameters and Types
* **Request**:
| Name | Type | Description | Field Number |
|-------------------------|---------------------------------------------|------------------------------------------------------------------------------------------------------------------|--------------|
| hash | bytes | The block header's hash of the block to propose. Present for convenience (can be derived from the block header). | 1 |
| header | [Header](../core/data_structures.md#header) | The header of the block to propose. | 2 |
| txs | repeated bytes | Preliminary list of transactions that have been picked as part of the block to propose. | 3 |
| last_commit_info | [LastCommitInfo](#lastcommitinfo) | Info about the last commit, including the round, the validator list, and which ones signed the last block. | 4 |
| byzantine_validators | repeated [Evidence](#evidence) | List of evidence of validators that acted maliciously. | 5 |
| max_tx_bytes | int64 | Currently configured maximum size in bytes taken by the modified transactions. | 6 |
>**TODO**: Add the changes needed in LastCommitInfo for vote extensions
>**TODO**: DISCUSS: We need to make clear whether a proposer is also running the logic of a non-proposer node (in particular "ProcessProposal")
From the App's perspective, they'll probably skip ProcessProposal
* **Response**:
| Name | Type | Description | Field Number |
|-------------------------|--------------------------------------------------|---------------------------------------------------------------------------------------------|--------------|
| modified_tx | bool | The Application sets it to true to denote it made changes to transactions | 1 |
| tx_records | repeated [TxRecord](#txrecord) | Possibly modified list of transactions that have been picked as part of the proposed block. | 2 |
| app_hash | bytes | The Merkle root hash of the application state. | 3 |
| tx_results | repeated [ExecTxResult](#txresult) | List of structures containing the data resulting from executing the transactions | 4 |
| validator_updates | repeated [ValidatorUpdate](#validatorupdate) | Changes to validator set (set voting power to 0 to remove). | 5 |
| consensus_param_updates | [ConsensusParams](#consensusparams) | Changes to consensus-critical gas, size, and other parameters. | 6 |
| app_signed_updates | repeated bytes | Optional changes to the *app_signed* part of vote extensions. | 7 |
* **Usage**:
* Contains a preliminary block to be proposed, called _raw block_, which the Application can modify.
* The first five parameters of `RequestPrepareProposal` are the same as `RequestProcessProposal`
and `RequestFinalizeBlock`.
* The header contains the height, timestamp, and more - it exactly matches the
Tendermint block header.
* The Application can modify the transactions received in `RequestPrepareProposal` before sending
them in `ResponsePrepareProposal`. In that case, `ResponsePrepareProposal.modified_tx` is set to true.
* If `ResponsePrepareProposal.modified_tx` is false, then Tendermint will ignore the contents of
`ResponsePrepareProposal.tx_records`.
* If the Application modifies the transactions, the modified transactions MUST NOT exceed the configured maximum size,
contained in `RequestPrepareProposal.max_tx_bytes`.
* If the Application modifies the *app_signed* part of vote extensions via `ResponsePrepareProposal.app_signed_updates`,
the new total size of those extensions cannot exceed their initial size.
* The Application may choose to not modify the *app_signed* part of vote extensions by leaving parameter
`ResponsePrepareProposal.app_signed_updates` empty.
* In same-block execution mode, the Application must provide values for `ResponsePrepareProposal.app_hash`,
`ResponsePrepareProposal.tx_results`, `ResponsePrepareProposal.validator_updates`, and
`ResponsePrepareProposal.consensus_param_updates`, as a result of fully executing the block.
* The values for `ResponsePrepareProposal.validator_updates`, or
`ResponsePrepareProposal.consensus_param_updates` may be empty. In this case, Tendermint will keep
the current values.
* `ResponsePrepareProposal.validator_updates`, triggered by block `H`, affect validation
for blocks `H+1`, and `H+2`. Heights following a validator update are affected in the following way:
* `H`: `NextValidatorsHash` includes the new `validator_updates` value.
* `H+1`: The validator set change takes effect and `ValidatorsHash` is updated.
* `H+2`: `last_commit_info` is changed to include the altered validator set.
* `ResponseFinalizeBlock.consensus_param_updates` returned for block `H` apply to the consensus
params for block `H+1` even if the change is agreed in block `H`.
For more information on the consensus parameters,
see the [application spec entry on consensus parameters](../abci/apps.md#consensus-parameters).
* It is the responsibility of the Application to set the right value for _TimeoutPropose_ so that
the (synchronous) execution of the block does not cause other processes to prevote `nil` because
their propose timeout goes off.
* In next-block execution mode, Tendermint will ignore parameters `ResponsePrepareProposal.tx_results`,
`ResponsePrepareProposal.validator_updates`, and `ResponsePrepareProposal.consensus_param_updates`.
* As a result of executing the prepared proposal, the Application may produce header events or transaction events.
The Application must keep those events until a block is decided and then pass them on to Tendermint via
`ResponseFinalizeBlock`.
* Likewise, in next-block execution mode, the Application must keep all responses to executing transactions
until it can call `ResponseFinalizeBlock`.
* The Application can change the transaction list via `ResponsePrepareProposal.tx_records`.
See [TxRecord](#txrecord) for further information on how to use it. Some notes:
* To remove a transaction from the proposed block the Application _marks_ the transaction as
"REMOVE". It does not remove it from the list. The transaction will also be removed from the mempool.
* Removing a transaction from the list means it is too early to propose that transaction,
so it will be excluded from the proposal but will stay in the mempool for later proposals.
The Application should be extra-careful, as abusing this feature may cause transactions to
stay forever in the mempool.
* The `new_hashes` field, besides helping with mempool maintenance, helps Tendermint handle
queries such as "what happened with this Tx?", by answering "it was modified into these ones".
* The Application _can_ reorder the transactions in the list.
* As a sanity check, Tendermint will check the returned parameters for validity if the Application modified them.
In particular, `ResponsePrepareProposal.tx_records` will be deemed invalid if
* There is a duplicate transaction in the list.
* The `new_hashes` field contains a dangling reference to a non-existing transaction.
* A new or modified transaction is marked as "TXUNMODIFIED" or "TXREMOVED".
* An unmodified transaction is marked as "TXADDED".
* A transaction is marked as "TXUNKNOWN".
* If Tendermint's sanity checks on the parameters of `ResponsePrepareProposal` fails, then it will drop the proposal
and proceed to the next round (thus simulating a network loss/delay of the proposal).
* **TODO**: [From discussion with William] Another possibility here is to panic. What do folks think we should do here?
* The implementation of `PrepareProposal` can be non-deterministic.
#### When does Tendermint call it?
When a validator _p_ enters Tendermint consensus round _r_, height _h_, in which _p_ is the proposer,
and _p_'s _validValue_ is `nil`:
1. _p_'s Tendermint collects outstanding transactions from the mempool
* The transactions will be collected in order of priority
* Let $C$ the list of currently collected transactions
* The collection stops when any of the following conditions are met
* the mempool is empty
* the total size of transactions $\in C$ is greater than or equal to `consensusParams.block.max_bytes`
* the sum of `GasWanted` field of transactions $\in C$ is greater than or equal to
`consensusParams.block.max_gas`
* _p_'s Tendermint creates a block header.
2. _p_'s Tendermint calls `RequestPrepareProposal` with the newly generated block.
The call is synchronous: Tendermint's execution will block until the Application returns from the call.
3. The Application checks the block (header, transactions, commit info, evidences). Besides,
* in same-block execution mode, the Application can (and should) provide `ResponsePrepareProposal.app_hash`,
`ResponsePrepareProposal.validator_updates`, or
`ResponsePrepareProposal.consensus_param_updates`.
* in "next-block execution" mode, _p_'s Tendermint will ignore the values for `ResponsePrepareProposal.app_hash`,
`ResponsePrepareProposal.validator_updates`, and `ResponsePrepareProposal.consensus_param_updates`.
* in both modes, the Application can manipulate transactions
* leave transactions untouched - `TxAction = UNMODIFIED`
* add new transactions (not previously in the mempool) - `TxAction = ADDED`
* remove transactions (invalid) from the proposal and from the mempool - `TxAction = REMOVED`
* remove transactions from the proposal but not from the mempool (effectively _delaying_ them) - the
Application removes the transaction from the list
* modify transactions (e.g. aggregate them) - `TxAction = ADDED` followed by `TxAction = REMOVED`
* reorder transactions - the Application reorders transactions in the list
4. If the block is modified, the Application sets `ResponsePrepareProposal.modified` to true,
and includes the modified block in the return parameters (see the rules in section _Usage_).
The Application returns from the call.
5. _p_'s Tendermint uses the (possibly) modified block as _p_'s proposal in round _r_, height _h_.
Note that, if _p_ has a non-`nil` _validValue_, Tendermint will use it as proposal and will not call `RequestPrepareProposal`.
### ProcessProposal
#### Parameters and Types
* **Request**:
| Name | Type | Description | Field Number |
|----------------------|---------------------------------------------|----------------------------------------------------------------------------------------------------------------|--------------|
| hash | bytes | The block header's hash of the proposed block. Present for convenience (can be derived from the block header). | 1 |
| header | [Header](../core/data_structures.md#header) | The proposed block's header. | 2 |
| txs | repeated bytes | List of transactions that have been picked as part of the proposed block. | 3 |
| last_commit_info | [LastCommitInfo](#lastcommitinfo) | Info about the last commit, including the round , the validator list, and which ones signed the last block. | 4 |
| byzantine_validators | repeated [Evidence](#evidence) | List of evidence of validators that acted maliciously. | 5 |
* **Response**:
| Name | Type | Description | Field Number |
|-------------------------|--------------------------------------------------|-----------------------------------------------------------------------------------|--------------|
| accept | bool | If false, the received block failed verification. | 1 |
| app_hash | bytes | The Merkle root hash of the application state. | 2 |
| tx_results | repeated [ExecTxResult](#txresult) | List of structures containing the data resulting from executing the transactions. | 3 |
| validator_updates | repeated [ValidatorUpdate](#validatorupdate) | Changes to validator set (set voting power to 0 to remove). | 4 |
| consensus_param_updates | [ConsensusParams](#consensusparams) | Changes to consensus-critical gas, size, and other parameters. | 5 |
* **Usage**:
* Contains a full proposed block.
* The parameters and types of `RequestProcessProposal` are the same as `RequestPrepareProposal`
and `RequestFinalizeBlock`.
* The Application may fully execute the block as though it was handling `RequestFinalizeBlock`.
However, any resulting state changes must be kept as _canditade state_,
and the Application should be ready to backtrack/discard it in case the decided block is different.
* The header exactly matches the Tendermint header of the proposed block.
* In next-block execution mode, the header hashes _AppHash_, _LastResultHash_, _ValidatorHash_,
and _ConsensusHash_ refer to the **last committed block** (data was provided by the last call to
`ResponseFinalizeBlock`).
* In same-block execution mode, the header hashes _AppHash_, _LastResultHash_, _ValidatorHash_,
and _ConsensusHash_ refer to the **same** block being passed in the `Request*` call to this
method (data was provided by the call to `ResponsePrepareProposal` at the current height that
resulted in the block being passed in the `Request*` call to this method)
* If `ResponseProcessProposal.accept` is _false_, Tendermint assumes the proposal received
is not valid.
* In same-block execution mode, the Application is required to fully execute the block and provide values
for parameters `ResponseProcessProposal.app_hash`, `ResponseProcessProposal.tx_results`,
`ResponseProcessProposal.validator_updates`, and `ResponseProcessProposal.consensus_param_updates`,
so that Tendermint can then verify the hashes in the block's header are correct.
If the hashes mismatch, Tendermint will reject the block even if `ResponseProcessProposal.accept`
was set to _true_.
* In next-block execution mode, the Application should *not* provide values for parameters
`ResponseProcessProposal.app_hash`, `ResponseProcessProposal.tx_results`,
`ResponseProcessProposal.validator_updates`, and `ResponseProcessProposal.consensus_param_updates`.
* The implementation of `ProcessProposal` MUST be deterministic. Moreover, the value of
`ResponseProcessProposal.accept` MUST **exclusively** depend on the parameters passed in
the call to `RequestProcessProposal`, and the last committed Application state
(see [Requirements](abci++_app_requirements_002_draft.md) section).
* Moreover, application implementors SHOULD always set `ResponseProcessProposal.accept` to _true_,
unless they _really_ know what the potential liveness implications of returning _false_ are.
>**TODO**: should `ResponseProcessProposal.accept` be of type `Result` rather than `bool`? (so we are able to extend the possible values in the future?)
#### When does Tendermint call it?
When a validator _p_ enters Tendermint consensus round _r_, height _h_, in which _q_ is the proposer (possibly _p_ = _q_):
1. _p_ sets up timer `ProposeTimeout`.
2. If _p_ is the proposer, _p_ executes steps 1-6 in [PrepareProposal](#prepareproposal).
3. Upon reception of Proposal message (which contains the header) for round _r_, height _h_ from _q_, _p_'s Tendermint verifies the block header.
4. Upon reception of Proposal message, along with all the block parts, for round _r_, height _h_ from _q_, _p_'s Tendermint follows its algorithm
to check whether it should prevote for the block just received, or `nil`
5. If Tendermint should prevote for the block just received
1. Tendermint calls `RequestProcessProposal` with the block. The call is synchronous.
2. The Application checks/processes the proposed block, which is read-only, and returns true (_accept_) or false (_reject_) in `ResponseProcessProposal.accept`.
* The Application, depending on its needs, may call `ResponseProcessProposal`
* either after it has completely processed the block (the simpler case),
* or immediately (after doing some basic checks), and process the block asynchronously. In this case the Application will
not be able to reject the block, or force prevote/precommit `nil` afterwards.
3. If the returned value is
* _accept_, Tendermint prevotes on this proposal for round _r_, height _h_.
* _reject_, Tendermint prevotes `nil`.
### ExtendVote
#### Parameters and Types
* **Request**:
| Name | Type | Description | Field Number |
|--------|-------|-------------------------------------------------------------------------------|--------------|
| hash | bytes | The header hash of the proposed block that the vote extension is to refer to. | 1 |
| height | int64 | Height of the proposed block (for sanity check). | 2 |
* **Response**:
| Name | Type | Description | Field Number |
|-------------------|-------|---------------------------------------------------------------------|--------------|
| app_signed | bytes | Optional information signed by the Application (not by Tendermint). | 1 |
| tendermint_signed | bytes | Optional information signed by Tendermint. | 2 |
* **Usage**:
* Both `ResponseExtendVote.app_signed` and `ResponseExtendVote.tendermint_signed` are optional information that will
be attached to the Precommit message.
* `RequestExtendVote.hash` corresponds to the hash of a proposed block that was made available to the application
in a previous call to `ProcessProposal` or `PrepareProposal` for the current height.
* `ResponseExtendVote.app_signed` and `ResponseExtendVote.tendermint_signed` will always be attached to a non-`nil`
Precommit message. If Tendermint is to precommit `nil`, it will not call `RequestExtendVote`.
* The Application logic that creates the extension can be non-deterministic.
#### When does Tendermint call it?
When a validator _p_ is in Tendermint consensus state _prevote_ of round _r_, height _h_, in which _q_ is the proposer; and _p_ has received
* the Proposal message _v_ for round _r_, height _h_, along with all the block parts, from _q_,
* `Prevote` messages from _2f + 1_ validators' voting power for round _r_, height _h_, prevoting for the same block _id(v)_,
then _p_'s Tendermint locks _v_ and sends a Precommit message in the following way
1. _p_'s Tendermint sets _lockedValue_ and _validValue_ to _v_, and sets _lockedRound_ and _validRound_ to _r_
2. _p_'s Tendermint calls `RequestExtendVote` with _id(v)_ (`RequestExtendVote.hash`). The call is synchronous.
3. The Application returns an array of bytes, `ResponseExtendVote.extension`, which is not interpreted by Tendermint.
4. _p_'s Tendermint includes `ResponseExtendVote.extension` as a new field in the Precommit message.
5. _p_'s Tendermint signs and broadcasts the Precommit message.
In the cases when _p_'s Tendermint is to broadcast `precommit nil` messages (either _2f+1_ `prevote nil` messages received, or _timeoutPrevote_ triggered), _p_'s Tendermint does **not** call `RequestExtendVote` and will include an empty byte array as vote extension in the `precommit nil` message.
### VerifyVoteExtension
#### Parameters and Types
* **Request**:
| Name | Type | Description | Field Number |
|-------------------|-------|------------------------------------------------------------------------------------------|--------------|
| app_signed | bytes | Optional information signed by the Application (not by Tendermint). | 1 |
| tendermint_signed | bytes | Optional information signed by Tendermint. | 2 |
| hash | bytes | The header hash of the propsed block that the vote extension refers to. | 3 |
| validator_address | bytes | [Address](../core/data_structures.md#address) of the validator that signed the extension | 4 |
| height | int64 | Height of the block (for sanity check). | 5 |
* **Response**:
| Name | Type | Description | Field Number |
|--------|------|-------------------------------------------------------|--------------|
| accept | bool | If false, Application is rejecting the vote extension | 1 |
* **Usage**:
* If `ResponseVerifyVoteExtension.accept` is _false_, Tendermint will reject the whole received vote.
See the [Requirements](abci++_app_requirements_002_draft.md) section to understand the potential
liveness implications of this.
* The implementation of `VerifyVoteExtension` MUST be deterministic. Moreover, the value of
`ResponseVerifyVoteExtension.accept` MUST **exclusively** depend on the parameters passed in
the call to `RequestVerifyVoteExtension`, and the last committed Application state
(see [Requirements](abci++_app_requirements_002_draft.md) section).
* Moreover, application implementors SHOULD always set `ResponseVerifyVoteExtension.accept` to _true_,
unless they _really_ know what the potential liveness implications of returning _false_ are.
#### When does Tendermint call it?
When a validator _p_ is in Tendermint consensus round _r_, height _h_, state _prevote_ (**TODO** discuss: I think I must remove the state
from this condition, but not sure), and _p_ receives a Precommit message for round _r_, height _h_ from _q_:
1. _p_'s Tendermint calls `RequestVerifyVoteExtension`.
2. The Application returns _accept_ or _reject_ via `ResponseVerifyVoteExtension.accept`.
3. If the Application returns
* _accept_, _p_'s Tendermint will keep the received vote, together with its corresponding
vote extension in its internal data structures. It will be used to:
* calculate field _LastCommitHash_ in the header of the block proposed for height _h + 1_
(in the rounds where _p_ will be proposer).
* populate _LastCommitInfo_ in calls to `RequestPrepareProposal`, `RequestProcessProposal`,
and `RequestFinalizeBlock` in height _h + 1_.
* _reject_, _p_'s Tendermint will deem the Precommit message invalid and discard it.
### FinalizeBlock
#### Parameters and Types
* **Request**:
| Name | Type | Description | Field Number |
|----------------------|---------------------------------------------|-------------------------------------------------------------------------------------------------------------------|--------------|
| hash | bytes | The block header's hash. Present for convenience (can be derived from the block header). | 1 |
| header | [Header](../core/data_structures.md#header) | The block header. | 2 |
| txs | repeated bytes | List of transactions committed as part of the block. | 3 |
| last_commit_info | [LastCommitInfo](#lastcommitinfo) | Info about the last commit, including the round, and the list of validators and which ones signed the last block. | 4 |
| byzantine_validators | repeated [Evidence](#evidence) | List of evidence of validators that acted maliciously. | 5 |
* **Response**:
| Name | Type | Description | Field Number |
|-------------------------|-------------------------------------------------------------|----------------------------------------------------------------------------------|--------------|
| block_events | repeated [Event](abci++_basic_concepts_002_draft.md#events) | Type & Key-Value events for indexing | 1 |
| tx_results | repeated [ExecTxResult](#txresult) | List of structures containing the data resulting from executing the transactions | 2 |
| validator_updates | repeated [ValidatorUpdate](#validatorupdate) | Changes to validator set (set voting power to 0 to remove). | 3 |
| consensus_param_updates | [ConsensusParams](#consensusparams) | Changes to consensus-critical gas, size, and other parameters. | 4 |
| app_hash | bytes | The Merkle root hash of the application state. | 5 |
| retain_height | int64 | Blocks below this height may be removed. Defaults to `0` (retain all). | 6 |
* **Usage**:
* Contains a newly decided block.
* This method is equivalent to the call sequence `BeginBlock`, [`DeliverTx`],
`EndBlock`, `Commit` in the previous version of ABCI.
* The header exactly matches the Tendermint header of the proposed block.
* The Application can use `RequestFinalizeBlock.last_commit_info` and `RequestFinalizeBlock.byzantine_validators`
to determine rewards and punishments for the validators.
* The application must execute the transactions in full, in the order they appear in `RequestFinalizeBlock.txs`,
before returning control to Tendermint. Alternatively, it can commit the candidate state corresponding to the same block
previously executed via `PrepareProposal` or `ProcessProposal`.
* `ResponseFinalizeBlock.tx_results[i].Code == 0` only if the _i_-th transaction is fully valid.
* In next-block execution mode, the Application must provide values for `ResponseFinalizeBlock.app_hash`,
`ResponseFinalizeBlock.tx_results`, `ResponseFinalizeBlock.validator_updates`, and
`ResponseFinalizeBlock.consensus_param_updates` as a result of executing the block.
* The values for `ResponseFinalizeBlock.validator_updates`, or
`ResponseFinalizeBlock.consensus_param_updates` may be empty. In this case, Tendermint will keep
the current values.
* `ResponseFinalizeBlock.validator_updates`, triggered by block `H`, affect validation
for blocks `H+1`, `H+2`, and `H+3`. Heights following a validator update are affected in the following way:
- Height `H+1`: `NextValidatorsHash` includes the new `validator_updates` value.
- Height `H+2`: The validator set change takes effect and `ValidatorsHash` is updated.
- Height `H+3`: `last_commit_info` is changed to include the altered validator set.
* `ResponseFinalizeBlock.consensus_param_updates` returned for block `H` apply to the consensus
params for block `H+1`. For more information on the consensus parameters,
see the [application spec entry on consensus parameters](../abci/apps.md#consensus-parameters).
* In same-block execution mode, Tendermint will log an error and ignore values for
`ResponseFinalizeBlock.app_hash`, `ResponseFinalizeBlock.tx_results`, `ResponseFinalizeBlock.validator_updates`,
and `ResponsePrepareProposal.consensus_param_updates`, as those must have been provided by `PrepareProposal`.
* Application is expected to persist its state at the end of this call, before calling `ResponseFinalizeBlock`.
* `ResponseFinalizeBlock.app_hash` contains an (optional) Merkle root hash of the application state.
* `ResponseFinalizeBlock.app_hash` is included
* [in next-block execution mode] as the `Header.AppHash` in the next block.
* [in same-block execution mode] as the `Header.AppHash` in the current block. In this case,
`PrepareProposal` is required to fully execute the block and set the App hash before
returning the proposed block to Tendermint.
* `ResponseFinalizeBlock.app_hash` may also be empty or hard-coded, but MUST be
**deterministic** - it must not be a function of anything that did not come from the parameters
of `RequestFinalizeBlock` and the previous committed state.
* Later calls to `Query` can return proofs about the application state anchored
in this Merkle root hash.
* Use `ResponseFinalizeBlock.retain_height` with caution! If all nodes in the network remove historical
blocks then this data is permanently lost, and no new nodes will be able to join the network and
bootstrap. Historical blocks may also be required for other purposes, e.g. auditing, replay of
non-persisted heights, light client verification, and so on.
* Just as `ProcessProposal`, the implementation of `FinalizeBlock` MUST be deterministic, since it is
making the Application's state evolve in the context of state machine replication.
* Currently, Tendermint will fill up all fields in `RequestFinalizeBlock`, even if they were
already passed on to the Application via `RequestPrepareProposal` or `RequestProcessProposal`.
If the Application is in same-block execution mode, it applies the right candidate state here
(rather than executing the whole block). In this case the Application disregards all parameters in
`RequestFinalizeBlock` except `RequestFinalizeBlock.hash`.
#### When does Tendermint call it?
When a validator _p_ is in Tendermint consensus height _h_, and _p_ receives
* the Proposal message with block _v_ for a round _r_, along with all its block parts, from _q_,
which is the proposer of round _r_, height _h_,
* `Precommit` messages from _2f + 1_ validators' voting power for round _r_, height _h_,
precommitting the same block _id(v)_,
then _p_'s Tendermint decides block _v_ and finalizes consensus for height _h_ in the following way
1. _p_'s Tendermint persists _v_ as decision for height _h_.
2. _p_'s Tendermint locks the mempool -- no calls to checkTx on new transactions.
3. _p_'s Tendermint calls `RequestFinalizeBlock` with _id(v)_. The call is synchronous.
4. _p_'s Application processes block _v_, received in a previous call to `RequestProcessProposal`.
5. _p_'s Application commits and persists the state resulting from processing the block.
6. _p_'s Application calculates and returns the _AppHash_, along with an array of arrays of bytes representing the output of each of the transactions
7. _p_'s Tendermint hashes the array of transaction outputs and stores it in _ResultHash_
8. _p_'s Tendermint persists _AppHash_ and _ResultHash_
9. _p_'s Tendermint unlocks the mempool -- newly received transactions can now be checked.
10. _p_'s starts consensus for a new height _h+1_, round 0
## Data Types existing in ABCI
Most of the data structures used in ABCI are shared [common data structures](../core/data_structures.md). In certain cases, ABCI uses different data structures which are documented here:
### Validator
* **Fields**:
| Name | Type | Description | Field Number |
|---------|-------|---------------------------------------------------------------------|--------------|
| address | bytes | [Address](../core/data_structures.md#address) of validator | 1 |
| power | int64 | Voting power of the validator | 3 |
* **Usage**:
* Validator identified by address
* Used in RequestBeginBlock as part of VoteInfo
* Does not include PubKey to avoid sending potentially large quantum pubkeys
over the ABCI
### ValidatorUpdate
* **Fields**:
| Name | Type | Description | Field Number |
|---------|--------------------------------------------------|-------------------------------|--------------|
| pub_key | [Public Key](../core/data_structures.md#pub_key) | Public key of the validator | 1 |
| power | int64 | Voting power of the validator | 2 |
* **Usage**:
* Validator identified by PubKey
* Used to tell Tendermint to update the validator set
### Evidence
* **Fields**:
| Name | Type | Description | Field Number |
|--------------------|--------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------|--------------|
| type | [EvidenceType](#evidencetype) | Type of the evidence. An enum of possible evidence's. | 1 |
| validator | [Validator](#validator) | The offending validator | 2 |
| height | int64 | Height when the offense occurred | 3 |
| time | [google.protobuf.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp) | Time of the block that was committed at the height that the offense occurred | 4 |
| total_voting_power | int64 | Total voting power of the validator set at height `Height` | 5 |
#### EvidenceType
* **Fields**
EvidenceType is an enum with the listed fields:
| Name | Field Number |
|---------------------|--------------|
| UNKNOWN | 0 |
| DUPLICATE_VOTE | 1 |
| LIGHT_CLIENT_ATTACK | 2 |
### LastCommitInfo
* **Fields**:
| Name | Type | Description | Field Number |
|-------|--------------------------------|-----------------------------------------------------------------------------------------------------------------------|--------------|
| round | int32 | Commit round. Reflects the total amount of rounds it took to come to consensus for the current block. | 1 |
| votes | repeated [VoteInfo](#voteinfo) | List of validators addresses in the last validator set with their voting power and whether or not they signed a vote. | 2 |
### ConsensusParams
* **Fields**:
| Name | Type | Description | Field Number |
|-----------|---------------------------------------------------------------|------------------------------------------------------------------------------|--------------|
| block | [BlockParams](../core/data_structures.md#blockparams) | Parameters limiting the size of a block and time between consecutive blocks. | 1 |
| evidence | [EvidenceParams](../core/data_structures.md#evidenceparams) | Parameters limiting the validity of evidence of byzantine behaviour. | 2 |
| validator | [ValidatorParams](../core/data_structures.md#validatorparams) | Parameters limiting the types of public keys validators can use. | 3 |
| version | [VersionsParams](../core/data_structures.md#versionparams) | The ABCI application version. | 4 |
### ProofOps
* **Fields**:
| Name | Type | Description | Field Number |
|------|------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
| ops | repeated [ProofOp](#proofop) | List of chained Merkle proofs, of possibly different types. The Merkle root of one op is the value being proven in the next op. The Merkle root of the final op should equal the ultimate root hash being verified against.. | 1 |
### ProofOp
* **Fields**:
| Name | Type | Description | Field Number |
|------|--------|------------------------------------------------|--------------|
| type | string | Type of Merkle proof and how it's encoded. | 1 |
| key | bytes | Key in the Merkle tree that this proof is for. | 2 |
| data | bytes | Encoded Merkle proof for the key. | 3 |
### Snapshot
* **Fields**:
| Name | Type | Description | Field Number |
|----------|--------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
| height | uint64 | The height at which the snapshot was taken (after commit). | 1 |
| format | uint32 | An application-specific snapshot format, allowing applications to version their snapshot data format and make backwards-incompatible changes. Tendermint does not interpret this. | 2 |
| chunks | uint32 | The number of chunks in the snapshot. Must be at least 1 (even if empty). | 3 |
| hash | bytes | TAn arbitrary snapshot hash. Must be equal only for identical snapshots across nodes. Tendermint does not interpret the hash, it only compares them. | 3 |
| metadata | bytes | Arbitrary application metadata, for example chunk hashes or other verification data. | 3 |
* **Usage**:
* Used for state sync snapshots, see the [state sync section](../p2p/messages/state-sync.md) for details.
* A snapshot is considered identical across nodes only if _all_ fields are equal (including
`Metadata`). Chunks may be retrieved from all nodes that have the same snapshot.
* When sent across the network, a snapshot message can be at most 4 MB.
## Data types introduced or modified in ABCI++
### VoteInfo
* **Fields**:
| Name | Type | Description | Field Number |
|-----------------------------|-------------------------|---------------------------------------------------------------|--------------|
| validator | [Validator](#validator) | A validator | 1 |
| signed_last_block | bool | Indicates whether or not the validator signed the last block | 2 |
| tendermint_signed_extension | bytes | Indicates whether or not the validator signed the last block | 3 |
| app_signed_extension | bytes | Indicates whether or not the validator signed the last block | 3 |
* **Usage**:
* Indicates whether a validator signed the last block, allowing for rewards
based on validator availability
* `tendermint_signed_extension` conveys the part of the validator's vote extension that was signed by Tendermint.
* `app_signed_extension` conveys the optional *app_signed* part of the validator's vote extension.
### ExecTxResult
* **Fields**:
| Name | Type | Description | Field Number |
|------------|-------------------------------------------------------------|-----------------------------------------------------------------------|--------------|
| code | uint32 | Response code. | 1 |
| data | bytes | Result bytes, if any. | 2 |
| log | string | The output of the application's logger. **May be non-deterministic.** | 3 |
| info | string | Additional information. **May be non-deterministic.** | 4 |
| gas_wanted | int64 | Amount of gas requested for transaction. | 5 |
| gas_used | int64 | Amount of gas consumed by transaction. | 6 |
| tx_events | repeated [Event](abci++_basic_concepts_002_draft.md#events) | Type & Key-Value events for indexing transactions (e.g. by account). | 7 |
| codespace | string | Namespace for the `code`. | 8 |
### TxAction
```protobuf
enum TxAction {
TXUNKNOWN = 0; // Unknown action
TXUNMODIFIED = 1; // The Application did not modify this transaction. Ignore new_hashes field
TXADDED = 2; // The Application added this transaction. Ignore new_hashes field
TXREMOVED = 3; // The Application wants this transaction removed from the proposal and the mempool.
// Use new_hashes field if the transaction was modified
}
```
* **Usage**:
* If `Action` is TXUNKNOWN, a problem happened in the Application. Tendermint will ignore this transaction. **TODO** should we panic?
* If `Action` is TXUNMODIFIED, Tendermint includes the transaction in the proposal. Nothing to do on the mempool. Field `new_hashes` is ignored.
* If `Action` is TXADDED, Tendermint includes the transaction in the proposal. The transaction is also added to the mempool and gossipped. Field `new_hashes` is ignored.
* If `Action` is TXREMOVED, Tendermint excludes the transaction from the proposal. The transaction is also removed from the mempool if it exists,
similar to `CheckTx` returning _false_. Tendermint can use field `new_hashes` to help clients trace transactions that have been modified into other transactions.
### TxRecord
* **Fields**:
| Name | Type | Description | Field Number |
|------------|-----------------------|------------------------------------------------------------------|--------------|
| action | [TxAction](#txaction) | What should Tendermint do with this transaction? | 1 |
| tx | bytes | Transaction contents | 2 |
| new_hashes | repeated bytes | List of hashes of successor transactions | 3 |
* **Usage**:
* The hashes contained in `new_hashes` MUST follow the same algorithm used by Tendermint for hashing transactions
that are in the mempool.
* As `new_hashes` is a list, `TxRecord` allows to trace many-to-many modifications. Some examples:
* Transaction $t1$ modified into $t2$ is represented with these records
* $t2$ "ADDED"
* $t1$ "REMOVED"; `new_hashes` contains [$id(t2)$]
* Transaction $t1$ modified into $t2$ and $t3$ is represented with these `TxRecord` records
* $t2$ "ADDED"
* $t3$ "ADDED"
* $t1$ "REMOVED"; `new_hashes` contains [$id(t2)$, $id(t3)$]
* Transactions $t1$ and $t2$ aggregated into $t3$ is represented with these `TxRecord` records
* $t3$ "ADDED"
* $t1$ "REMOVED"; `new_hashes` contains [$id(t3)$]
* $t2$ "REMOVED"; `new_hashes` contains [$id(t3)$]
* Transactions $t1$ and $t2$ combined into $t3$ and $t4$ is represented with these `TxRecord` records
* $t3$ "ADDED"
* $t4$ "ADDED"
* $t1$ "REMOVED" and `new_hashes` containing [$id(t3)$, $id(t4)$]
* $t2$ "REMOVED" and `new_hashes` containing [$id(t3)$, $id(t4)$]

+ 211
- 0
spec/abci++/abci++_tmint_expected_behavior_002_draft.md View File

@ -0,0 +1,211 @@
---
order: 4
title: Tendermint's expected behavior
---
# Tendermint's expected behavior
## Valid method call sequences
This section describes what the Application can expect from Tendermint.
The Tendermint consensus algorithm is designed to protect safety under any network conditions, as long as
less than 1/3 of validators' voting power is byzantine. Most of the time, though, the network will behave synchronously and there will be no byzantine process. In these frequent, benign conditions:
* Tendermint will decide in round 0;
* `PrepareProposal` will be called exactly once at the proposer process of round 0, height _h_;
* `ProcessProposal` will be called exactly once at all processes except the proposer of round 0, and
will return _accept_ in its `Response*`;
* `ExtendVote` will be called exactly once at all processes
* `VerifyVoteExtension` will be called _n-1_ times at each validator process, where _n_ is the number of validators; and
* `FinalizeBlock` will be finally called at all processes at the end of height _h_, conveying the same prepared
block that all calls to `PrepareProposal` and `ProcessProposal` had previously reported for height _h_.
However, the Application logic must be ready to cope with any possible run of Tendermint for a given
height, including bad periods (byzantine proposers, network being asynchronous).
In these cases, the sequence of calls to ABCI++ methods may not be so straighforward, but
the Application should still be able to handle them, e.g., without crashing.
The purpose of this section is to define what these sequences look like an a precise way.
As mentioned in the [Basic Concepts](abci++_basic_concepts_002_draft.md) section, Tendermint
acts as a client of ABCI++ and the Application acts as a server. Thus, it is up to Tendermint to
determine when and in which order the different ABCI++ methods will be called. A well-written
Application design should consider _any_ of these possible sequences.
The following grammar, written in case-sensitive Augmented Backus–Naur form (ABNF, specified
in [IETF rfc7405](https://datatracker.ietf.org/doc/html/rfc7405)), specifies all possible
sequences of calls to ABCI++, taken by a correct process, across all heights from the genesis block,
including recovery runs, from the point of view of the Application.
```abnf
start = clean-start / recovery
clean-start = init-chain [state-sync] consensus-exec
state-sync = *state-sync-attempt success-sync info
state-sync-attempt = offer-snapshot *apply-chunk
success-sync = offer-snapshot 1*apply-chunk
recovery = info *consensus-replay consensus-exec
consensus-replay = decide
consensus-exec = (inf)consensus-height
consensus-height = *consensus-round decide
consensus-round = proposer / non-proposer
proposer = prepare-proposal extend-proposer
extend-proposer = *got-vote [extend-vote] *got-vote
non-proposer = *got-vote [extend-non-proposer] *got-vote
extend-non-proposer = process-proposal *got-vote [extend-vote]
init-chain = %s"<InitChain>"
offer-snapshot = %s"<OfferSnapshot>"
apply-chunk = %s"<ApplySnapshotChunk>"
info = %s"<Info>"
prepare-proposal = %s"<PrepareProposal>"
process-proposal = %s"<ProcessProposal>"
extend-vote = %s"<ExtendVote>"
got-vote = %s"<VerifyVoteExtension>"
decide = %s"<FinalizeBlock>"
```
>**TODO** Still hesitating... introduce _n_ as total number of validators, so that we can bound the occurrences of
>`got-vote` in a round.
We have kept some of the ABCI++ methods out of the grammar, in order to keep it as clear and concise as possible.
A common reason for keeping all these methods out is that they all can be called at any point in a sequence defined
by the grammar above. Other reasons depend on the method in question:
* `Echo` and `Flush` are only used for debugging purposes. Further, their handling by the Application should be trivial.
* `CheckTx` is detached from the main method call sequence that drives block execution.
* `Query` provides read-only access to the current Application state, so handling it should also be independent from
block execution.
* Similarly, `ListSnapshots` and `LoadSnapshotChunk` provide read-only access to the Application's previously created
snapshots (if any), and help populate the parameters of `OfferSnapshot` and `ApplySnapshotChunk` at a process performing
state-sync while bootstrapping. Unlike `ListSnapshots` and `LoadSnapshotChunk`, both `OfferSnapshot`
and `ApplySnapshotChunk` _are_ included in the grammar.
Finally, method `Info` is a special case. The method's purpose is three-fold, it can be used
1. as part of handling an RPC call from an external client,
2. as a handshake between Tendermint and the Application upon recovery to check whether any blocks need
to be replayed, and
3. at the end of _state-sync_ to verify that the correct state has been reached.
We have left `Info`'s first purpose out of the grammar for the same reasons as all the others: it can happen
at any time, and has nothing to do with the block execution sequence. The second and third purposes, on the other
hand, are present in the grammar.
Let us now examine the grammar line by line, providing further details.
* When a process starts, it may do so for the first time or after a crash (it is recovering).
>```abnf
>start = clean-start / recovery
>```
* If the process is starting from scratch, Tendermint first calls `InitChain`, then it may optionally
start a _state-sync_ mechanism to catch up with other processes. Finally, it enters normal
consensus execution.
>```abnf
>clean-start = init-chain [state-sync] consensus-exec
>```
* In _state-sync_ mode, Tendermint makes one or more attempts at synchronizing the Application's state.
At the beginning of each attempt, it offers the Application a snapshot found at another process.
If the Application accepts the snapshop, at sequence of calls to `ApplySnapshotChunk` method follow
to provide the Application with all the snapshots needed, in order to reconstruct the state locally.
A successful attempt must provide at least one chunk via `ApplySnapshotChunk`.
At the end of a successful attempt, Tendermint calls `Info` to make sure the recontructed state's
_AppHash_ matches the one in the block header at the corresponding height.
>```abnf
>state-sync = *state-sync-attempt success-sync info
>state-sync-attempt = offer-snapshot *apply-chunk
>success-sync = offer-snapshot 1*apply-chunk
>```
* In recovery mode, Tendermint first calls `Info` to know from which height it needs to replay decisions
to the Application. To replay a decision, Tendermint simply calls `FinalizeBlock` with the decided
block at that height. After this, Tendermint enters nomal consensus execution.
>```abnf
>recovery = info *consensus-replay consensus-exec
>consensus-replay = decide
>```
* The non-terminal `consensus-exec` is a key point in this grammar. It is an infinite sequence of
consensus heights. The grammar is thus an
[omega-grammar](https://dl.acm.org/doi/10.5555/2361476.2361481), since it produces infinite
sequences of terminals (i.e., the API calls).
>```abnf
>consensus-exec = (inf)consensus-height
>```
* A consensus height consists of zero or more rounds before deciding via a call to `FinalizeBlock`.
In each round, the sequence of method calls depends on whether the local process is the proposer or not.
>```abnf
>consensus-height = *consensus-round decide
>consensus-round = proposer / non-proposer
>```
* If the local process is the proposer of the current round, Tendermint starts by calling `PrepareProposal`.
No calls to methods related to vote extensions (`ExtendVote`, `VerifyVoteExtension`) can be called
in the present round before `PrepareProposal`. Once `PrepareProposal` is called, calls to
`ExtendVote` and `VerifyVoteExtension` can come in any order, although the former will be called
at most once in this round.
>```abnf
>proposer = prepare-proposal extend-proposer
>extend-proposer = *got-vote [extend-vote] *got-vote
>```
* If the local process is not the proposer of the current round, Tendermint will call `ProcessProposal`
at most once. At most one call to `ExtendVote` can occur only after `ProcessProposal` is called.
A number of calls to `VerifyVoteExtension` can occur in any order with respect to `ProcessProposal`
and `ExtendVote` throughout the round.
>```abnf
>non-proposer = *got-vote [extend-non-proposer] *got-vote
>extend-non-proposer = process-proposal *got-vote [extend-vote]
>```
* Finally, the grammar describes all its terminal symbols, which denote the different ABCI++ method calls that
may appear in a sequence.
>```abnf
>init-chain = %s"<InitChain>"
>offer-snapshot = %s"<OfferSnapshot>"
>apply-chunk = %s"<ApplySnapshotChunk>"
>info = %s"<Info>"
>prepare-proposal = %s"<PrepareProposal>"
>process-proposal = %s"<ProcessProposal>"
>extend-vote = %s"<ExtendVote>"
>got-vote = %s"<VerifyVoteExtension>"
>decide = %s"<FinalizeBlock>"
>```
## Adapting existing Applications that use ABCI
In some cases, an existing Application using the legacy ABCI may need to be adapted to work with ABCI++
with as minimal changes as possible. In this case, of course, ABCI++ will not provide any advange with respect
to the existing implementation, but will keep the same guarantees already provided by ABCI.
Here is how ABCI++ methods should be implemented.
First of all, all the methods that did not change from ABCI to ABCI++, namely `Echo`, `Flush`, `Info`, `InitChain`,
`Query`, `CheckTx`, `ListSnapshots`, `LoadSnapshotChunk`, `OfferSnapshot`, and `ApplySnapshotChunk`, do not need
to undergo any changes in their implementation.
As for the new methods:
* `PrepareProposal` should set `ResponsePrepareProposal.modified_tx` to _false_ and return.
* `ProcessProposal` should set `ResponseProcessProposal.accept` to _true_ and return.
* `ExtendVote` should set `ResponseExtendVote.extension` to an empty byte array and return.
* `VerifyVoteExtension` should set `ResponseVerifyVoteExtension.accept` to _true_ if the extension is an empty byte array
and _false_ otherwise, then return.
* `FinalizeBlock` should coalesce the implementation of methods `BeginBlock`, `DeliverTx`, `EndBlock`, and `Commit`.
The logic extracted from `DeliverTx` should be wrappped by a loop that will execute as many times as
transactions exist in `RequestFinalizeBlock.tx`.

+ 156
- 0
spec/abci++/v0.md View File

@ -0,0 +1,156 @@
# Tendermint v0 Markdown pseudocode
This translates the latex code for Tendermint consensus from the Tendermint paper into markdown.
### Initialization
```go
h_p ← 0
round_p ← 0
step_p is one of {propose, prevote, precommit}
decision_p ← Vector()
lockedRound_p ← -1
lockedValue_p ← nil
validValue_p ← nil
validRound_p ← -1
```
### StartRound(round)
```go
function startRound(round) {
round_p ← round
step_p ← propose
if proposer(h_p, round_p) = p {
if validValue_p != nil {
proposal ← validValue_p
} else {
proposal ← getValue()
}
broadcast ⟨PROPOSAL, h_p, round_p, proposal, validRound_p⟩
} else {
schedule OnTimeoutPropose(h_p,round_p) to be executed after timeoutPropose(round_p)
}
}
```
### ReceiveProposal
In the case where the local node is not locked on any round, the following is ran:
```go
upon ⟨PROPOSAL, h_p, round_p, v, −1) from proposer(h_p, round_p) while step_p = propose do {
if valid(v) ∧ (lockedRound_p = −1 ∨ lockedValue_p = v) {
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
} else {
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
}
step_p ← prevote
}
```
In the case where the node is locked on a round, the following is ran:
```go
upon ⟨PROPOSAL, h_p, round_p, v, vr⟩ from proposer(h_p, round_p)
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v)⟩
while step_p = propose ∧ (vr ≥ 0 ∧ vr < round_p) do {
if valid(v) ∧ (lockedRound_p ≤ vr ∨ lockedValue_p = v) {
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
} else {
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
}
step_p ← prevote
}
```
### Prevote timeout
Upon receiving 2f + 1 prevotes, setup a timeout.
```go
upon 2f + 1 ⟨PREVOTE, h_p, vr, *⟩ with step_p = prevote for the first time, do {
schedule OnTimeoutPrevote(h_p, round_p) to be executed after timeoutPrevote(round_p)
}
```
with OnTimeoutPrevote defined as:
```go
function OnTimeoutPrevote(height, round) {
if (height = h_p && round = round_p && step_p = prevote) {
broadcast ⟨PRECOMMIT, h_p, round_p, nil⟩
step_p ← precommit
}
}
```
### Receiving enough prevotes to precommit
The following code is ran upon receiving 2f + 1 prevotes for the same block
```go
upon ⟨PROPOSAL, h_p, round_p, v, *⟩
from proposer(h_p, round_p)
AND 2f + 1 ⟨PREVOTE, h_p, round_p, id(v)⟩
while valid(v) ∧ step_p >= prevote for the first time do {
if (step_p = prevote) {
lockedValue_p ← v
lockedRound_p ← round_p
broadcast ⟨PRECOMMIT, h_p, round_p, id(v)⟩
step_p ← precommit
}
validValue_p ← v
validRound_p ← round_p
}
```
And upon receiving 2f + 1 prevotes for nil:
```go
upon 2f + 1 ⟨PREVOTE, h_p, round_p, nil⟩
while step_p = prevote do {
broadcast ⟨PRECOMMIT, h_p, round_p, nil⟩
step_p ← precommit
}
```
### Precommit timeout
Upon receiving 2f + 1 precommits, setup a timeout.
```go
upon 2f + 1 ⟨PRECOMMIT, h_p, vr, *⟩ for the first time, do {
schedule OnTimeoutPrecommit(h_p, round_p) to be executed after timeoutPrecommit(round_p)
}
```
with OnTimeoutPrecommit defined as:
```go
function OnTimeoutPrecommit(height, round) {
if (height = h_p && round = round_p) {
StartRound(round_p + 1)
}
}
```
### Upon Receiving 2f + 1 precommits
The following code is ran upon receiving 2f + 1 precommits for the same block
```go
upon ⟨PROPOSAL, h_p, r, v, *⟩
from proposer(h_p, r)
AND 2f + 1 ⟨ PRECOMMIT, h_p, r, id(v)⟩
while decision_p[h_p] = nil do {
if (valid(v)) {
decision_p[h_p] ← v
h_p ← h_p + 1
reset lockedRound_p, lockedValue_p,validRound_p and validValue_p to initial values
StartRound(0)
}
}
```
If we don't see 2f + 1 precommits for the same block, we wait until we get 2f + 1 precommits, and the timeout occurs.

+ 162
- 0
spec/abci++/v1.md View File

@ -0,0 +1,162 @@
# Tendermint v1 Markdown pseudocode
This adds hooks for the existing ABCI to the prior pseudocode
### Initialization
```go
h_p ← 0
round_p ← 0
step_p is one of {propose, prevote, precommit}
decision_p ← Vector()
lockedValue_p ← nil
validValue_p ← nil
validRound_p ← -1
```
### StartRound(round)
```go
function startRound(round) {
round_p ← round
step_p ← propose
if proposer(h_p, round_p) = p {
if validValue_p != nil {
proposal ← validValue_p
} else {
txdata ← mempool.GetBlock()
// getBlockProposal fills in header
proposal ← getBlockProposal(txdata)
}
broadcast ⟨PROPOSAL, h_p, round_p, proposal, validRound_p⟩
} else {
schedule OnTimeoutPropose(h_p,round_p) to be executed after timeoutPropose(round_p)
}
}
```
### ReceiveProposal
In the case where the local node is not locked on any round, the following is ran:
```go
upon ⟨PROPOSAL, h_p, round_p, v, −1) from proposer(h_p, round_p) while step_p = propose do {
if valid(v) ∧ (lockedRound_p = −1 ∨ lockedValue_p = v) {
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
} else {
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
}
step_p ← prevote
}
```
In the case where the node is locked on a round, the following is ran:
```go
upon ⟨PROPOSAL, h_p, round_p, v, vr⟩
from proposer(h_p, round_p)
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v)⟩
while step_p = propose ∧ (vr ≥ 0 ∧ vr < round_p) do {
if valid(v) ∧ (lockedRound_p ≤ vr ∨ lockedValue_p = v) {
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
} else {
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
}
step_p ← prevote
}
```
### Prevote timeout
Upon receiving 2f + 1 prevotes, setup a timeout.
```go
upon 2f + 1 ⟨PREVOTE, h_p, vr, -1⟩
with step_p = prevote for the first time, do {
schedule OnTimeoutPrevote(h_p, round_p) to be executed after timeoutPrevote(round_p)
}
```
with OnTimeoutPrevote defined as:
```go
function OnTimeoutPrevote(height, round) {
if (height = h_p && round = round_p && step_p = prevote) {
broadcast ⟨PRECOMMIT, h_p, round_p, nil⟩
step_p ← precommit
}
}
```
### Receiving enough prevotes to precommit
The following code is ran upon receiving 2f + 1 prevotes for the same block
```go
upon ⟨PROPOSAL, h_p, round_p, v, *⟩
from proposer(h_p, round_p)
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v)⟩
while valid(v) ∧ step_p >= prevote for the first time do {
if (step_p = prevote) {
lockedValue_p ← v
lockedRound_p ← round_p
broadcast ⟨PRECOMMIT, h_p, round_p, id(v)⟩
step_p ← precommit
}
validValue_p ← v
validRound_p ← round_p
}
```
And upon receiving 2f + 1 prevotes for nil:
```go
upon 2f + 1 ⟨PREVOTE, h_p, round_p, nil⟩
while step_p = prevote do {
broadcast ⟨PRECOMMIT, h_p, round_p, nil⟩
step_p ← precommit
}
```
### Precommit timeout
Upon receiving 2f + 1 precommits, setup a timeout.
```go
upon 2f + 1 ⟨PRECOMMIT, h_p, vr, *⟩ for the first time, do {
schedule OnTimeoutPrecommit(h_p, round_p) to be executed after timeoutPrecommit(round_p)
}
```
with OnTimeoutPrecommit defined as:
```go
function OnTimeoutPrecommit(height, round) {
if (height = h_p && round = round_p) {
StartRound(round_p + 1)
}
}
```
### Upon Receiving 2f + 1 precommits
The following code is ran upon receiving 2f + 1 precommits for the same block
```go
upon ⟨PROPOSAL, h_p, r, v, *⟩
from proposer(h_p, r)
AND 2f + 1 ⟨ PRECOMMIT, h_p, r, id(v)⟩
while decision_p[h_p] = nil do {
if (valid(v)) {
decision_p[h_p] ← v
h_p ← h_p + 1
reset lockedRound_p, lockedValue_p,validRound_p and validValue_p to initial values
ABCI.BeginBlock(v.header)
ABCI.DeliverTxs(v.data)
ABCI.EndBlock()
StartRound(0)
}
}
```
If we don't see 2f + 1 precommits for the same block, we wait until we get 2f + 1 precommits, and the timeout occurs.

+ 180
- 0
spec/abci++/v2.md View File

@ -0,0 +1,180 @@
# Tendermint v2 Markdown pseudocode
This adds a single-threaded implementation of ABCI++,
with no optimization for splitting out verifying the header and verifying the proposal.
### Initialization
```go
h_p ← 0
round_p ← 0
step_p is one of {propose, prevote, precommit}
decision_p ← Vector()
lockedValue_p ← nil
validValue_p ← nil
validRound_p ← -1
```
### StartRound(round)
```go
function startRound(round) {
round_p ← round
step_p ← propose
if proposer(h_p, round_p) = p {
if validValue_p != nil {
proposal ← validValue_p
} else {
txdata ← mempool.GetBlock()
// getUnpreparedBlockProposal takes tx data, and fills in the unprepared header data
unpreparedProposal ← getUnpreparedBlockProposal(txdata)
// ABCI++: the proposer may reorder/update transactions in `unpreparedProposal`
proposal ← ABCI.PrepareProposal(unpreparedProposal)
}
broadcast ⟨PROPOSAL, h_p, round_p, proposal, validRound_p⟩
} else {
schedule OnTimeoutPropose(h_p,round_p) to be executed after timeoutPropose(round_p)
}
}
```
### ReceiveProposal
In the case where the local node is not locked on any round, the following is ran:
```go
upon ⟨PROPOSAL, h_p, round_p, v, −1) from proposer(h_p, round_p) while step_p = propose do {
if valid(v) ∧ ABCI.ProcessProposal(h_p, v).accept ∧ (lockedRound_p = −1 ∨ lockedValue_p = v) {
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
} else {
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
// Include any slashing evidence that may be sent in the process proposal response
for evidence in ABCI.ProcessProposal(h_p, v).evidence_list {
broadcast ⟨EVIDENCE, evidence⟩
}
}
step_p ← prevote
}
```
In the case where the node is locked on a round, the following is ran:
```go
upon ⟨PROPOSAL, h_p, round_p, v, vr⟩
from proposer(h_p, round_p)
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v)⟩
while step_p = propose ∧ (vr ≥ 0 ∧ vr < round_p) do {
if valid(v) ∧ ABCI.ProcessProposal(h_p, v).accept ∧ (lockedRound_p ≤ vr ∨ lockedValue_p = v) {
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
} else {
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
// Include any slashing evidence that may be sent in the process proposal response
for evidence in ABCI.ProcessProposal(h_p, v).evidence_list {
broadcast ⟨EVIDENCE, evidence⟩
}
}
step_p ← prevote
}
```
### Prevote timeout
Upon receiving 2f + 1 prevotes, setup a timeout.
```go
upon 2f + 1 ⟨PREVOTE, h_p, vr, -1⟩
with step_p = prevote for the first time, do {
schedule OnTimeoutPrevote(h_p, round_p) to be executed after timeoutPrevote(round_p)
}
```
with OnTimeoutPrevote defined as:
```go
function OnTimeoutPrevote(height, round) {
if (height = h_p && round = round_p && step_p = prevote) {
precommit_extension ← ABCI.ExtendVote(h_p, round_p, nil)
broadcast ⟨PRECOMMIT, h_p, round_p, nil, precommit_extension⟩
step_p ← precommit
}
}
```
### Receiving enough prevotes to precommit
The following code is ran upon receiving 2f + 1 prevotes for the same block
```go
upon ⟨PROPOSAL, h_p, round_p, v, *⟩
from proposer(h_p, round_p)
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v)⟩
while valid(v) ∧ step_p >= prevote for the first time do {
if (step_p = prevote) {
lockedValue_p ← v
lockedRound_p ← round_p
precommit_extension ← ABCI.ExtendVote(h_p, round_p, id(v))
broadcast ⟨PRECOMMIT, h_p, round_p, id(v), precommit_extension⟩
step_p ← precommit
}
validValue_p ← v
validRound_p ← round_p
}
```
And upon receiving 2f + 1 prevotes for nil:
```go
upon 2f + 1 ⟨PREVOTE, h_p, round_p, nil⟩
while step_p = prevote do {
precommit_extension ← ABCI.ExtendVote(h_p, round_p, nil)
broadcast ⟨PRECOMMIT, h_p, round_p, nil, precommit_extension⟩
step_p ← precommit
}
```
### Upon receiving a precommit
Upon receiving a precommit `precommit`, we ensure that `ABCI.VerifyVoteExtension(precommit.precommit_extension) = true`
before accepting the precommit. This is akin to how we check the signature on precommits normally, hence its not wrapped
in the syntax of methods from the paper.
### Precommit timeout
Upon receiving 2f + 1 precommits, setup a timeout.
```go
upon 2f + 1 ⟨PRECOMMIT, h_p, vr, *⟩ for the first time, do {
schedule OnTimeoutPrecommit(h_p, round_p) to be executed after timeoutPrecommit(round_p)
}
```
with OnTimeoutPrecommit defined as:
```go
function OnTimeoutPrecommit(height, round) {
if (height = h_p && round = round_p) {
StartRound(round_p + 1)
}
}
```
### Upon Receiving 2f + 1 precommits
The following code is ran upon receiving 2f + 1 precommits for the same block
```go
upon ⟨PROPOSAL, h_p, r, v, *⟩
from proposer(h_p, r)
AND 2f + 1 ⟨ PRECOMMIT, h_p, r, id(v)⟩
while decision_p[h_p] = nil do {
if (valid(v)) {
decision_p[h_p] ← v
h_p ← h_p + 1
reset lockedRound_p, lockedValue_p,validRound_p and validValue_p to initial values
ABCI.FinalizeBlock(id(v))
StartRound(0)
}
}
```
If we don't see 2f + 1 precommits for the same block, we wait until we get 2f + 1 precommits, and the timeout occurs.

+ 201
- 0
spec/abci++/v3.md View File

@ -0,0 +1,201 @@
# Tendermint v3 Markdown pseudocode
This is a single-threaded implementation of ABCI++,
with an optimization for the ProcessProposal phase.
Namely, processing of the header and the block data is separated into two different functions.
### Initialization
```go
h_p ← 0
round_p ← 0
step_p is one of {propose, prevote, precommit}
decision_p ← Vector()
lockedValue_p ← nil
validValue_p ← nil
validRound_p ← -1
```
### StartRound(round)
```go
function startRound(round) {
round_p ← round
step_p ← propose
if proposer(h_p, round_p) = p {
if validValue_p != nil {
proposal ← validValue_p
} else {
txdata ← mempool.GetBlock()
// getUnpreparedBlockProposal fills in header
unpreparedProposal ← getUnpreparedBlockProposal(txdata)
proposal ← ABCI.PrepareProposal(unpreparedProposal)
}
broadcast ⟨PROPOSAL, h_p, round_p, proposal, validRound_p⟩
} else {
schedule OnTimeoutPropose(h_p,round_p) to be executed after timeoutPropose(round_p)
}
}
```
### ReceiveProposal
In the case where the local node is not locked on any round, the following is ran:
```go
upon ⟨PROPOSAL, h_p, round_p, v_header, −1) from proposer(h_p, round_p) while step_p = propose do {
prevote_nil ← false
// valid is Tendermints validation, ABCI.VerifyHeader is the applications
if valid(v_header) ∧ ABCI.VerifyHeader(h_p, v_header) ∧ (lockedRound_p = −1 ∨ lockedValue_p = id(v_header)) {
wait to receive proposal v corresponding to v_header
// We split up the app's header verification from the remainder of its processing of the proposal
if ABCI.ProcessProposal(h_p, v).accept {
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
} else {
prevote_nil ← true
// Include any slashing evidence that may be sent in the process proposal response
for evidence in ABCI.ProcessProposal(h_p, v).evidence_list {
broadcast ⟨EVIDENCE, evidence⟩
}
}
} else {
prevote_nil ← true
}
if prevote_nil {
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
}
step_p ← prevote
}
```
In the case where the node is locked on a round, the following is ran:
```go
upon ⟨PROPOSAL, h_p, round_p, v_header, vr⟩
from proposer(h_p, round_p)
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v_header)⟩
while step_p = propose ∧ (vr ≥ 0 ∧ vr < round_p) do {
prevote_nil ← false
if valid(v) ∧ ABCI.VerifyHeader(h_p, v.header) ∧ (lockedRound_p ≤ vr ∨ lockedValue_p = v) {
wait to receive proposal v corresponding to v_header
// We split up the app's header verification from the remainder of its processing of the proposal
if ABCI.ProcessProposal(h_p, v).accept {
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
} else {
prevote_nil ← true
// Include any slashing evidence that may be sent in the process proposal response
for evidence in ABCI.ProcessProposal(h_p, v).evidence_list {
broadcast ⟨EVIDENCE, evidence⟩
}
}
} else {
prevote_nil ← true
}
if prevote_nil {
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
}
step_p ← prevote
}
```
### Prevote timeout
Upon receiving 2f + 1 prevotes, setup a timeout.
```go
upon 2f + 1 ⟨PREVOTE, h_p, vr, -1⟩
with step_p = prevote for the first time, do {
schedule OnTimeoutPrevote(h_p, round_p) to be executed after timeoutPrevote(round_p)
}
```
with OnTimeoutPrevote defined as:
```go
function OnTimeoutPrevote(height, round) {
if (height = h_p && round = round_p && step_p = prevote) {
precommit_extension ← ABCI.ExtendVote(h_p, round_p, nil)
broadcast ⟨PRECOMMIT, h_p, round_p, nil, precommit_extension⟩
step_p ← precommit
}
}
```
### Receiving enough prevotes to precommit
The following code is ran upon receiving 2f + 1 prevotes for the same block
```go
upon ⟨PROPOSAL, h_p, round_p, v, *⟩
from proposer(h_p, round_p)
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v)⟩
while valid(v) ∧ step_p >= prevote for the first time do {
if (step_p = prevote) {
lockedValue_p ← v
lockedRound_p ← round_p
precommit_extension ← ABCI.ExtendVote(h_p, round_p, id(v))
broadcast ⟨PRECOMMIT, h_p, round_p, id(v), precommit_extension⟩
step_p ← precommit
}
validValue_p ← v
validRound_p ← round_p
}
```
And upon receiving 2f + 1 prevotes for nil:
```go
upon 2f + 1 ⟨PREVOTE, h_p, round_p, nil⟩
while step_p = prevote do {
precommit_extension ← ABCI.ExtendVote(h_p, round_p, nil)
broadcast ⟨PRECOMMIT, h_p, round_p, nil, precommit_extension⟩
step_p ← precommit
}
```
### Upon receiving a precommit
Upon receiving a precommit `precommit`, we ensure that `ABCI.VerifyVoteExtension(precommit.precommit_extension) = true`
before accepting the precommit. This is akin to how we check the signature on precommits normally, hence its not wrapped
in the syntax of methods from the paper.
### Precommit timeout
Upon receiving 2f + 1 precommits, setup a timeout.
```go
upon 2f + 1 ⟨PRECOMMIT, h_p, vr, *⟩ for the first time, do {
schedule OnTimeoutPrecommit(h_p, round_p) to be executed after timeoutPrecommit(round_p)
}
```
with OnTimeoutPrecommit defined as:
```go
function OnTimeoutPrecommit(height, round) {
if (height = h_p && round = round_p) {
StartRound(round_p + 1)
}
}
```
### Upon Receiving 2f + 1 precommits
The following code is ran upon receiving 2f + 1 precommits for the same block
```go
upon ⟨PROPOSAL, h_p, r, v, *⟩
from proposer(h_p, r)
AND 2f + 1 ⟨ PRECOMMIT, h_p, r, id(v)⟩
while decision_p[h_p] = nil do {
if (valid(v)) {
decision_p[h_p] ← v
h_p ← h_p + 1
reset lockedRound_p, lockedValue_p,validRound_p and validValue_p to initial values
ABCI.FinalizeBlock(id(v))
StartRound(0)
}
}
```
If we don't see 2f + 1 precommits for the same block, we wait until we get 2f + 1 precommits, and the timeout occurs.

+ 199
- 0
spec/abci++/v4.md View File

@ -0,0 +1,199 @@
# Tendermint v4 Markdown pseudocode
This is a multi-threaded implementation of ABCI++,
where ProcessProposal starts when the proposal is received, but ends before precommitting.
### Initialization
```go
h_p ← 0
round_p ← 0
step_p is one of {propose, prevote, precommit}
decision_p ← Vector()
lockedValue_p ← nil
validValue_p ← nil
validRound_p ← -1
```
### StartRound(round)
```go
function startRound(round) {
round_p ← round
step_p ← propose
if proposer(h_p, round_p) = p {
if validValue_p != nil {
proposal ← validValue_p
} else {
txdata ← mempool.GetBlock()
// getUnpreparedBlockProposal fills in header
unpreparedProposal ← getUnpreparedBlockProposal(txdata)
proposal ← ABCI.PrepareProposal(unpreparedProposal)
}
broadcast ⟨PROPOSAL, h_p, round_p, proposal, validRound_p⟩
} else {
schedule OnTimeoutPropose(h_p,round_p) to be executed after timeoutPropose(round_p)
}
}
```
### ReceiveProposal
In the case where the local node is not locked on any round, the following is ran:
```go
upon ⟨PROPOSAL, h_p, round_p, v, −1) from proposer(h_p, round_p) while step_p = propose do {
if valid(v) ∧ ABCI.VerifyHeader(h_p, v.header) ∧ (lockedRound_p = −1 ∨ lockedValue_p = v) {
// We fork process proposal into a parallel process
Fork ABCI.ProcessProposal(h_p, v)
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
} else {
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
}
step_p ← prevote
}
```
In the case where the node is locked on a round, the following is ran:
```go
upon ⟨PROPOSAL, h_p, round_p, v, vr⟩
from proposer(h_p, round_p)
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v)⟩
while step_p = propose ∧ (vr ≥ 0 ∧ vr < round_p) do {
if valid(v) ∧ ABCI.VerifyHeader(h_p, v.header) ∧ (lockedRound_p ≤ vr ∨ lockedValue_p = v) {
// We fork process proposal into a parallel process
Fork ABCI.ProcessProposal(h_p, v)
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
} else {
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
}
step_p ← prevote
}
```
### Prevote timeout
Upon receiving 2f + 1 prevotes, setup a timeout.
```go
upon 2f + 1 ⟨PREVOTE, h_p, vr, -1⟩
with step_p = prevote for the first time, do {
schedule OnTimeoutPrevote(h_p, round_p) to be executed after timeoutPrevote(round_p)
}
```
with OnTimeoutPrevote defined as:
```go
def OnTimeoutPrevote(height, round) {
if (height = h_p && round = round_p && step_p = prevote) {
// Join the ProcessProposal, and output any evidence in case it has some.
processProposalOutput ← Join ABCI.ProcessProposal(h_p, v)
for evidence in processProposalOutput.evidence_list {
broadcast ⟨EVIDENCE, evidence⟩
}
precommit_extension ← ABCI.ExtendVote(h_p, round_p, nil)
broadcast ⟨PRECOMMIT, h_p, round_p, nil, precommit_extension⟩
step_p ← precommit
}
}
```
### Receiving enough prevotes to precommit
The following code is ran upon receiving 2f + 1 prevotes for the same block
```go
upon ⟨PROPOSAL, h_p, round_p, v, *⟩
from proposer(h_p, round_p)
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v)⟩
while valid(v) ∧ step_p >= prevote for the first time do {
if (step_p = prevote) {
lockedValue_p ← v
lockedRound_p ← round_p
processProposalOutput ← Join ABCI.ProcessProposal(h_p, v)
// If the proposal is valid precommit as before.
// If it was invalid, precommit nil.
// Note that ABCI.ProcessProposal(h_p, v).accept is deterministic for all honest nodes.
precommit_value ← nil
if processProposalOutput.accept {
precommit_value ← id(v)
}
precommit_extension ← ABCI.ExtendVote(h_p, round_p, precommit_value)
broadcast ⟨PRECOMMIT, h_p, round_p, precommit_value, precommit_extension⟩
for evidence in processProposalOutput.evidence_list {
broadcast ⟨EVIDENCE, evidence⟩
}
step_p ← precommit
}
validValue_p ← v
validRound_p ← round_p
}
```
And upon receiving 2f + 1 prevotes for nil:
```go
upon 2f + 1 ⟨PREVOTE, h_p, round_p, nil⟩
while step_p = prevote do {
// Join ABCI.ProcessProposal, and broadcast any evidence if it exists.
processProposalOutput ← Join ABCI.ProcessProposal(h_p, v)
for evidence in processProposalOutput.evidence_list {
broadcast ⟨EVIDENCE, evidence⟩
}
precommit_extension ← ABCI.ExtendVote(h_p, round_p, nil)
broadcast ⟨PRECOMMIT, h_p, round_p, nil, precommit_extension⟩
step_p ← precommit
}
```
### Upon receiving a precommit
Upon receiving a precommit `precommit`, we ensure that `ABCI.VerifyVoteExtension(precommit.precommit_extension) = true`
before accepting the precommit. This is akin to how we check the signature on precommits normally, hence its not wrapped
in the syntax of methods from the paper.
### Precommit timeout
Upon receiving 2f + 1 precommits, setup a timeout.
```go
upon 2f + 1 ⟨PRECOMMIT, h_p, vr, *⟩ for the first time, do {
schedule OnTimeoutPrecommit(h_p, round_p) to be executed after timeoutPrecommit(round_p)
}
```
with OnTimeoutPrecommit defined as:
```go
def OnTimeoutPrecommit(height, round) {
if (height = h_p && round = round_p) {
StartRound(round_p + 1)
}
}
```
### Upon Receiving 2f + 1 precommits
The following code is ran upon receiving 2f + 1 precommits for the same block
```go
upon ⟨PROPOSAL, h_p, r, v, *⟩
from proposer(h_p, r)
AND 2f + 1 ⟨ PRECOMMIT, h_p, r, id(v)⟩
while decision_p[h_p] = nil do {
if (valid(v)) {
decision_p[h_p] ← v
h_p ← h_p + 1
reset lockedRound_p, lockedValue_p,validRound_p and validValue_p to initial values
ABCI.FinalizeBlock(id(v))
StartRound(0)
}
}
```
If we don't see 2f + 1 precommits for the same block, we wait until we get 2f + 1 precommits, and the timeout occurs.

+ 27
- 0
spec/abci/README.md View File

@ -0,0 +1,27 @@
---
order: 1
parent:
title: ABCI
order: 2
---
# ABCI
ABCI stands for "**A**pplication **B**lock**c**hain **I**nterface".
ABCI is the interface between Tendermint (a state-machine replication engine)
and your application (the actual state machine). It consists of a set of
_methods_, each with a corresponding `Request` and `Response`message type.
To perform state-machine replication, Tendermint calls the ABCI methods on the
ABCI application by sending the `Request*` messages and receiving the `Response*` messages in return.
All ABCI messages and methods are defined in [protocol buffers](../../proto/tendermint/abci/types.proto).
This allows Tendermint to run with applications written in many programming languages.
This specification is split as follows:
- [Methods and Types](./abci.md) - complete details on all ABCI methods and
message types
- [Applications](./apps.md) - how to manage ABCI application state and other
details about building ABCI applications
- [Client and Server](./client-server.md) - for those looking to implement their
own ABCI application servers

+ 777
- 0
spec/abci/abci.md View File

@ -0,0 +1,777 @@
---
order: 1
title: Method and Types
---
# Methods and Types
## Connections
ABCI applications can run either within the _same_ process as the Tendermint
state-machine replication engine, or as a _separate_ process from the state-machine
replication engine. When run within the same process, Tendermint will call the ABCI
application methods directly as Go method calls.
When Tendermint and the ABCI application are run as separate processes, Tendermint
opens four connections to the application for ABCI methods. The connections each
handle a subset of the ABCI method calls. These subsets are defined as follows:
#### **Consensus** connection
* Driven by a consensus protocol and is responsible for block execution.
* Handles the `InitChain`, `BeginBlock`, `DeliverTx`, `EndBlock`, and `Commit` method
calls.
#### **Mempool** connection
* For validating new transactions, before they're shared or included in a block.
* Handles the `CheckTx` calls.
#### **Info** connection
* For initialization and for queries from the user.
* Handles the `Info` and `Query` calls.
#### **Snapshot** connection
* For serving and restoring [state sync snapshots](apps.md#state-sync).
* Handles the `ListSnapshots`, `LoadSnapshotChunk`, `OfferSnapshot`, and `ApplySnapshotChunk` calls.
Additionally, there is a `Flush` method that is called on every connection,
and an `Echo` method that is just for debugging.
More details on managing state across connections can be found in the section on
[ABCI Applications](apps.md).
## Errors
The `Query`, `CheckTx` and `DeliverTx` methods include a `Code` field in their `Response*`.
This field is meant to contain an application-specific response code.
A response code of `0` indicates no error. Any other response code
indicates to Tendermint that an error occurred.
These methods also return a `Codespace` string to Tendermint. This field is
used to disambiguate `Code` values returned by different domains of the
application. The `Codespace` is a namespace for the `Code`.
The `Echo`, `Info`, `InitChain`, `BeginBlock`, `EndBlock`, `Commit` methods
do not return errors. An error in any of these methods represents a critical
issue that Tendermint has no reasonable way to handle. If there is an error in one
of these methods, the application must crash to ensure that the error is safely
handled by an operator.
The handling of non-zero response codes by Tendermint is described below
### CheckTx
The `CheckTx` ABCI method controls what transactions are considered for inclusion in a block.
When Tendermint receives a `ResponseCheckTx` with a non-zero `Code`, the associated
transaction will be not be added to Tendermint's mempool or it will be removed if
it is already included.
### DeliverTx
The `DeliverTx` ABCI method delivers transactions from Tendermint to the application.
When Tendermint recieves a `ResponseDeliverTx` with a non-zero `Code`, the response code is logged.
The transaction was already included in a block, so the `Code` does not influence
Tendermint consensus.
### Query
The `Query` ABCI method query queries the application for information about application state.
When Tendermint receives a `ResponseQuery` with a non-zero `Code`, this code is
returned directly to the client that initiated the query.
## Events
The `CheckTx`, `BeginBlock`, `DeliverTx`, `EndBlock` methods include an `Events`
field in their `Response*`. Applications may respond to these ABCI methods with a set of events.
Events allow applications to associate metadata about ABCI method execution with the
transactions and blocks this metadata relates to.
Events returned via these ABCI methods do not impact Tendermint consensus in any way
and instead exist to power subscriptions and queries of Tendermint state.
An `Event` contains a `type` and a list of `EventAttributes`, which are key-value
string pairs denoting metadata about what happened during the method's execution.
`Event` values can be used to index transactions and blocks according to what happened
during their execution. Note that the set of events returned for a block from
`BeginBlock` and `EndBlock` are merged. In case both methods return the same
key, only the value defined in `EndBlock` is used.
Each event has a `type` which is meant to categorize the event for a particular
`Response*` or `Tx`. A `Response*` or `Tx` may contain multiple events with duplicate
`type` values, where each distinct entry is meant to categorize attributes for a
particular event. Every key and value in an event's attributes must be UTF-8
encoded strings along with the event type itself.
```protobuf
message Event {
string type = 1;
repeated EventAttribute attributes = 2;
}
```
The attributes of an `Event` consist of a `key`, a `value`, and an `index` flag. The
index flag notifies the Tendermint indexer to index the attribute. The value of
the `index` flag is non-deterministic and may vary across different nodes in the network.
```protobuf
message EventAttribute {
bytes key = 1;
bytes value = 2;
bool index = 3; // nondeterministic
}
```
Example:
```go
abci.ResponseDeliverTx{
// ...
Events: []abci.Event{
{
Type: "validator.provisions",
Attributes: []abci.EventAttribute{
abci.EventAttribute{Key: []byte("address"), Value: []byte("..."), Index: true},
abci.EventAttribute{Key: []byte("amount"), Value: []byte("..."), Index: true},
abci.EventAttribute{Key: []byte("balance"), Value: []byte("..."), Index: true},
},
},
{
Type: "validator.provisions",
Attributes: []abci.EventAttribute{
abci.EventAttribute{Key: []byte("address"), Value: []byte("..."), Index: true},
abci.EventAttribute{Key: []byte("amount"), Value: []byte("..."), Index: false},
abci.EventAttribute{Key: []byte("balance"), Value: []byte("..."), Index: false},
},
},
{
Type: "validator.slashed",
Attributes: []abci.EventAttribute{
abci.EventAttribute{Key: []byte("address"), Value: []byte("..."), Index: false},
abci.EventAttribute{Key: []byte("amount"), Value: []byte("..."), Index: true},
abci.EventAttribute{Key: []byte("reason"), Value: []byte("..."), Index: true},
},
},
// ...
},
}
```
## EvidenceType
Tendermint's security model relies on the use of "evidence". Evidence is proof of
malicious behaviour by a network participant. It is the responsibility of Tendermint
to detect such malicious behaviour. When malicious behavior is detected, Tendermint
will gossip evidence of the behavior to other nodes and commit the evidence to
the chain once it is verified by all validators. This evidence will then be
passed it on to the application through the ABCI. It is the responsibility of the
application to handle the evidence and exercise punishment.
EvidenceType has the following protobuf format:
```proto
enum EvidenceType {
UNKNOWN = 0;
DUPLICATE_VOTE = 1;
LIGHT_CLIENT_ATTACK = 2;
}
```
There are two forms of evidence: Duplicate Vote and Light Client Attack. More
information can be found in either [data structures](../core/data_structures.md)
or [accountability](../light-client/accountability/README.md)
## Determinism
ABCI applications must implement deterministic finite-state machines to be
securely replicated by the Tendermint consensus engine. This means block execution
over the Consensus Connection must be strictly deterministic: given the same
ordered set of requests, all nodes will compute identical responses, for all
BeginBlock, DeliverTx, EndBlock, and Commit. This is critical, because the
responses are included in the header of the next block, either via a Merkle root
or directly, so all nodes must agree on exactly what they are.
For this reason, it is recommended that applications not be exposed to any
external user or process except via the ABCI connections to a consensus engine
like Tendermint Core. The application must only change its state based on input
from block execution (BeginBlock, DeliverTx, EndBlock, Commit), and not through
any other kind of request. This is the only way to ensure all nodes see the same
transactions and compute the same results.
If there is some non-determinism in the state machine, consensus will eventually
fail as nodes disagree over the correct values for the block header. The
non-determinism must be fixed and the nodes restarted.
Sources of non-determinism in applications may include:
* Hardware failures
* Cosmic rays, overheating, etc.
* Node-dependent state
* Random numbers
* Time
* Underspecification
* Library version changes
* Race conditions
* Floating point numbers
* JSON serialization
* Iterating through hash-tables/maps/dictionaries
* External Sources
* Filesystem
* Network calls (eg. some external REST API service)
See [#56](https://github.com/tendermint/abci/issues/56) for original discussion.
Note that some methods (`Query, CheckTx, DeliverTx`) return
explicitly non-deterministic data in the form of `Info` and `Log` fields. The `Log` is
intended for the literal output from the application's logger, while the
`Info` is any additional info that should be returned. These are the only fields
that are not included in block header computations, so we don't need agreement
on them. All other fields in the `Response*` must be strictly deterministic.
## Block Execution
The first time a new blockchain is started, Tendermint calls
`InitChain`. From then on, the following sequence of methods is executed for each
block:
`BeginBlock, [DeliverTx], EndBlock, Commit`
where one `DeliverTx` is called for each transaction in the block.
The result is an updated application state.
Cryptographic commitments to the results of DeliverTx, EndBlock, and
Commit are included in the header of the next block.
## State Sync
State sync allows new nodes to rapidly bootstrap by discovering, fetching, and applying
state machine snapshots instead of replaying historical blocks. For more details, see the
[state sync section](../p2p/messages/state-sync.md).
New nodes will discover and request snapshots from other nodes in the P2P network.
A Tendermint node that receives a request for snapshots from a peer will call
`ListSnapshots` on its application to retrieve any local state snapshots. After receiving
snapshots from peers, the new node will offer each snapshot received from a peer
to its local application via the `OfferSnapshot` method.
Snapshots may be quite large and are thus broken into smaller "chunks" that can be
assembled into the whole snapshot. Once the application accepts a snapshot and
begins restoring it, Tendermint will fetch snapshot "chunks" from existing nodes.
The node providing "chunks" will fetch them from its local application using
the `LoadSnapshotChunk` method.
As the new node receives "chunks" it will apply them sequentially to the local
application with `ApplySnapshotChunk`. When all chunks have been applied, the application
`AppHash` is retrieved via an `Info` query. The `AppHash` is then compared to
the blockchain's `AppHash` which is verified via [light client verification](../light-client/verification/README.md).
## Messages
### Echo
* **Request**:
* `Message (string)`: A string to echo back
* **Response**:
* `Message (string)`: The input string
* **Usage**:
* Echo a string to test an abci client/server implementation
### Flush
* **Usage**:
* Signals that messages queued on the client should be flushed to
the server. It is called periodically by the client
implementation to ensure asynchronous requests are actually
sent, and is called immediately to make a synchronous request,
which returns when the Flush response comes back.
### Info
* **Request**:
| Name | Type | Description | Field Number |
|---------------|--------|------------------------------------------|--------------|
| version | string | The Tendermint software semantic version | 1 |
| block_version | uint64 | The Tendermint Block Protocol version | 2 |
| p2p_version | uint64 | The Tendermint P2P Protocol version | 3 |
| abci_version | string | The Tendermint ABCI semantic version | 4 |
* **Response**:
| Name | Type | Description | Field Number |
|---------------------|--------|--------------------------------------------------|--------------|
| data | string | Some arbitrary information | 1 |
| version | string | The application software semantic version | 2 |
| app_version | uint64 | The application protocol version | 3 |
| last_block_height | int64 | Latest block for which the app has called Commit | 4 |
| last_block_app_hash | bytes | Latest result of Commit | 5 |
* **Usage**:
* Return information about the application state.
* Used to sync Tendermint with the application during a handshake
that happens on startup.
* The returned `app_version` will be included in the Header of every block.
* Tendermint expects `last_block_app_hash` and `last_block_height` to
be updated during `Commit`, ensuring that `Commit` is never
called twice for the same block height.
> Note: Semantic version is a reference to [semantic versioning](https://semver.org/). Semantic versions in info will be displayed as X.X.x.
### InitChain
* **Request**:
| Name | Type | Description | Field Number |
|------------------|--------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------|--------------|
| time | [google.protobuf.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp) | Genesis time | 1 |
| chain_id | string | ID of the blockchain. | 2 |
| consensus_params | [ConsensusParams](#consensusparams) | Initial consensus-critical parameters. | 3 |
| validators | repeated [ValidatorUpdate](#validatorupdate) | Initial genesis validators, sorted by voting power. | 4 |
| app_state_bytes | bytes | Serialized initial application state. JSON bytes. | 5 |
| initial_height | int64 | Height of the initial block (typically `1`). | 6 |
* **Response**:
| Name | Type | Description | Field Number |
|------------------|----------------------------------------------|-------------------------------------------------|--------------|
| consensus_params | [ConsensusParams](#consensusparams) | Initial consensus-critical parameters (optional | 1 |
| validators | repeated [ValidatorUpdate](#validatorupdate) | Initial validator set (optional). | 2 |
| app_hash | bytes | Initial application hash. | 3 |
* **Usage**:
* Called once upon genesis.
* If ResponseInitChain.Validators is empty, the initial validator set will be the RequestInitChain.Validators
* If ResponseInitChain.Validators is not empty, it will be the initial
validator set (regardless of what is in RequestInitChain.Validators).
* This allows the app to decide if it wants to accept the initial validator
set proposed by tendermint (ie. in the genesis file), or if it wants to use
a different one (perhaps computed based on some application specific
information in the genesis file).
### Query
* **Request**:
| Name | Type | Description | Field Number |
|--------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
| data | bytes | Raw query bytes. Can be used with or in lieu of Path. | 1 |
| path | string | Path field of the request URI. Can be used with or in lieu of `data`. Apps MUST interpret `/store` as a query by key on the underlying store. The key SHOULD be specified in the `data` field. Apps SHOULD allow queries over specific types like `/accounts/...` or `/votes/...` | 2 |
| height | int64 | The block height for which you want the query (default=0 returns data for the latest committed block). Note that this is the height of the block containing the application's Merkle root hash, which represents the state as it was after committing the block at Height-1 | 3 |
| prove | bool | Return Merkle proof with response if possible | 4 |
* **Response**:
| Name | Type | Description | Field Number |
|-----------|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
| code | uint32 | Response code. | 1 |
| log | string | The output of the application's logger. **May be non-deterministic.** | 3 |
| info | string | Additional information. **May be non-deterministic.** | 4 |
| index | int64 | The index of the key in the tree. | 5 |
| key | bytes | The key of the matching data. | 6 |
| value | bytes | The value of the matching data. | 7 |
| proof_ops | [ProofOps](#proofops) | Serialized proof for the value data, if requested, to be verified against the `app_hash` for the given Height. | 8 |
| height | int64 | The block height from which data was derived. Note that this is the height of the block containing the application's Merkle root hash, which represents the state as it was after committing the block at Height-1 | 9 |
| codespace | string | Namespace for the `code`. | 10 |
* **Usage**:
* Query for data from the application at current or past height.
* Optionally return Merkle proof.
* Merkle proof includes self-describing `type` field to support many types
of Merkle trees and encoding formats.
### BeginBlock
* **Request**:
| Name | Type | Description | Field Number |
|----------------------|---------------------------------------------|-------------------------------------------------------------------------------------------------------------------|--------------|
| hash | bytes | The block's hash. This can be derived from the block header. | 1 |
| header | [Header](../core/data_structures.md#header) | The block header. | 2 |
| last_commit_info | [LastCommitInfo](#lastcommitinfo) | Info about the last commit, including the round, and the list of validators and which ones signed the last block. | 3 |
| byzantine_validators | repeated [Evidence](#evidence) | List of evidence of validators that acted maliciously. | 4 |
* **Response**:
| Name | Type | Description | Field Number |
|--------|---------------------------|-------------------------------------|--------------|
| events | repeated [Event](#events) | type & Key-Value events for indexing | 1 |
* **Usage**:
* Signals the beginning of a new block.
* Called prior to any `DeliverTx` method calls.
* The header contains the height, timestamp, and more - it exactly matches the
Tendermint block header. We may seek to generalize this in the future.
* The `LastCommitInfo` and `ByzantineValidators` can be used to determine
rewards and punishments for the validators.
### CheckTx
* **Request**:
| Name | Type | Description | Field Number |
|------|-------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
| tx | bytes | The request transaction bytes | 1 |
| type | CheckTxType | One of `CheckTx_New` or `CheckTx_Recheck`. `CheckTx_New` is the default and means that a full check of the tranasaction is required. `CheckTx_Recheck` types are used when the mempool is initiating a normal recheck of a transaction. | 2 |
* **Response**:
| Name | Type | Description | Field Number |
|------------|---------------------------|-----------------------------------------------------------------------|--------------|
| code | uint32 | Response code. | 1 |
| data | bytes | Result bytes, if any. | 2 |
| log | string | The output of the application's logger. **May be non-deterministic.** | 3 |
| info | string | Additional information. **May be non-deterministic.** | 4 |
| gas_wanted | int64 | Amount of gas requested for transaction. | 5 |
| gas_used | int64 | Amount of gas consumed by transaction. | 6 |
| events | repeated [Event](#events) | Type & Key-Value events for indexing transactions (eg. by account). | 7 |
| codespace | string | Namespace for the `code`. | 8 |
| sender | string | The transaction's sender (e.g. the signer) | 9 |
| priority | int64 | The transaction's priority (for mempool ordering) | 10 |
* **Usage**:
* Technically optional - not involved in processing blocks.
* Guardian of the mempool: every node runs `CheckTx` before letting a
transaction into its local mempool.
* The transaction may come from an external user or another node
* `CheckTx` validates the transaction against the current state of the application,
for example, checking signatures and account balances, but does not apply any
of the state changes described in the transaction.
not running code in a virtual machine.
* Transactions where `ResponseCheckTx.Code != 0` will be rejected - they will not be broadcast to
other nodes or included in a proposal block.
* Tendermint attributes no other value to the response code
### DeliverTx
* **Request**:
| Name | Type | Description | Field Number |
|------|-------|--------------------------------|--------------|
| tx | bytes | The request transaction bytes. | 1 |
* **Response**:
| Name | Type | Description | Field Number |
|------------|---------------------------|-----------------------------------------------------------------------|--------------|
| code | uint32 | Response code. | 1 |
| data | bytes | Result bytes, if any. | 2 |
| log | string | The output of the application's logger. **May be non-deterministic.** | 3 |
| info | string | Additional information. **May be non-deterministic.** | 4 |
| gas_wanted | int64 | Amount of gas requested for transaction. | 5 |
| gas_used | int64 | Amount of gas consumed by transaction. | 6 |
| events | repeated [Event](#events) | Type & Key-Value events for indexing transactions (eg. by account). | 7 |
| codespace | string | Namespace for the `code`. | 8 |
* **Usage**:
* [**Required**] The core method of the application.
* When `DeliverTx` is called, the application must execute the transaction in full before returning control to Tendermint.
* `ResponseDeliverTx.Code == 0` only if the transaction is fully valid.
### EndBlock
* **Request**:
| Name | Type | Description | Field Number |
|--------|-------|------------------------------------|--------------|
| height | int64 | Height of the block just executed. | 1 |
* **Response**:
| Name | Type | Description | Field Number |
|-------------------------|----------------------------------------------|-----------------------------------------------------------------|--------------|
| validator_updates | repeated [ValidatorUpdate](#validatorupdate) | Changes to validator set (set voting power to 0 to remove). | 1 |
| consensus_param_updates | [ConsensusParams](#consensusparams) | Changes to consensus-critical time, size, and other parameters. | 2 |
| events | repeated [Event](#events) | Type & Key-Value events for indexing | 3 |
* **Usage**:
* Signals the end of a block.
* Called after all the transactions for the current block have been delivered, prior to the block's `Commit` message.
* Optional `validator_updates` triggered by block `H`. These updates affect validation
for blocks `H+1`, `H+2`, and `H+3`.
* Heights following a validator update are affected in the following way:
* `H+1`: `NextValidatorsHash` includes the new `validator_updates` value.
* `H+2`: The validator set change takes effect and `ValidatorsHash` is updated.
* `H+3`: `LastCommitInfo` is changed to include the altered validator set.
* `consensus_param_updates` returned for block `H` apply to the consensus
params for block `H+1`. For more information on the consensus parameters,
see the [application spec entry on consensus parameters](./apps.md#consensus-parameters).
### Commit
* **Request**:
| Name | Type | Description | Field Number |
|--------|-------|------------------------------------|--------------|
Commit signals the application to persist application state. It takes no parameters.
* **Response**:
| Name | Type | Description | Field Number |
|---------------|-------|------------------------------------------------------------------------|--------------|
| data | bytes | The Merkle root hash of the application state. | 2 |
| retain_height | int64 | Blocks below this height may be removed. Defaults to `0` (retain all). | 3 |
* **Usage**:
* Signal the application to persist the application state.
* Return an (optional) Merkle root hash of the application state
* `ResponseCommit.Data` is included as the `Header.AppHash` in the next block
* it may be empty
* Later calls to `Query` can return proofs about the application state anchored
in this Merkle root hash
* Note developers can return whatever they want here (could be nothing, or a
constant string, etc.), so long as it is deterministic - it must not be a
function of anything that did not come from the
BeginBlock/DeliverTx/EndBlock methods.
* Use `RetainHeight` with caution! If all nodes in the network remove historical
blocks then this data is permanently lost, and no new nodes will be able to
join the network and bootstrap. Historical blocks may also be required for
other purposes, e.g. auditing, replay of non-persisted heights, light client
verification, and so on.
### ListSnapshots
* **Request**:
| Name | Type | Description | Field Number |
|--------|-------|------------------------------------|--------------|
Empty request asking the application for a list of snapshots.
* **Response**:
| Name | Type | Description | Field Number |
|-----------|--------------------------------|--------------------------------|--------------|
| snapshots | repeated [Snapshot](#snapshot) | List of local state snapshots. | 1 |
* **Usage**:
* Used during state sync to discover available snapshots on peers.
* See `Snapshot` data type for details.
### LoadSnapshotChunk
* **Request**:
| Name | Type | Description | Field Number |
|--------|--------|-----------------------------------------------------------------------|--------------|
| height | uint64 | The height of the snapshot the chunks belongs to. | 1 |
| format | uint32 | The application-specific format of the snapshot the chunk belongs to. | 2 |
| chunk | uint32 | The chunk index, starting from `0` for the initial chunk. | 3 |
* **Response**:
| Name | Type | Description | Field Number |
|-------|-------|-------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
| chunk | bytes | The binary chunk contents, in an arbitray format. Chunk messages cannot be larger than 16 MB _including metadata_, so 10 MB is a good starting point. | 1 |
* **Usage**:
* Used during state sync to retrieve snapshot chunks from peers.
### OfferSnapshot
* **Request**:
| Name | Type | Description | Field Number |
|----------|-----------------------|--------------------------------------------------------------------------|--------------|
| snapshot | [Snapshot](#snapshot) | The snapshot offered for restoration. | 1 |
| app_hash | bytes | The light client-verified app hash for this height, from the blockchain. | 2 |
* **Response**:
| Name | Type | Description | Field Number |
|--------|-------------------|-----------------------------------|--------------|
| result | [Result](#result) | The result of the snapshot offer. | 1 |
#### Result
```proto
enum Result {
UNKNOWN = 0; // Unknown result, abort all snapshot restoration
ACCEPT = 1; // Snapshot is accepted, start applying chunks.
ABORT = 2; // Abort snapshot restoration, and don't try any other snapshots.
REJECT = 3; // Reject this specific snapshot, try others.
REJECT_FORMAT = 4; // Reject all snapshots with this `format`, try others.
REJECT_SENDER = 5; // Reject all snapshots from all senders of this snapshot, try others.
}
```
* **Usage**:
* `OfferSnapshot` is called when bootstrapping a node using state sync. The application may
accept or reject snapshots as appropriate. Upon accepting, Tendermint will retrieve and
apply snapshot chunks via `ApplySnapshotChunk`. The application may also choose to reject a
snapshot in the chunk response, in which case it should be prepared to accept further
`OfferSnapshot` calls.
* Only `AppHash` can be trusted, as it has been verified by the light client. Any other data
can be spoofed by adversaries, so applications should employ additional verification schemes
to avoid denial-of-service attacks. The verified `AppHash` is automatically checked against
the restored application at the end of snapshot restoration.
* For more information, see the `Snapshot` data type or the [state sync section](../p2p/messages/state-sync.md).
### ApplySnapshotChunk
* **Request**:
| Name | Type | Description | Field Number |
|--------|--------|-----------------------------------------------------------------------------|--------------|
| index | uint32 | The chunk index, starting from `0`. Tendermint applies chunks sequentially. | 1 |
| chunk | bytes | The binary chunk contents, as returned by `LoadSnapshotChunk`. | 2 |
| sender | string | The P2P ID of the node who sent this chunk. | 3 |
* **Response**:
| Name | Type | Description | Field Number |
|----------------|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
| result | Result (see below) | The result of applying this chunk. | 1 |
| refetch_chunks | repeated uint32 | Refetch and reapply the given chunks, regardless of `result`. Only the listed chunks will be refetched, and reapplied in sequential order. | 2 |
| reject_senders | repeated string | Reject the given P2P senders, regardless of `Result`. Any chunks already applied will not be refetched unless explicitly requested, but queued chunks from these senders will be discarded, and new chunks or other snapshots rejected. | 3 |
```proto
enum Result {
UNKNOWN = 0; // Unknown result, abort all snapshot restoration
ACCEPT = 1; // The chunk was accepted.
ABORT = 2; // Abort snapshot restoration, and don't try any other snapshots.
RETRY = 3; // Reapply this chunk, combine with `RefetchChunks` and `RejectSenders` as appropriate.
RETRY_SNAPSHOT = 4; // Restart this snapshot from `OfferSnapshot`, reusing chunks unless instructed otherwise.
REJECT_SNAPSHOT = 5; // Reject this snapshot, try a different one.
}
```
* **Usage**:
* The application can choose to refetch chunks and/or ban P2P peers as appropriate. Tendermint
will not do this unless instructed by the application.
* The application may want to verify each chunk, e.g. by attaching chunk hashes in
`Snapshot.Metadata` and/or incrementally verifying contents against `AppHash`.
* When all chunks have been accepted, Tendermint will make an ABCI `Info` call to verify that
`LastBlockAppHash` and `LastBlockHeight` matches the expected values, and record the
`AppVersion` in the node state. It then switches to fast sync or consensus and joins the
network.
* If Tendermint is unable to retrieve the next chunk after some time (e.g. because no suitable
peers are available), it will reject the snapshot and try a different one via `OfferSnapshot`.
The application should be prepared to reset and accept it or abort as appropriate.
## Data Types
Most of the data structures used in ABCI are shared [common data structures](../core/data_structures.md). In certain cases, ABCI uses different data structures which are documented here:
### Validator
* **Fields**:
| Name | Type | Description | Field Number |
|---------|-------|---------------------------------------------------------------------|--------------|
| address | bytes | [Address](../core/data_structures.md#address) of validator | 1 |
| power | int64 | Voting power of the validator | 3 |
* **Usage**:
* Validator identified by address
* Used in RequestBeginBlock as part of VoteInfo
* Does not include PubKey to avoid sending potentially large quantum pubkeys
over the ABCI
### ValidatorUpdate
* **Fields**:
| Name | Type | Description | Field Number |
|---------|--------------------------------------------------|-------------------------------|--------------|
| pub_key | [Public Key](../core/data_structures.md#pub_key) | Public key of the validator | 1 |
| power | int64 | Voting power of the validator | 2 |
* **Usage**:
* Validator identified by PubKey
* Used to tell Tendermint to update the validator set
### VoteInfo
* **Fields**:
| Name | Type | Description | Field Number |
|-------------------|-------------------------|--------------------------------------------------------------|--------------|
| validator | [Validator](#validator) | A validator | 1 |
| signed_last_block | bool | Indicates whether or not the validator signed the last block | 2 |
* **Usage**:
* Indicates whether a validator signed the last block, allowing for rewards
based on validator availability
### Evidence
* **Fields**:
| Name | Type | Description | Field Number |
|--------------------|--------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------|--------------|
| type | [EvidenceType](#evidencetype) | Type of the evidence. An enum of possible evidence's. | 1 |
| validator | [Validator](#validator) | The offending validator | 2 |
| height | int64 | Height when the offense occurred | 3 |
| time | [google.protobuf.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp) | Time of the block that was committed at the height that the offense occurred | 4 |
| total_voting_power | int64 | Total voting power of the validator set at height `Height` | 5 |
#### EvidenceType
* **Fields**
EvidenceType is an enum with the listed fields:
| Name | Field Number |
|---------------------|--------------|
| UNKNOWN | 0 |
| DUPLICATE_VOTE | 1 |
| LIGHT_CLIENT_ATTACK | 2 |
### LastCommitInfo
* **Fields**:
| Name | Type | Description | Field Number |
|-------|--------------------------------|-----------------------------------------------------------------------------------------------------------------------|--------------|
| round | int32 | Commit round. Reflects the total amount of rounds it took to come to consensus for the current block. | 1 |
| votes | repeated [VoteInfo](#voteinfo) | List of validators addresses in the last validator set with their voting power and whether or not they signed a vote. | 2 |
### ConsensusParams
* **Fields**:
| Name | Type | Description | Field Number |
|-----------|---------------------------------------------------------------|------------------------------------------------------------------------------|--------------|
| block | [BlockParams](../core/data_structures.md#blockparams) | Parameters limiting the size of a block and time between consecutive blocks. | 1 |
| evidence | [EvidenceParams](../core/data_structures.md#evidenceparams) | Parameters limiting the validity of evidence of byzantine behaviour. | 2 |
| validator | [ValidatorParams](../core/data_structures.md#validatorparams) | Parameters limiting the types of public keys validators can use. | 3 |
| version | [VersionsParams](../core/data_structures.md#versionparams) | The ABCI application version. | 4 |
| synchrony | [SynchronyParams](../core/data_structures.md#synchronyparams) | Parameters that determine the bounds under which a proposed block's timestamp is considered valid. | 5 |
| timeout | [TimeoutParams](../core/data_structures.md#timeoutparams) | Parameters that configure the timeouts for the steps of the Tendermint consensus algorithm. | 6 |
### ProofOps
* **Fields**:
| Name | Type | Description | Field Number |
|------|------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
| ops | repeated [ProofOp](#proofop) | List of chained Merkle proofs, of possibly different types. The Merkle root of one op is the value being proven in the next op. The Merkle root of the final op should equal the ultimate root hash being verified against.. | 1 |
### ProofOp
* **Fields**:
| Name | Type | Description | Field Number |
|------|--------|------------------------------------------------|--------------|
| type | string | Type of Merkle proof and how it's encoded. | 1 |
| key | bytes | Key in the Merkle tree that this proof is for. | 2 |
| data | bytes | Encoded Merkle proof for the key. | 3 |
### Snapshot
* **Fields**:
| Name | Type | Description | Field Number |
|----------|--------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
| height | uint64 | The height at which the snapshot was taken (after commit). | 1 |
| format | uint32 | An application-specific snapshot format, allowing applications to version their snapshot data format and make backwards-incompatible changes. Tendermint does not interpret this. | 2 |
| chunks | uint32 | The number of chunks in the snapshot. Must be at least 1 (even if empty). | 3 |
| hash | bytes | TAn arbitrary snapshot hash. Must be equal only for identical snapshots across nodes. Tendermint does not interpret the hash, it only compares them. | 3 |
| metadata | bytes | Arbitrary application metadata, for example chunk hashes or other verification data. | 3 |
* **Usage**:
* Used for state sync snapshots, see the [state sync section](../p2p/messages/state-sync.md) for details.
* A snapshot is considered identical across nodes only if _all_ fields are equal (including
`Metadata`). Chunks may be retrieved from all nodes that have the same snapshot.
* When sent across the network, a snapshot message can be at most 4 MB.

+ 672
- 0
spec/abci/apps.md View File

@ -0,0 +1,672 @@
---
order: 2
title: Applications
---
# Applications
Please ensure you've first read the spec for [ABCI Methods and Types](abci.md)
Here we cover the following components of ABCI applications:
- [Connection State](#connection-state) - the interplay between ABCI connections and application state
and the differences between `CheckTx` and `DeliverTx`.
- [Transaction Results](#transaction-results) - rules around transaction
results and validity
- [Validator Set Updates](#validator-updates) - how validator sets are
changed during `InitChain` and `EndBlock`
- [Query](#query) - standards for using the `Query` method and proofs about the
application state
- [Crash Recovery](#crash-recovery) - handshake protocol to synchronize
Tendermint and the application on startup.
- [State Sync](#state-sync) - rapid bootstrapping of new nodes by restoring state machine snapshots
## Connection State
Since Tendermint maintains four concurrent ABCI connections, it is typical
for an application to maintain a distinct state for each, and for the states to
be synchronized during `Commit`.
### Concurrency
In principle, each of the four ABCI connections operate concurrently with one
another. This means applications need to ensure access to state is
thread safe. In practice, both the
[default in-process ABCI client](https://github.com/tendermint/tendermint/blob/v0.34.4/abci/client/local_client.go#L18)
and the
[default Go ABCI
server](https://github.com/tendermint/tendermint/blob/v0.34.4/abci/server/socket_server.go#L32)
use global locks across all connections, so they are not
concurrent at all. This means if your app is written in Go, and compiled in-process with Tendermint
using the default `NewLocalClient`, or run out-of-process using the default `SocketServer`,
ABCI messages from all connections will be linearizable (received one at a
time).
The existence of this global mutex means Go application developers can get
thread safety for application state by routing *all* reads and writes through the ABCI
system. Thus it may be *unsafe* to expose application state directly to an RPC
interface, and unless explicit measures are taken, all queries should be routed through the ABCI Query method.
### BeginBlock
The BeginBlock request can be used to run some code at the beginning of
every block. It also allows Tendermint to send the current block hash
and header to the application, before it sends any of the transactions.
The app should remember the latest height and header (ie. from which it
has run a successful Commit) so that it can tell Tendermint where to
pick up from when it restarts. See information on the Handshake, below.
### Commit
Application state should only be persisted to disk during `Commit`.
Before `Commit` is called, Tendermint locks and flushes the mempool so that no new messages will
be received on the mempool connection. This provides an opportunity to safely update all four connection
states to the latest committed state at once.
When `Commit` completes, it unlocks the mempool.
WARNING: if the ABCI app logic processing the `Commit` message sends a
`/broadcast_tx_sync` or `/broadcast_tx_commit` and waits for the response
before proceeding, it will deadlock. Executing those `broadcast_tx` calls
involves acquiring a lock that is held during the `Commit` call, so it's not
possible. If you make the call to the `broadcast_tx` endpoints concurrently,
that's no problem, it just can't be part of the sequential logic of the
`Commit` function.
### Consensus Connection
The Consensus Connection should maintain a `DeliverTxState` - the working state
for block execution. It should be updated by the calls to `BeginBlock`, `DeliverTx`,
and `EndBlock` during block execution and committed to disk as the "latest
committed state" during `Commit`.
Updates made to the `DeliverTxState` by each method call must be readable by each subsequent method -
ie. the updates are linearizable.
### Mempool Connection
The mempool Connection should maintain a `CheckTxState`
to sequentially process pending transactions in the mempool that have
not yet been committed. It should be initialized to the latest committed state
at the end of every `Commit`.
Before calling `Commit`, Tendermint will lock and flush the mempool connection,
ensuring that all existing CheckTx are responded to and no new ones can begin.
The `CheckTxState` may be updated concurrently with the `DeliverTxState`, as
messages may be sent concurrently on the Consensus and Mempool connections.
After `Commit`, while still holding the mempool lock, CheckTx is run again on all transactions that remain in the
node's local mempool after filtering those included in the block.
An additional `Type` parameter is made available to the CheckTx function that
indicates whether an incoming transaction is new (`CheckTxType_New`), or a
recheck (`CheckTxType_Recheck`).
Finally, after re-checking transactions in the mempool, Tendermint will unlock
the mempool connection. New transactions are once again able to be processed through CheckTx.
Note that CheckTx is just a weak filter to keep invalid transactions out of the block chain.
CheckTx doesn't have to check everything that affects transaction validity; the
expensive things can be skipped. It's weak because a Byzantine node doesn't
care about CheckTx; it can propose a block full of invalid transactions if it wants.
#### Replay Protection
To prevent old transactions from being replayed, CheckTx must implement
replay protection.
It is possible for old transactions to be sent to the application. So
it is important CheckTx implements some logic to handle them.
### Query Connection
The Info Connection should maintain a `QueryState` for answering queries from the user,
and for initialization when Tendermint first starts up (both described further
below).
It should always contain the latest committed state associated with the
latest committed block.
`QueryState` should be set to the latest `DeliverTxState` at the end of every `Commit`,
after the full block has been processed and the state committed to disk.
Otherwise it should never be modified.
Tendermint Core currently uses the Query connection to filter peers upon
connecting, according to IP address or node ID. For instance,
returning non-OK ABCI response to either of the following queries will
cause Tendermint to not connect to the corresponding peer:
- `p2p/filter/addr/<ip addr>`, where `<ip addr>` is an IP address.
- `p2p/filter/id/<id>`, where `<is>` is the hex-encoded node ID (the hash of
the node's p2p pubkey).
Note: these query formats are subject to change!
### Snapshot Connection
The Snapshot Connection is optional, and is only used to serve state sync snapshots for other nodes
and/or restore state sync snapshots to a local node being bootstrapped.
For more information, see [the state sync section of this document](#state-sync).
## Transaction Results
The `Info` and `Log` fields are non-deterministic values for debugging/convenience purposes
that are otherwise ignored.
The `Data` field must be strictly deterministic, but can be arbitrary data.
### Gas
Ethereum introduced the notion of `gas` as an abstract representation of the
cost of resources used by nodes when processing transactions. Every operation in the
Ethereum Virtual Machine uses some amount of gas, and gas can be accepted at a market-variable price.
Users propose a maximum amount of gas for their transaction; if the tx uses less, they get
the difference credited back. Tendermint adopts a similar abstraction,
though uses it only optionally and weakly, allowing applications to define
their own sense of the cost of execution.
In Tendermint, the
[ConsensusParams.Block.MaxGas](../../proto/tendermint/types/params.proto)
limits the amount of `gas` that can be used in a block. The default value is
`-1`, meaning no limit, or that the concept of gas is meaningless.
Responses contain a `GasWanted` and `GasUsed` field. The former is the maximum
amount of gas the sender of a tx is willing to use, and the later is how much it actually
used. Applications should enforce that `GasUsed <= GasWanted` - ie. tx execution
should halt before it can use more resources than it requested.
When `MaxGas > -1`, Tendermint enforces the following rules:
- `GasWanted <= MaxGas` for all txs in the mempool
- `(sum of GasWanted in a block) <= MaxGas` when proposing a block
If `MaxGas == -1`, no rules about gas are enforced.
Note that Tendermint does not currently enforce anything about Gas in the consensus, only the mempool.
This means it does not guarantee that committed blocks satisfy these rules!
It is the application's responsibility to return non-zero response codes when gas limits are exceeded.
The `GasUsed` field is ignored completely by Tendermint. That said, applications should enforce:
- `GasUsed <= GasWanted` for any given transaction
- `(sum of GasUsed in a block) <= MaxGas` for every block
In the future, we intend to add a `Priority` field to the responses that can be
used to explicitly prioritize txs in the mempool for inclusion in a block
proposal. See [#1861](https://github.com/tendermint/tendermint/issues/1861).
### CheckTx
If `Code != 0`, it will be rejected from the mempool and hence
not broadcasted to other peers and not included in a proposal block.
`Data` contains the result of the CheckTx transaction execution, if any. It is
semantically meaningless to Tendermint.
`Events` include any events for the execution, though since the transaction has not
been committed yet, they are effectively ignored by Tendermint.
### DeliverTx
DeliverTx is the workhorse of the blockchain. Tendermint sends the
DeliverTx requests asynchronously but in order, and relies on the
underlying socket protocol (ie. TCP) to ensure they are received by the
app in order. They have already been ordered in the global consensus by
the Tendermint protocol.
If DeliverTx returns `Code != 0`, the transaction will be considered invalid,
though it is still included in the block.
DeliverTx also returns a [Code, Data, and Log](../../proto/tendermint/abci/types.proto#L189-L191).
`Data` contains the result of the CheckTx transaction execution, if any. It is
semantically meaningless to Tendermint.
Both the `Code` and `Data` are included in a structure that is hashed into the
`LastResultsHash` of the next block header.
`Events` include any events for the execution, which Tendermint will use to index
the transaction by. This allows transactions to be queried according to what
events took place during their execution.
## Updating the Validator Set
The application may set the validator set during InitChain, and may update it during
EndBlock.
Note that the maximum total power of the validator set is bounded by
`MaxTotalVotingPower = MaxInt64 / 8`. Applications are responsible for ensuring
they do not make changes to the validator set that cause it to exceed this
limit.
Additionally, applications must ensure that a single set of updates does not contain any duplicates -
a given public key can only appear once within a given update. If an update includes
duplicates, the block execution will fail irrecoverably.
### InitChain
The `InitChain` method can return a list of validators.
If the list is empty, Tendermint will use the validators loaded in the genesis
file.
If the list returned by `InitChain` is not empty, Tendermint will use its contents as the validator set.
This way the application can set the initial validator set for the
blockchain.
### EndBlock
Updates to the Tendermint validator set can be made by returning
`ValidatorUpdate` objects in the `ResponseEndBlock`:
```protobuf
message ValidatorUpdate {
tendermint.crypto.keys.PublicKey pub_key
int64 power
}
message PublicKey {
oneof {
ed25519 bytes = 1;
}
```
The `pub_key` currently supports only one type:
- `type = "ed25519"`
The `power` is the new voting power for the validator, with the
following rules:
- power must be non-negative
- if power is 0, the validator must already exist, and will be removed from the
validator set
- if power is non-0:
- if the validator does not already exist, it will be added to the validator
set with the given power
- if the validator does already exist, its power will be adjusted to the given power
- the total power of the new validator set must not exceed MaxTotalVotingPower
Note the updates returned in block `H` will only take effect at block `H+2`.
## Consensus Parameters
ConsensusParams enforce certain limits in the blockchain, like the maximum size
of blocks, amount of gas used in a block, and the maximum acceptable age of
evidence. They can be set in InitChain and updated in EndBlock.
### BlockParams.MaxBytes
The maximum size of a complete Protobuf encoded block.
This is enforced by Tendermint consensus.
This implies a maximum transaction size that is this MaxBytes, less the expected size of
the header, the validator set, and any included evidence in the block.
Must have `0 < MaxBytes < 100 MB`.
### BlockParams.MaxGas
The maximum of the sum of `GasWanted` that will be allowed in a proposed block.
This is *not* enforced by Tendermint consensus.
It is left to the app to enforce (ie. if txs are included past the
limit, they should return non-zero codes). It is used by Tendermint to limit the
txs included in a proposed block.
Must have `MaxGas >= -1`.
If `MaxGas == -1`, no limit is enforced.
### EvidenceParams.MaxAgeDuration
This is the maximum age of evidence in time units.
This is enforced by Tendermint consensus.
If a block includes evidence older than this (AND the evidence was created more
than `MaxAgeNumBlocks` ago), the block will be rejected (validators won't vote
for it).
Must have `MaxAgeDuration > 0`.
### EvidenceParams.MaxAgeNumBlocks
This is the maximum age of evidence in blocks.
This is enforced by Tendermint consensus.
If a block includes evidence older than this (AND the evidence was created more
than `MaxAgeDuration` ago), the block will be rejected (validators won't vote
for it).
Must have `MaxAgeNumBlocks > 0`.
### EvidenceParams.MaxNum
This is the maximum number of evidence that can be committed to a single block.
The product of this and the `MaxEvidenceBytes` must not exceed the size of
a block minus it's overhead ( ~ `MaxBytes`).
Must have `MaxNum > 0`.
### Updates
The application may set the ConsensusParams during InitChain, and update them during
EndBlock. If the ConsensusParams is empty, it will be ignored. Each field
that is not empty will be applied in full. For instance, if updating the
Block.MaxBytes, applications must also set the other Block fields (like
Block.MaxGas), even if they are unchanged, as they will otherwise cause the
value to be updated to 0.
#### InitChain
ResponseInitChain includes a ConsensusParams.
If ConsensusParams is nil, Tendermint will use the params loaded in the genesis
file. If ConsensusParams is not nil, Tendermint will use it.
This way the application can determine the initial consensus params for the
blockchain.
#### EndBlock
ResponseEndBlock includes a ConsensusParams.
If ConsensusParams nil, Tendermint will do nothing.
If ConsensusParam is not nil, Tendermint will use it.
This way the application can update the consensus params over time.
Note the updates returned in block `H` will take effect right away for block
`H+1`.
## Query
Query is a generic method with lots of flexibility to enable diverse sets
of queries on application state. Tendermint makes use of Query to filter new peers
based on ID and IP, and exposes Query to the user over RPC.
Note that calls to Query are not replicated across nodes, but rather query the
local node's state - hence they may return stale reads. For reads that require
consensus, use a transaction.
The most important use of Query is to return Merkle proofs of the application state at some height
that can be used for efficient application-specific light-clients.
Note Tendermint has technically no requirements from the Query
message for normal operation - that is, the ABCI app developer need not implement
Query functionality if they do not wish too.
### Query Proofs
The Tendermint block header includes a number of hashes, each providing an
anchor for some type of proof about the blockchain. The `ValidatorsHash` enables
quick verification of the validator set, the `DataHash` gives quick
verification of the transactions included in the block, etc.
The `AppHash` is unique in that it is application specific, and allows for
application-specific Merkle proofs about the state of the application.
While some applications keep all relevant state in the transactions themselves
(like Bitcoin and its UTXOs), others maintain a separated state that is
computed deterministically *from* transactions, but is not contained directly in
the transactions themselves (like Ethereum contracts and accounts).
For such applications, the `AppHash` provides a much more efficient way to verify light-client proofs.
ABCI applications can take advantage of more efficient light-client proofs for
their state as follows:
- return the Merkle root of the deterministic application state in
`ResponseCommit.Data`. This Merkle root will be included as the `AppHash` in the next block.
- return efficient Merkle proofs about that application state in `ResponseQuery.Proof`
that can be verified using the `AppHash` of the corresponding block.
For instance, this allows an application's light-client to verify proofs of
absence in the application state, something which is much less efficient to do using the block hash.
Some applications (eg. Ethereum, Cosmos-SDK) have multiple "levels" of Merkle trees,
where the leaves of one tree are the root hashes of others. To support this, and
the general variability in Merkle proofs, the `ResponseQuery.Proof` has some minimal structure:
```protobuf
message ProofOps {
repeated ProofOp ops
}
message ProofOp {
string type = 1;
bytes key = 2;
bytes data = 3;
}
```
Each `ProofOp` contains a proof for a single key in a single Merkle tree, of the specified `type`.
This allows ABCI to support many different kinds of Merkle trees, encoding
formats, and proofs (eg. of presence and absence) just by varying the `type`.
The `data` contains the actual encoded proof, encoded according to the `type`.
When verifying the full proof, the root hash for one ProofOp is the value being
verified for the next ProofOp in the list. The root hash of the final ProofOp in
the list should match the `AppHash` being verified against.
### Peer Filtering
When Tendermint connects to a peer, it sends two queries to the ABCI application
using the following paths, with no additional data:
- `/p2p/filter/addr/<IP:PORT>`, where `<IP:PORT>` denote the IP address and
the port of the connection
- `p2p/filter/id/<ID>`, where `<ID>` is the peer node ID (ie. the
pubkey.Address() for the peer's PubKey)
If either of these queries return a non-zero ABCI code, Tendermint will refuse
to connect to the peer.
### Paths
Queries are directed at paths, and may optionally include additional data.
The expectation is for there to be some number of high level paths
differentiating concerns, like `/p2p`, `/store`, and `/app`. Currently,
Tendermint only uses `/p2p`, for filtering peers. For more advanced use, see the
implementation of
[Query in the Cosmos-SDK](https://github.com/cosmos/cosmos-sdk/blob/v0.23.1/baseapp/baseapp.go#L333).
## Crash Recovery
On startup, Tendermint calls the `Info` method on the Info Connection to get the latest
committed state of the app. The app MUST return information consistent with the
last block it succesfully completed Commit for.
If the app succesfully committed block H, then `last_block_height = H` and `last_block_app_hash = <hash returned by Commit for block H>`. If the app
failed during the Commit of block H, then `last_block_height = H-1` and
`last_block_app_hash = <hash returned by Commit for block H-1, which is the hash in the header of block H>`.
We now distinguish three heights, and describe how Tendermint syncs itself with
the app.
```md
storeBlockHeight = height of the last block Tendermint saw a commit for
stateBlockHeight = height of the last block for which Tendermint completed all
block processing and saved all ABCI results to disk
appBlockHeight = height of the last block for which ABCI app succesfully
completed Commit
```
Note we always have `storeBlockHeight >= stateBlockHeight` and `storeBlockHeight >= appBlockHeight`
Note also Tendermint never calls Commit on an ABCI app twice for the same height.
The procedure is as follows.
First, some simple start conditions:
If `appBlockHeight == 0`, then call InitChain.
If `storeBlockHeight == 0`, we're done.
Now, some sanity checks:
If `storeBlockHeight < appBlockHeight`, error
If `storeBlockHeight < stateBlockHeight`, panic
If `storeBlockHeight > stateBlockHeight+1`, panic
Now, the meat:
If `storeBlockHeight == stateBlockHeight && appBlockHeight < storeBlockHeight`,
replay all blocks in full from `appBlockHeight` to `storeBlockHeight`.
This happens if we completed processing the block, but the app forgot its height.
If `storeBlockHeight == stateBlockHeight && appBlockHeight == storeBlockHeight`, we're done.
This happens if we crashed at an opportune spot.
If `storeBlockHeight == stateBlockHeight+1`
This happens if we started processing the block but didn't finish.
If `appBlockHeight < stateBlockHeight`
replay all blocks in full from `appBlockHeight` to `storeBlockHeight-1`,
and replay the block at `storeBlockHeight` using the WAL.
This happens if the app forgot the last block it committed.
If `appBlockHeight == stateBlockHeight`,
replay the last block (storeBlockHeight) in full.
This happens if we crashed before the app finished Commit
If `appBlockHeight == storeBlockHeight`
update the state using the saved ABCI responses but dont run the block against the real app.
This happens if we crashed after the app finished Commit but before Tendermint saved the state.
## State Sync
A new node joining the network can simply join consensus at the genesis height and replay all
historical blocks until it is caught up. However, for large chains this can take a significant
amount of time, often on the order of days or weeks.
State sync is an alternative mechanism for bootstrapping a new node, where it fetches a snapshot
of the state machine at a given height and restores it. Depending on the application, this can
be several orders of magnitude faster than replaying blocks.
Note that state sync does not currently backfill historical blocks, so the node will have a
truncated block history - users are advised to consider the broader network implications of this in
terms of block availability and auditability. This functionality may be added in the future.
For details on the specific ABCI calls and types, see the [methods and types section](abci.md).
### Taking Snapshots
Applications that want to support state syncing must take state snapshots at regular intervals. How
this is accomplished is entirely up to the application. A snapshot consists of some metadata and
a set of binary chunks in an arbitrary format:
- `Height (uint64)`: The height at which the snapshot is taken. It must be taken after the given
height has been committed, and must not contain data from any later heights.
- `Format (uint32)`: An arbitrary snapshot format identifier. This can be used to version snapshot
formats, e.g. to switch from Protobuf to MessagePack for serialization. The application can use
this when restoring to choose whether to accept or reject a snapshot.
- `Chunks (uint32)`: The number of chunks in the snapshot. Each chunk contains arbitrary binary
data, and should be less than 16 MB; 10 MB is a good starting point.
- `Hash ([]byte)`: An arbitrary hash of the snapshot. This is used to check whether a snapshot is
the same across nodes when downloading chunks.
- `Metadata ([]byte)`: Arbitrary snapshot metadata, e.g. chunk hashes for verification or any other
necessary info.
For a snapshot to be considered the same across nodes, all of these fields must be identical. When
sent across the network, snapshot metadata messages are limited to 4 MB.
When a new node is running state sync and discovering snapshots, Tendermint will query an existing
application via the ABCI `ListSnapshots` method to discover available snapshots, and load binary
snapshot chunks via `LoadSnapshotChunk`. The application is free to choose how to implement this
and which formats to use, but must provide the following guarantees:
- **Consistent:** A snapshot must be taken at a single isolated height, unaffected by
concurrent writes. This can be accomplished by using a data store that supports ACID
transactions with snapshot isolation.
- **Asynchronous:** Taking a snapshot can be time-consuming, so it must not halt chain progress,
for example by running in a separate thread.
- **Deterministic:** A snapshot taken at the same height in the same format must be identical
(at the byte level) across nodes, including all metadata. This ensures good availability of
chunks, and that they fit together across nodes.
A very basic approach might be to use a datastore with MVCC transactions (such as RocksDB),
start a transaction immediately after block commit, and spawn a new thread which is passed the
transaction handle. This thread can then export all data items, serialize them using e.g.
Protobuf, hash the byte stream, split it into chunks, and store the chunks in the file system
along with some metadata - all while the blockchain is applying new blocks in parallel.
A more advanced approach might include incremental verification of individual chunks against the
chain app hash, parallel or batched exports, compression, and so on.
Old snapshots should be removed after some time - generally only the last two snapshots are needed
(to prevent the last one from being removed while a node is restoring it).
### Bootstrapping a Node
An empty node can be state synced by setting the configuration option `statesync.enabled =
true`. The node also needs the chain genesis file for basic chain info, and configuration for
light client verification of the restored snapshot: a set of Tendermint RPC servers, and a
trusted header hash and corresponding height from a trusted source, via the `statesync`
configuration section.
Once started, the node will connect to the P2P network and begin discovering snapshots. These
will be offered to the local application via the `OfferSnapshot` ABCI method. Once a snapshot
is accepted Tendermint will fetch and apply the snapshot chunks. After all chunks have been
successfully applied, Tendermint verifies the app's `AppHash` against the chain using the light
client, then switches the node to normal consensus operation.
#### Snapshot Discovery
When the empty node join the P2P network, it asks all peers to report snapshots via the
`ListSnapshots` ABCI call (limited to 10 per node). After some time, the node picks the most
suitable snapshot (generally prioritized by height, format, and number of peers), and offers it
to the application via `OfferSnapshot`. The application can choose a number of responses,
including accepting or rejecting it, rejecting the offered format, rejecting the peer who sent
it, and so on. Tendermint will keep discovering and offering snapshots until one is accepted or
the application aborts.
#### Snapshot Restoration
Once a snapshot has been accepted via `OfferSnapshot`, Tendermint begins downloading chunks from
any peers that have the same snapshot (i.e. that have identical metadata fields). Chunks are
spooled in a temporary directory, and then given to the application in sequential order via
`ApplySnapshotChunk` until all chunks have been accepted.
The method for restoring snapshot chunks is entirely up to the application.
During restoration, the application can respond to `ApplySnapshotChunk` with instructions for how
to continue. This will typically be to accept the chunk and await the next one, but it can also
ask for chunks to be refetched (either the current one or any number of previous ones), P2P peers
to be banned, snapshots to be rejected or retried, and a number of other responses - see the ABCI
reference for details.
If Tendermint fails to fetch a chunk after some time, it will reject the snapshot and try a
different one via `OfferSnapshot` - the application can choose whether it wants to support
restarting restoration, or simply abort with an error.
#### Snapshot Verification
Once all chunks have been accepted, Tendermint issues an `Info` ABCI call to retrieve the
`LastBlockAppHash`. This is compared with the trusted app hash from the chain, retrieved and
verified using the light client. Tendermint also checks that `LastBlockHeight` corresponds to the
height of the snapshot.
This verification ensures that an application is valid before joining the network. However, the
snapshot restoration may take a long time to complete, so applications may want to employ additional
verification during the restore to detect failures early. This might e.g. include incremental
verification of each chunk against the app hash (using bundled Merkle proofs), checksums to
protect against data corruption by the disk or network, and so on. However, it is important to
note that the only trusted information available is the app hash, and all other snapshot metadata
can be spoofed by adversaries.
Apps may also want to consider state sync denial-of-service vectors, where adversaries provide
invalid or harmful snapshots to prevent nodes from joining the network. The application can
counteract this by asking Tendermint to ban peers. As a last resort, node operators can use
P2P configuration options to whitelist a set of trusted peers that can provide valid snapshots.
#### Transition to Consensus
Once the snapshots have all been restored, Tendermint gathers additional information necessary for
bootstrapping the node (e.g. chain ID, consensus parameters, validator sets, and block headers)
from the genesis file and light client RPC servers. It also fetches and records the `AppVersion`
from the ABCI application.
Once the state machine has been restored and Tendermint has gathered this additional
information, it transitions to block sync (if enabled) to fetch any remaining blocks up the chain
head, and then transitions to regular consensus operation. At this point the node operates like
any other node, apart from having a truncated block history at the height of the restored snapshot.

+ 113
- 0
spec/abci/client-server.md View File

@ -0,0 +1,113 @@
---
order: 3
title: Client and Server
---
# Client and Server
This section is for those looking to implement their own ABCI Server, perhaps in
a new programming language.
You are expected to have read [ABCI Methods and Types](./abci.md) and [ABCI
Applications](./apps.md).
## Message Protocol
The message protocol consists of pairs of requests and responses defined in the
[protobuf file](../../proto/tendermint/abci/types.proto).
Some messages have no fields, while others may include byte-arrays, strings, integers,
or custom protobuf types.
For more details on protobuf, see the [documentation](https://developers.google.com/protocol-buffers/docs/overview).
For each request, a server should respond with the corresponding
response, where the order of requests is preserved in the order of
responses.
## Server Implementations
To use ABCI in your programming language of choice, there must be a ABCI
server in that language. Tendermint supports three implementations of the ABCI, written in Go:
- In-process ([Golang](https://github.com/tendermint/tendermint/tree/master/abci), [Rust](https://github.com/tendermint/rust-abci))
- ABCI-socket
- GRPC
The latter two can be tested using the `abci-cli` by setting the `--abci` flag
appropriately (ie. to `socket` or `grpc`).
See examples, in various stages of maintenance, in
[Go](https://github.com/tendermint/tendermint/tree/master/abci/server),
[JavaScript](https://github.com/tendermint/js-abci),
[C++](https://github.com/mdyring/cpp-tmsp), and
[Java](https://github.com/jTendermint/jabci).
### In Process
The simplest implementation uses function calls within Golang.
This means ABCI applications written in Golang can be compiled with Tendermint Core and run as a single binary.
### GRPC
If GRPC is available in your language, this is the easiest approach,
though it will have significant performance overhead.
To get started with GRPC, copy in the [protobuf
file](../../proto/tendermint/abci/types.proto) and compile it using the GRPC
plugin for your language. For instance, for golang, the command is `protoc
--go_out=plugins=grpc:. types.proto`. See the [grpc documentation for more
details](http://www.grpc.io/docs/). `protoc` will autogenerate all the
necessary code for ABCI client and server in your language, including whatever
interface your application must satisfy to be used by the ABCI server for
handling requests.
Note the length-prefixing used in the socket implementation (TSP) does not apply for GRPC.
### TSP
Tendermint Socket Protocol is an asynchronous, raw socket server which provides ordered message passing over unix or tcp.
Messages are serialized using Protobuf3 and length-prefixed with a [signed Varint](https://developers.google.com/protocol-buffers/docs/encoding?csw=1#signed-integers)
If GRPC is not available in your language, or you require higher
performance, or otherwise enjoy programming, you may implement your own
ABCI server using the Tendermint Socket Protocol. The first step is still to auto-generate the relevant data
types and codec in your language using `protoc`. In addition to being proto3 encoded, messages coming over
the socket are length-prefixed to facilitate use as a streaming protocol. proto3 doesn't have an
official length-prefix standard, so we use our own. The first byte in
the prefix represents the length of the Big Endian encoded length. The
remaining bytes in the prefix are the Big Endian encoded length.
For example, if the proto3 encoded ABCI message is 0xDEADBEEF (4
bytes), the length-prefixed message is 0x0104DEADBEEF. If the proto3
encoded ABCI message is 65535 bytes long, the length-prefixed message
would be like 0x02FFFF....
The benefit of using this `varint` encoding over the old version (where integers were encoded as `<len of len><big endian len>` is that
it is the standard way to encode integers in Protobuf. It is also generally shorter.
As noted above, this prefixing does not apply for GRPC.
An ABCI server must also be able to support multiple connections, as
Tendermint uses four connections.
### Async vs Sync
The main ABCI server (ie. non-GRPC) provides ordered asynchronous messages.
This is useful for DeliverTx and CheckTx, since it allows Tendermint to forward
transactions to the app before it's finished processing previous ones.
Thus, DeliverTx and CheckTx messages are sent asynchronously, while all other
messages are sent synchronously.
## Client
There are currently two use-cases for an ABCI client. One is a testing
tool, as in the `abci-cli`, which allows ABCI requests to be sent via
command line. The other is a consensus engine, such as Tendermint Core,
which makes requests to the application every time a new transaction is
received or a block is committed.
It is unlikely that you will need to implement a client. For details of
our client, see
[here](https://github.com/tendermint/tendermint/tree/master/abci/client).

+ 55
- 0
spec/consensus/bft-time.md View File

@ -0,0 +1,55 @@
---
order: 2
---
# BFT Time
Tendermint provides a deterministic, Byzantine fault-tolerant, source of time.
Time in Tendermint is defined with the Time field of the block header.
It satisfies the following properties:
- Time Monotonicity: Time is monotonically increasing, i.e., given
a header H1 for height h1 and a header H2 for height `h2 = h1 + 1`, `H1.Time < H2.Time`.
- Time Validity: Given a set of Commit votes that forms the `block.LastCommit` field, a range of
valid values for the Time field of the block header is defined only by
Precommit messages (from the LastCommit field) sent by correct processes, i.e.,
a faulty process cannot arbitrarily increase the Time value.
In the context of Tendermint, time is of type int64 and denotes UNIX time in milliseconds, i.e.,
corresponds to the number of milliseconds since January 1, 1970. Before defining rules that need to be enforced by the
Tendermint consensus protocol, so the properties above holds, we introduce the following definition:
- median of a Commit is equal to the median of `Vote.Time` fields of the `Vote` messages,
where the value of `Vote.Time` is counted number of times proportional to the process voting power. As in Tendermint
the voting power is not uniform (one process one vote), a vote message is actually an aggregator of the same votes whose
number is equal to the voting power of the process that has casted the corresponding votes message.
Let's consider the following example:
- we have four processes p1, p2, p3 and p4, with the following voting power distribution (p1, 23), (p2, 27), (p3, 10)
and (p4, 10). The total voting power is 70 (`N = 3f+1`, where `N` is the total voting power, and `f` is the maximum voting
power of the faulty processes), so we assume that the faulty processes have at most 23 of voting power.
Furthermore, we have the following vote messages in some LastCommit field (we ignore all fields except Time field):
- (p1, 100), (p2, 98), (p3, 1000), (p4, 500). We assume that p3 and p4 are faulty processes. Let's assume that the
`block.LastCommit` message contains votes of processes p2, p3 and p4. Median is then chosen the following way:
the value 98 is counted 27 times, the value 1000 is counted 10 times and the value 500 is counted also 10 times.
So the median value will be the value 98. No matter what set of messages with at least `2f+1` voting power we
choose, the median value will always be between the values sent by correct processes.
We ensure Time Monotonicity and Time Validity properties by the following rules:
- let rs denotes `RoundState` (consensus internal state) of some process. Then
`rs.ProposalBlock.Header.Time == median(rs.LastCommit) &&
rs.Proposal.Timestamp == rs.ProposalBlock.Header.Time`.
- Furthermore, when creating the `vote` message, the following rules for determining `vote.Time` field should hold:
- if `rs.LockedBlock` is defined then
`vote.Time = max(rs.LockedBlock.Timestamp + time.Millisecond, time.Now())`, where `time.Now()`
denotes local Unix time in milliseconds
- else if `rs.Proposal` is defined then
`vote.Time = max(rs.Proposal.Timestamp + time.Millisecond,, time.Now())`,
- otherwise, `vote.Time = time.Now())`. In this case vote is for `nil` so it is not taken into account for
the timestamp of the next block.

+ 2417
- 0
spec/consensus/consensus-paper/IEEEtran.bst
File diff suppressed because it is too large
View File


+ 4733
- 0
spec/consensus/consensus-paper/IEEEtran.cls
File diff suppressed because it is too large
View File


+ 24
- 0
spec/consensus/consensus-paper/README.md View File

@ -0,0 +1,24 @@
# Tendermint-spec
The repository contains the specification (and the proofs) of the Tendermint
consensus protocol.
## How to install Latex on Mac OS
MacTex is Latex distribution for Mac OS. You can download it [here](http://www.tug.org/mactex/mactex-download.html).
Popular IDE for Latex-based projects is TexStudio. It can be downloaded
[here](https://www.texstudio.org/).
## How to build project
In order to compile the latex files (and write bibliography), execute
`$ pdflatex paper` <br/>
`$ bibtex paper` <br/>
`$ pdflatex paper` <br/>
`$ pdflatex paper` <br/>
The generated file is paper.pdf. You can open it with
`$ open paper.pdf`

+ 195
- 0
spec/consensus/consensus-paper/algorithmicplus.sty View File

@ -0,0 +1,195 @@
% ALGORITHMICPLUS STYLE
% for LaTeX version 2e
% Original ``algorithmic.sty'' by -- 1994 Peter Williams <pwil3058@bigpond.net.au>
% Bug fix (13 July 2004) by Arnaud Giersch <giersch@icps.u-strasbg.fr>
% Includes ideas from 'algorithmicext' by Martin Biely
% <biely@ecs.tuwien.ac.at> and 'distribalgo' by Xavier Defago
% Modifications: Martin Hutle <martin.hutle@epfl.ch>
%
% This style file is free software; you can redistribute it and/or
% modify it under the terms of the GNU Lesser General Public
% License as published by the Free Software Foundation; either
% version 2 of the License, or (at your option) any later version.
%
% This style file is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
% Lesser General Public License for more details.
%
% You should have received a copy of the GNU Lesser General Public
% License along with this style file; if not, write to the
% Free Software Foundation, Inc., 59 Temple Place - Suite 330,
% Boston, MA 02111-1307, USA.
%
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{algorithmicplus}
\typeout{Document Style `algorithmicplus' - environment, replaces `algorithmic'}
%
\RequirePackage{ifthen}
\RequirePackage{calc}
\newboolean{ALC@noend}
\setboolean{ALC@noend}{false}
\newcounter{ALC@line}
\newcounter{ALC@rem}
\newcounter{ALC@depth}
\newcounter{ALCPLUS@lastline}
\newlength{\ALC@tlm}
%
\DeclareOption{noend}{\setboolean{ALC@noend}{true}}
%
\ProcessOptions
%
% ALGORITHMIC
\newcommand{\algorithmiclnosize}{\small}
\newcommand{\algorithmiclnofont}{\tt}
\newcommand{\algorithmiclnodelimiter}{:}
%
\newcommand{\algorithmicrequire}{\textbf{Require:}}
\newcommand{\algorithmicensure}{\textbf{Ensure:}}
\newcommand{\algorithmiccomment}[1]{\{#1\}}
\newcommand{\algorithmicend}{\textbf{end}}
\newcommand{\algorithmicif}{\textbf{if}}
\newcommand{\algorithmicthen}{\textbf{then}}
\newcommand{\algorithmicelse}{\textbf{else}}
\newcommand{\algorithmicelsif}{\algorithmicelse\ \algorithmicif}
\newcommand{\algorithmicendif}{\algorithmicend\ \algorithmicif}
\newcommand{\algorithmicfor}{\textbf{for}}
\newcommand{\algorithmicforall}{\textbf{for all}}
\newcommand{\algorithmicdo}{\textbf{do}}
\newcommand{\algorithmicendfor}{\algorithmicend\ \algorithmicfor}
\newcommand{\algorithmicwhile}{\textbf{while}}
\newcommand{\algorithmicendwhile}{\algorithmicend\ \algorithmicwhile}
\newcommand{\algorithmicloop}{\textbf{loop}}
\newcommand{\algorithmicendloop}{\algorithmicend\ \algorithmicloop}
\newcommand{\algorithmicrepeat}{\textbf{repeat}}
\newcommand{\algorithmicuntil}{\textbf{until}}
\def\ALC@item[#1]{%
\if@noparitem \@donoparitem
\else \if@inlabel \indent \par \fi
\ifhmode \unskip\unskip \par \fi
\if@newlist \if@nobreak \@nbitem \else
\addpenalty\@beginparpenalty
\addvspace\@topsep \addvspace{-\parskip}\fi
\else \addpenalty\@itempenalty \addvspace\itemsep
\fi
\global\@inlabeltrue
\fi
\everypar{\global\@minipagefalse\global\@newlistfalse
\if@inlabel\global\@inlabelfalse \hskip -\parindent \box\@labels
\penalty\z@ \fi
\everypar{}}\global\@nobreakfalse
\if@noitemarg \@noitemargfalse \if@nmbrlist \refstepcounter{\@listctr}\fi \fi
\sbox\@tempboxa{\makelabel{#1}}%
\global\setbox\@labels
\hbox{\unhbox\@labels \hskip \itemindent
\hskip -\labelwidth \hskip -\ALC@tlm
\ifdim \wd\@tempboxa >\labelwidth
\box\@tempboxa
\else \hbox to\labelwidth {\unhbox\@tempboxa}\fi
\hskip \ALC@tlm}\ignorespaces}
%
\newenvironment{algorithmic}[1][0]{
\setcounter{ALC@depth}{\@listdepth}%
\let\@listdepth\c@ALC@depth%
\let\@item\ALC@item
\newcommand{\ALC@lno}{%
\ifthenelse{\equal{\arabic{ALC@rem}}{0}}
{{\algorithmiclnosize\algorithmiclnofont \arabic{ALC@line}\algorithmiclnodelimiter}}{}%
}
\let\@listii\@listi
\let\@listiii\@listi
\let\@listiv\@listi
\let\@listv\@listi
\let\@listvi\@listi
\let\@listvii\@listi
\newenvironment{ALC@g}{
\begin{list}{\ALC@lno}{ \itemsep\z@ \itemindent\z@
\listparindent\z@ \rightmargin\z@
\topsep\z@ \partopsep\z@ \parskip\z@\parsep\z@
\leftmargin 1em
\addtolength{\ALC@tlm}{\leftmargin}
}
}
{\end{list}}
\newcommand{\ALC@it}{\refstepcounter{ALC@line}\addtocounter{ALC@rem}{1}\ifthenelse{\equal{\arabic{ALC@rem}}{#1}}{\setcounter{ALC@rem}{0}}{}\item}
\newcommand{\ALC@com}[1]{\ifthenelse{\equal{##1}{default}}%
{}{\ \algorithmiccomment{##1}}}
\newcommand{\REQUIRE}{\item[\algorithmicrequire]}
\newcommand{\ENSURE}{\item[\algorithmicensure]}
\newcommand{\STATE}{\ALC@it}
\newcommand{\COMMENT}[1]{\algorithmiccomment{##1}}
\newenvironment{ALC@if}{\begin{ALC@g}}{\end{ALC@g}}
\newenvironment{ALC@for}{\begin{ALC@g}}{\end{ALC@g}}
\newenvironment{ALC@whl}{\begin{ALC@g}}{\end{ALC@g}}
\newenvironment{ALC@loop}{\begin{ALC@g}}{\end{ALC@g}}
\newenvironment{ALC@rpt}{\begin{ALC@g}}{\end{ALC@g}}
\renewcommand{\\}{\@centercr}
\newcommand{\IF}[2][default]{\ALC@it\algorithmicif\ ##2\ \algorithmicthen%
\ALC@com{##1}\begin{ALC@if}}
\newcommand{\ELSE}[1][default]{\end{ALC@if}\ALC@it\algorithmicelse%
\ALC@com{##1}\begin{ALC@if}}
\newcommand{\ELSIF}[2][default]%
{\end{ALC@if}\ALC@it\algorithmicelsif\ ##2\ \algorithmicthen%
\ALC@com{##1}\begin{ALC@if}}
\newcommand{\FOR}[2][default]{\ALC@it\algorithmicfor\ ##2\ \algorithmicdo%
\ALC@com{##1}\begin{ALC@for}}
\newcommand{\FORALL}[2][default]{\ALC@it\algorithmicforall\ ##2\ %
\algorithmicdo%
\ALC@com{##1}\begin{ALC@for}}
\newcommand{\WHILE}[2][default]{\ALC@it\algorithmicwhile\ ##2\ %
\algorithmicdo%
\ALC@com{##1}\begin{ALC@whl}}
\newcommand{\LOOP}[1][default]{\ALC@it\algorithmicloop%
\ALC@com{##1}\begin{ALC@loop}}
\newcommand{\REPEAT}[1][default]{\ALC@it\algorithmicrepeat%
\ALC@com{##1}\begin{ALC@rpt}}
\newcommand{\UNTIL}[1]{\end{ALC@rpt}\ALC@it\algorithmicuntil\ ##1}
\ifthenelse{\boolean{ALC@noend}}{
\newcommand{\ENDIF}{\end{ALC@if}}
\newcommand{\ENDFOR}{\end{ALC@for}}
\newcommand{\ENDWHILE}{\end{ALC@whl}}
\newcommand{\ENDLOOP}{\end{ALC@loop}}
}{
\newcommand{\ENDIF}{\end{ALC@if}\ALC@it\algorithmicendif}
\newcommand{\ENDFOR}{\end{ALC@for}\ALC@it\algorithmicendfor}
\newcommand{\ENDWHILE}{\end{ALC@whl}\ALC@it\algorithmicendwhile}
\newcommand{\ENDLOOP}{\end{ALC@loop}\ALC@it\algorithmicendloop}
}
\renewcommand{\@toodeep}{}
\begin{list}{\ALC@lno}{\setcounter{ALC@line}{0}\setcounter{ALC@rem}{0}%
\itemsep\z@ \itemindent\z@ \listparindent\z@%
\partopsep\z@ \parskip\z@ \parsep\z@%
\labelsep 0.5em \topsep 0.2em%
\ifthenelse{\equal{#1}{0}}
{\labelwidth 0.5em }
{\labelwidth 1.2em }
\leftmargin\labelwidth \addtolength{\leftmargin}{\labelsep}
\ALC@tlm\labelsep
}
}
{%
\setcounter{ALCPLUS@lastline}{\value{ALC@line}}%
\end{list}}
\newcommand{\continuecounting}{\setcounter{ALC@line}{\value{ALCPLUS@lastline}}}
\newcommand{\startcounting}[1]{\setcounter{ALC@line}{#1}\addtocounter{ALC@line}{-1}}
\newcommand{\EMPTY}{\item[]}
\newcommand{\SPACE}{\vspace{3mm}}
\newcommand{\SHORTSPACE}{\vspace{1mm}}
\newcommand{\newlinetag}[3]{\newcommand{#1}[#2]{\item[#3]}}
\newcommand{\newconstruct}[5]{%
\newenvironment{ALC@\string#1}{\begin{ALC@g}}{\end{ALC@g}}
\newcommand{#1}[2][default]{\ALC@it#2\ ##2\ #3%
\ALC@com{##1}\begin{ALC@\string#1}}
\ifthenelse{\boolean{ALC@noend}}{
\newcommand{#4}{\end{ALC@\string#1}}
}{
\newcommand{#4}{\end{ALC@\string#1}\ALC@it#5}
}
}
\newconstruct{\INDENT}{}{}{\ENDINDENT}{}
\newcommand{\setlinenosize}[1]{\renewcommand{\algorithmiclnosize}{#1}}
\newcommand{\setlinenofont}[1]{\renewcommand{\algorithmiclnofont}{#1}}

+ 16
- 0
spec/consensus/consensus-paper/conclusion.tex View File

@ -0,0 +1,16 @@
\section{Conclusion} \label{sec:conclusion}
We have proposed a new Byzantine-fault tolerant consensus algorithm that is the
core of the Tendermint BFT SMR platform. The algorithm is designed for the wide
area network with high number of mutually distrusted nodes that communicate
over gossip based peer-to-peer network. It has only a single mode of execution
and the communication pattern is very similar to the "normal" case of the
state-of-the art PBFT algorithm. The algorithm ensures termination with a novel
mechanism that takes advantage of the gossip based communication between nodes.
The proposed algorithm and the proofs are simple and elegant, and we believe
that this makes it easier to understand and implement correctly.
\section*{Acknowledgment}
We would like to thank Anton Kaliaev, Ismail Khoffi and Dahlia Malkhi for comments on an earlier version of the paper. We also want to thank Marko Vukolic, Ming Chuan Lin, Maria Potop-Butucaru, Sara Tucci, Antonella Del Pozzo and Yackolley Amoussou-Guenou for pointing out the liveness issues
in the previous version of the algorithm. Finally, we want to thank the Tendermint team members and all project contributors for making Tendermint such a great platform.

+ 397
- 0
spec/consensus/consensus-paper/consensus.tex View File

@ -0,0 +1,397 @@
\section{Tendermint consensus algorithm} \label{sec:tendermint}
\newcommand\Disseminate{\textbf{Disseminate}}
\newcommand\Proposal{\mathsf{PROPOSAL}}
\newcommand\ProposalPart{\mathsf{PROPOSAL\mbox{-}PART}}
\newcommand\PrePrepare{\mathsf{INIT}} \newcommand\Prevote{\mathsf{PREVOTE}}
\newcommand\Precommit{\mathsf{PRECOMMIT}}
\newcommand\Decision{\mathsf{DECISION}}
\newcommand\ViewChange{\mathsf{VC}}
\newcommand\ViewChangeAck{\mathsf{VC\mbox{-}ACK}}
\newcommand\NewPrePrepare{\mathsf{VC\mbox{-}INIT}}
\newcommand\coord{\mathsf{proposer}}
\newcommand\newHeight{newHeight} \newcommand\newRound{newRound}
\newcommand\nil{nil} \newcommand\id{id} \newcommand{\propose}{propose}
\newcommand\prevote{prevote} \newcommand\prevoteWait{prevoteWait}
\newcommand\precommit{precommit} \newcommand\precommitWait{precommitWait}
\newcommand\commit{commit}
\newcommand\timeoutPropose{timeoutPropose}
\newcommand\timeoutPrevote{timeoutPrevote}
\newcommand\timeoutPrecommit{timeoutPrecommit}
\newcommand\proofOfLocking{proof\mbox{-}of\mbox{-}locking}
\begin{algorithm}[htb!] \def\baselinestretch{1} \scriptsize\raggedright
\begin{algorithmic}[1]
\SHORTSPACE
\INIT{}
\STATE $h_p := 0$
\COMMENT{current height, or consensus instance we are currently executing}
\STATE $round_p := 0$ \COMMENT{current round number}
\STATE $step_p \in \set{\propose, \prevote, \precommit}$
\STATE $decision_p[] := nil$
\STATE $lockedValue_p := nil$
\STATE $lockedRound_p := -1$
\STATE $validValue_p := nil$
\STATE $validRound_p := -1$
\ENDINIT
\SHORTSPACE
\STATE \textbf{upon} start \textbf{do} $StartRound(0)$
\SHORTSPACE
\FUNCTION{$StartRound(round)$} \label{line:tab:startRound}
\STATE $round_p \assign round$
\STATE $step_p \assign \propose$
\IF{$\coord(h_p, round_p) = p$}
\IF{$validValue_p \neq \nil$} \label{line:tab:isThereLockedValue}
\STATE $proposal \assign validValue_p$ \ELSE \STATE $proposal \assign
getValue()$
\label{line:tab:getValidValue}
\ENDIF
\STATE \Broadcast\ $\li{\Proposal,h_p, round_p, proposal, validRound_p}$
\label{line:tab:send-proposal}
\ELSE
\STATE \textbf{schedule} $OnTimeoutPropose(h_p,
round_p)$ to be executed \textbf{after} $\timeoutPropose(round_p)$
\ENDIF
\ENDFUNCTION
\SPACE
\UPON{$\li{\Proposal,h_p,round_p, v, -1}$ \From\ $\coord(h_p,round_p)$
\With\ $step_p = \propose$} \label{line:tab:recvProposal}
\IF{$valid(v) \wedge (lockedRound_p = -1 \vee lockedValue_p = v$)}
\label{line:tab:accept-proposal-2}
\STATE \Broadcast \ $\li{\Prevote,h_p,round_p,id(v)}$
\label{line:tab:prevote-proposal}
\ELSE
\label{line:tab:acceptProposal1}
\STATE \Broadcast \ $\li{\Prevote,h_p,round_p,\nil}$
\label{line:tab:prevote-nil}
\ENDIF
\STATE $step_p \assign \prevote$ \label{line:tab:setStateToPrevote1}
\ENDUPON
\SPACE
\UPON{$\li{\Proposal,h_p,round_p, v, vr}$ \From\ $\coord(h_p,round_p)$
\textbf{AND} $2f+1$ $\li{\Prevote,h_p, vr,id(v)}$ \With\ $step_p = \propose \wedge (vr \ge 0 \wedge vr < round_p)$}
\label{line:tab:acceptProposal}
\IF{$valid(v) \wedge (lockedRound_p \le vr
\vee lockedValue_p = v)$} \label{line:tab:cond-prevote-higher-proposal}
\STATE \Broadcast \ $\li{\Prevote,h_p,round_p,id(v)}$
\label{line:tab:prevote-higher-proposal}
\ELSE
\label{line:tab:acceptProposal2}
\STATE \Broadcast \ $\li{\Prevote,h_p,round_p,\nil}$
\label{line:tab:prevote-nil2}
\ENDIF
\STATE $step_p \assign \prevote$ \label{line:tab:setStateToPrevote3}
\ENDUPON
\SPACE
\UPON{$2f+1$ $\li{\Prevote,h_p, round_p,*}$ \With\ $step_p = \prevote$ for the first time}
\label{line:tab:recvAny2/3Prevote}
\STATE \textbf{schedule} $OnTimeoutPrevote(h_p, round_p)$ to be executed \textbf{after} $\timeoutPrevote(round_p)$ \label{line:tab:timeoutPrevote}
\ENDUPON
\SPACE
\UPON{$\li{\Proposal,h_p,round_p, v, *}$ \From\ $\coord(h_p,round_p)$
\textbf{AND} $2f+1$ $\li{\Prevote,h_p, round_p,id(v)}$ \With\ $valid(v) \wedge step_p \ge \prevote$ for the first time}
\label{line:tab:recvPrevote}
\IF{$step_p = \prevote$}
\STATE $lockedValue_p \assign v$ \label{line:tab:setLockedValue}
\STATE $lockedRound_p \assign round_p$ \label{line:tab:setLockedRound}
\STATE \Broadcast \ $\li{\Precommit,h_p,round_p,id(v))}$
\label{line:tab:precommit-v}
\STATE $step_p \assign \precommit$ \label{line:tab:setStateToCommit}
\ENDIF
\STATE $validValue_p \assign v$ \label{line:tab:setValidRound}
\STATE $validRound_p \assign round_p$ \label{line:tab:setValidValue}
\ENDUPON
\SHORTSPACE
\UPON{$2f+1$ $\li{\Prevote,h_p,round_p, \nil}$
\With\ $step_p = \prevote$}
\STATE \Broadcast \ $\li{\Precommit,h_p,round_p, \nil}$
\label{line:tab:precommit-v-1}
\STATE $step_p \assign \precommit$
\ENDUPON
\SPACE
\UPON{$2f+1$ $\li{\Precommit,h_p,round_p,*}$ for the first time}
\label{line:tab:startTimeoutPrecommit}
\STATE \textbf{schedule} $OnTimeoutPrecommit(h_p, round_p)$ to be executed \textbf{after} $\timeoutPrecommit(round_p)$
\ENDUPON
\SPACE
\UPON{$\li{\Proposal,h_p,r, v, *}$ \From\ $\coord(h_p,r)$ \textbf{AND}
$2f+1$ $\li{\Precommit,h_p,r,id(v)}$ \With\ $decision_p[h_p] = \nil$}
\label{line:tab:onDecideRule}
\IF{$valid(v)$} \label{line:tab:validDecisionValue}
\STATE $decision_p[h_p] = v$ \label{line:tab:decide}
\STATE$h_p \assign h_p + 1$ \label{line:tab:increaseHeight}
\STATE reset $lockedRound_p$, $lockedValue_p$, $validRound_p$ and $validValue_p$ to initial values
and empty message log
\STATE $StartRound(0)$
\ENDIF
\ENDUPON
\SHORTSPACE
\UPON{$f+1$ $\li{*,h_p,round, *, *}$ \textbf{with} $round > round_p$}
\label{line:tab:skipRounds}
\STATE $StartRound(round)$ \label{line:tab:nextRound2}
\ENDUPON
\SHORTSPACE
\FUNCTION{$OnTimeoutPropose(height,round)$} \label{line:tab:onTimeoutPropose}
\IF{$height = h_p \wedge round = round_p \wedge step_p = \propose$}
\STATE \Broadcast \ $\li{\Prevote,h_p,round_p, \nil}$
\label{line:tab:prevote-nil-on-timeout}
\STATE $step_p \assign \prevote$
\ENDIF
\ENDFUNCTION
\SHORTSPACE
\FUNCTION{$OnTimeoutPrevote(height,round)$} \label{line:tab:onTimeoutPrevote}
\IF{$height = h_p \wedge round = round_p \wedge step_p = \prevote$}
\STATE \Broadcast \ $\li{\Precommit,h_p,round_p,\nil}$
\label{line:tab:precommit-nil-onTimeout}
\STATE $step_p \assign \precommit$
\ENDIF
\ENDFUNCTION
\SHORTSPACE
\FUNCTION{$OnTimeoutPrecommit(height,round)$} \label{line:tab:onTimeoutPrecommit}
\IF{$height = h_p \wedge round = round_p$}
\STATE $StartRound(round_p + 1)$ \label{line:tab:nextRound}
\ENDIF
\ENDFUNCTION
\end{algorithmic} \caption{Tendermint consensus algorithm}
\label{alg:tendermint}
\end{algorithm}
In this section we present the Tendermint Byzantine fault-tolerant consensus
algorithm. The algorithm is specified by the pseudo-code shown in
Algorithm~\ref{alg:tendermint}. We present the algorithm as a set of \emph{upon
rules} that are executed atomically\footnote{In case several rules are active
at the same time, the first rule to be executed is picked randomly. The
correctness of the algorithm does not depend on the order in which rules are
executed.}. We assume that processes exchange protocol messages using a gossip
protocol and that both sent and received messages are stored in a local message
log for every process. An upon rule is triggered once the message log contains
messages such that the corresponding condition evaluates to $\tt{true}$. The
condition that assumes reception of $X$ messages of a particular type and
content denotes reception of messages whose senders have aggregate voting power at
least equal to $X$. For example, the condition $2f+1$ $\li{\Precommit,h_p,r,id(v)}$,
evaluates to true upon reception of $\Precommit$ messages for height $h_p$,
a round $r$ and with value equal to $id(v)$ whose senders have aggregate voting
power at least equal to $2f+1$. Some of the rules ends with "for the first time" constraint
to denote that it is triggered only the first time a corresponding condition evaluates
to $\tt{true}$. This is because those rules do not always change the state of algorithm
variables so without this constraint, the algorithm could keep
executing those rules forever. The variables with index $p$ are process local state
variables, while variables without index $p$ are value placeholders. The sign
$*$ denotes any value.
We denote with $n$ the total voting power of processes in the system, and we
assume that the total voting power of faulty processes in the system is bounded
with a system parameter $f$. The algorithm assumes that $n > 3f$, i.e., it
requires that the total voting power of faulty processes is smaller than one
third of the total voting power. For simplicity we present the algorithm for
the case $n = 3f + 1$.
The algorithm proceeds in rounds, where each round has a dedicated
\emph{proposer}. The mapping of rounds to proposers is known to all processes
and is given as a function $\coord(h, round)$, returning the proposer for
the round $round$ in the consensus instance $h$. We
assume that the proposer selection function is weighted round-robin, where
processes are rotated proportional to their voting power\footnote{A validator
with more voting power is selected more frequently, proportional to its power.
More precisely, during a sequence of rounds of size $n$, every process is
proposer in a number of rounds equal to its voting power.}.
The internal protocol state transitions are triggered by message reception and
by expiration of timeouts. There are three timeouts in Algorithm \ref{alg:tendermint}:
$\timeoutPropose$, $\timeoutPrevote$ and $\timeoutPrecommit$.
The timeouts prevent the algorithm from blocking and
waiting forever for some condition to be true, ensure that processes continuously
transition between rounds, and guarantee that eventually (after GST) communication
between correct processes is timely and reliable so they can decide.
The last role is achieved by increasing the timeouts with every new round $r$,
i.e, $timeoutX(r) = initTimeoutX + r*timeoutDelta$;
they are reset for every new height (consensus
instance).
Processes exchange the following messages in Tendermint: $\Proposal$,
$\Prevote$ and $\Precommit$. The $\Proposal$ message is used by the proposer of
the current round to suggest a potential decision value, while $\Prevote$ and
$\Precommit$ are votes for a proposed value. According to the classification of
consensus algorithms from \cite{RMS10:dsn}, Tendermint, like PBFT
\cite{CL02:tcs} and DLS \cite{DLS88:jacm}, belongs to class 3, so it requires
two voting steps (three communication exchanges in total) to decide a value.
The Tendermint consensus algorithm is designed for the blockchain context where
the value to decide is a block of transactions (ie. it is potentially quite
large, consisting of many transactions). Therefore, in the Algorithm
\ref{alg:tendermint} (similar as in \cite{CL02:tcs}) we are explicit about
sending a value (block of transactions) and a small, constant size value id (a
unique value identifier, normally a hash of the value, i.e., if $\id(v) =
\id(v')$, then $v=v'$). The $\Proposal$ message is the only one carrying the
value; $\Prevote$ and $\Precommit$ messages carry the value id. A correct
process decides on a value $v$ in Tendermint upon receiving the $\Proposal$ for
$v$ and $2f+1$ voting-power equivalent $\Precommit$ messages for $\id(v)$ in
some round $r$. In order to send $\Precommit$ message for $v$ in a round $r$, a
correct process waits to receive the $\Proposal$ and $2f+1$ of the
corresponding $\Prevote$ messages in the round $r$. Otherwise,
it sends $\Precommit$ message with a special $\nil$ value.
This ensures that correct processes can $\Precommit$ only a
single value (or $\nil$) in a round. As
proposers may be faulty, the proposed value is treated by correct processes as
a suggestion (it is not blindly accepted), and a correct process tells others
if it accepted the $\Proposal$ for value $v$ by sending $\Prevote$ message for
$\id(v)$; otherwise it sends $\Prevote$ message with the special $\nil$ value.
Every process maintains the following variables in the Algorithm
\ref{alg:tendermint}: $step$, $lockedValue$, $lockedRound$, $validValue$ and
$validRound$. The $step$ denotes the current state of the internal Tendermint
state machine, i.e., it reflects the stage of the algorithm execution in the
current round. The $lockedValue$ stores the most recent value (with respect to
a round number) for which a $\Precommit$ message has been sent. The
$lockedRound$ is the last round in which the process sent a $\Precommit$
message that is not $\nil$. We also say that a correct process locks a value
$v$ in a round $r$ by setting $lockedValue = v$ and $lockedRound = r$ before
sending $\Precommit$ message for $\id(v)$. As a correct process can decide a
value $v$ only if $2f+1$ $\Precommit$ messages for $\id(v)$ are received, this
implies that a possible decision value is a value that is locked by at least
$f+1$ voting power equivalent of correct processes. Therefore, any value $v$
for which $\Proposal$ and $2f+1$ of the corresponding $\Prevote$ messages are
received in some round $r$ is a \emph{possible decision} value. The role of the
$validValue$ variable is to store the most recent possible decision value; the
$validRound$ is the last round in which $validValue$ is updated. Apart from
those variables, a process also stores the current consensus instance ($h_p$,
called \emph{height} in Tendermint), and the current round number ($round_p$)
and attaches them to every message. Finally, a process also stores an array of
decisions, $decision_p$ (Tendermint assumes a sequence of consensus instances,
one for each height).
Every round starts by a proposer suggesting a value with the $\Proposal$
message (see line \ref{line:tab:send-proposal}). In the initial round of each
height, the proposer is free to chose the value to suggest. In the
Algorithm~\ref{alg:tendermint}, a correct process obtains a value to propose
using an external function $getValue()$ that returns a valid value to
propose. In the following rounds, a correct proposer will suggest a new value
only if $validValue = \nil$; otherwise $validValue$ is proposed (see
lines~\ref{line:tab:isThereLockedValue}-\ref{line:tab:getValidValue}).
In addition to the value proposed, the $\Proposal$ message also
contains the $validRound$ so other processes are informed about the last round
in which the proposer observed $validValue$ as a possible decision value.
Note that if a correct proposer $p$ sends $validValue$ with the $validRound$ in the
$\Proposal$, this implies that the process $p$ received $\Proposal$ and the
corresponding $2f+1$ $\Prevote$ messages for $validValue$ in the round
$validRound$.
If a correct process sends $\Proposal$ message with $validValue$ ($validRound > -1$)
at time $t > GST$, by the \emph{Gossip communication} property, the
corresponding $\Proposal$ and the $\Prevote$ messages will be received by all
correct processes before time $t+\Delta$. Therefore, all correct processes will
be able to verify the correctness of the suggested value as it is supported by
the $\Proposal$ and the corresponding $2f+1$ voting power equivalent $\Prevote$
messages.
A correct process $p$ accepts the proposal for a value $v$ (send $\Prevote$
for $id(v)$) if an external \emph{valid} function returns $true$ for the value
$v$, and if $p$ hasn't locked any value ($lockedRound = -1$) or $p$ has locked
the value $v$ ($lockedValue = v$); see the line
\ref{line:tab:accept-proposal-2}. In case the proposed pair is $(v,vr \ge 0)$ and a
correct process $p$ has locked some value, it will accept
$v$ if it is a more recent possible decision value\footnote{As
explained above, the possible decision value in a round $r$ is the one for
which $\Proposal$ and the corresponding $2f+1$ $\Prevote$ messages are received
for the round $r$.}, $vr > lockedRound_p$, or if $lockedValue = v$
(see line~\ref{line:tab:cond-prevote-higher-proposal}). Otherwise, a correct
process will reject the proposal by sending $\Prevote$ message with $\nil$
value. A correct process will send $\Prevote$ message with $\nil$ value also in
case $\timeoutPropose$ expired (it is triggered when a correct process starts a
new round) and a process has not sent $\Prevote$ message in the current round
yet (see the line \ref{line:tab:onTimeoutPropose}).
If a correct process receives $\Proposal$ message for some value $v$ and $2f+1$
$\Prevote$ messages for $\id(v)$, then it sends $\Precommit$ message with
$\id(v)$. Otherwise, it sends $\Precommit$ $\nil$. A correct process will send
$\Precommit$ message with $\nil$ value also in case $\timeoutPrevote$ expired
(it is started when a correct process sent $\Prevote$ message and received any
$2f+1$ $\Prevote$ messages) and a process has not sent $\Precommit$ message in
the current round yet (see the line \ref{line:tab:onTimeoutPrecommit}). A
correct process decides on some value $v$ if it receives in some round $r$
$\Proposal$ message for $v$ and $2f+1$ $\Precommit$ messages with $\id(v)$ (see
the line \ref{line:tab:decide}). To prevent the algorithm from blocking and
waiting forever for this condition to be true, the Algorithm
\ref{alg:tendermint} relies on $\timeoutPrecommit$. It is triggered after a
process receives any set of $2f+1$ $\Precommit$ messages for the current round.
If the $\timeoutPrecommit$ expires and a process has not decided yet, the
process starts the next round (see the line \ref{line:tab:onTimeoutPrecommit}).
When a correct process $p$ decides, it starts the next consensus instance
(for the next height). The \emph{Gossip communication} property ensures
that $\Proposal$ and $2f+1$ $\Prevote$ messages that led $p$ to decide
are eventually received by all correct processes, so they will also decide.
\subsection{Termination mechanism}
Tendermint ensures termination by a novel mechanism that benefits from the
gossip based nature of communication (see \emph{Gossip communication}
property). It requires managing two additional variables, $validValue$ and
$validRound$ that are then used by the proposer during the propose step as
explained above. The $validValue$ and $validRound$ are updated to $v$ and $r$
by a correct process in a round $r$ when the process receives valid $\Proposal$
message for the value $v$ and the corresponding $2f+1$ $\Prevote$ messages for
$id(v)$ in the round $r$ (see the rule at line~\ref{line:tab:recvPrevote}).
We now give briefly the intuition how managing and proposing $validValue$
and $validRound$ ensures termination. Formal treatment is left for
Section~\ref{sec:proof}.
The first thing to note is that during good period, because of the
\emph{Gossip communication} property, if a correct process $p$ locks a value
$v$ in some round $r$, all correct processes will update $validValue$ to $v$
and $validRound$ to $r$ before the end of the round $r$ (we prove this formally
in the Section~\ref{sec:proof}). The intuition is that messages that led to $p$
locking a value $v$ in the round $r$ will be gossiped to all correct processes
before the end of the round $r$, so it will update $validValue$ and
$validRound$ (the line~\ref{line:tab:recvPrevote}). Therefore, if a correct
process locks some value during good period, $validValue$ and $validRound$ are
updated by all correct processes so that the value proposed in the following
rounds will be acceptable by all correct processes. Note
that it could happen that during good period, no correct process locks a value,
but some correct process $q$ updates $validValue$ and $validRound$ during some
round. As no correct process locks a value in this case, $validValue_q$ and
$validRound_q$ will also be acceptable by all correct processes as
$validRound_q > lockedRound_c$ for every correct process $c$ and as the
\emph{Gossip communication} property ensures that the corresponding $\Prevote$
messages that $q$ received in the round $validRound_q$ are received by all
correct processes $\Delta$ time later.
Finally, it could happen that after GST, there is a long sequence of rounds in which
no correct process neither locks a value nor update $validValue$ and $validRound$.
In this case, during this sequence of rounds, the proposed value suggested by correct
processes was not accepted by all correct processes. Note that this sequence of rounds
is always finite as at the beginning of every
round there is at least a single correct process $c$ such that $validValue_c$
and $validRound_c$ are acceptable by every correct process. This is true as
there exists a correct process $c$ such that for every other correct process
$p$, $validRound_c > lockedRound_p$ or $validValue_c = lockedValue_p$. This is
true as $c$ is the process that has locked a value in the most recent round
among all correct processes (or no correct process locked any value). Therefore,
eventually $c$ will be the proper in some round and the proposed value will be accepted
by all correct processes, terminating therefore this sequence of
rounds.
Therefore, updating $validValue$ and $validRound$ variables, and the
\emph{Gossip communication} property, together ensures that eventually, during
the good period, there exists a round with a correct proposer whose proposed
value will be accepted by all correct processes, and all correct processes will
terminate in that round. Note that this mechanism, contrary to the common
termination mechanism illustrated in the
Figure~\ref{ch3:fig:coordinator-change}, does not require exchanging any
additional information in addition to messages already sent as part of what is
normally being called "normal" case.

+ 126
- 0
spec/consensus/consensus-paper/definitions.tex View File

@ -0,0 +1,126 @@
\section{Definitions} \label{sec:definitions}
\subsection{Model}
We consider a system of processes that communicate by exchanging messages.
Processes can be correct or faulty, where a faulty process can behave in an
arbitrary way, i.e., we consider Byzantine faults. We assume that each process
has some amount of voting power (voting power of a process can be $0$).
Processes in our model are not part of a single administrative domain;
therefore we cannot enforce a direct network connectivity between all
processes. Instead, we assume that each process is connected to a subset of
processes called peers, such that there is an indirect communication channel
between all correct processes. Communication between processes is established
using a gossip protocol \cite{Dem1987:gossip}.
Formally, we model the network communication using a variant of the \emph{partially
synchronous system model}~\cite{DLS88:jacm}: in all executions of the system
there is a bound $\Delta$ and an instant GST (Global Stabilization Time) such
that all communication among correct processes after GST is reliable and
$\Delta$-timely, i.e., if a correct process $p$ sends message $m$ at time $t
\ge GST$ to a correct process $q$, then $q$ will receive $m$ before $t +
\Delta$\footnote{Note that as we do not assume direct communication channels
among all correct processes, this implies that before the message $m$
reaches $q$, it might pass through a number of correct processes that will
forward the message $m$ using gossip protocol towards $q$.}.
In addition to the standard \emph{partially
synchronous system model}~\cite{DLS88:jacm}, we assume an auxiliary property
that captures gossip-based nature of communication\footnote{The details of the Tendermint gossip protocol will be discussed in a separate
technical report. }:
\begin{itemize} \item \emph{Gossip communication:} If a correct process $p$
sends some message $m$ at time $t$, all correct processes will receive
$m$ before $max\{t, GST\} + \Delta$. Furthermore, if a correct process $p$
receives some message $m$ at time $t$, all correct processes will receive
$m$ before $max\{t, GST\} + \Delta$. \end{itemize}
The bound $\Delta$ and GST are system
parameters whose values are not required to be known for the safety of our
algorithm. Termination of the algorithm is guaranteed within a bounded duration
after GST. In practice, the algorithm will work correctly in the slightly
weaker variant of the model where the system alternates between (long enough)
good periods (corresponds to the \emph{after} GST period where system is
reliable and $\Delta$-timely) and bad periods (corresponds to the period
\emph{before} GST during which the system is asynchronous and messages can be
lost), but consideration of the GST model simplifies the discussion.
We assume that process steps (which might include sending and receiving
messages) take zero time. Processes are equipped with clocks so they can
measure local timeouts.
Spoofing/impersonation attacks are assumed to be impossible at all times due to
the use of public-key cryptography, i.e., we assume that all protocol messages contains a digital signature.
Therefore, when a correct
process $q$ receives a signed message $m$ from its peer, the process $q$ can
verify who was the original sender of the message $m$ and if the message signature is valid.
We do not explicitly state a signature verification step in the pseudo-code of the algorithm to improve readability;
we assume that only messages with the valid signature are considered at that level (and messages with invalid signatures
are dropped).
%Messages that are being gossiped are created by the consensus layer. We can
%think about consensus protocol as a content creator, which %defines what
%messages should be disseminated using the gossip protocol. A correct
%process creates the message for dissemination either i) %explicitly, by
%invoking \emph{send} function as part of the consensus protocol or ii)
%implicitly, by receiving a message from some other %process. Note that in
%the case ii) gossiping of messages is implicit, i.e., it happens without
%explicit send clause in the consensus algorithm %whenever a correct
%process receives some messages in the consensus algorithm\footnote{If a
%message is received by a correct process at %the consensus level then it
%is considered valid from the protocol point of view, i.e., it has a
%correct signature, a proper message structure %and a valid height and
%round number.}.
%\item Processes keep resending messages (in case of failures or message loss)
%until all its peers get them. This ensures that every message %sent or
%received by a correct process is eventually received by all correct
%processes.
\subsection{State Machine Replication}
State machine replication (SMR) is a general approach for replicating services
modeled as a deterministic state machine~\cite{Lam78:cacm,Sch90:survey}. The
key idea of this approach is to guarantee that all replicas start in the same
state and then apply requests from clients in the same order, thereby
guaranteeing that the replicas' states will not diverge. Following
Schneider~\cite{Sch90:survey}, we note that the following is key for
implementing a replicated state machine tolerant to (Byzantine) faults:
\begin{itemize} \item \emph{Replica Coordination.} All [non-faulty] replicas
receive and process the same sequence of requests. \end{itemize}
Moreover, as Schneider also notes, this property can be decomposed into two
parts, \emph{Agreement} and \emph{Order}: Agreement requires all (non-faulty)
replicas to receive all requests, and Order requires that the order of received
requests is the same at all replicas.
There is an additional requirement that needs to be ensured by Byzantine
tolerant state machine replication: only requests (called transactions in the
Tendermint terminology) proposed by clients are executed. In Tendermint,
transaction verification is the responsibility of the service that is being
replicated; upon receiving a transaction from the client, the Tendermint
process will ask the service if the request is valid, and only valid requests
will be processed.
\subsection{Consensus} \label{sec:consensus}
Tendermint solves state machine replication by sequentially executing consensus
instances to agree on each block of transactions that are
then executed by the service being replicated. We consider a variant of the
Byzantine consensus problem called Validity Predicate-based Byzantine consensus
that is motivated by blockchain systems~\cite{GLR17:red-belly-bc}. The problem
is defined by an agreement, a termination, and a validity property.
\begin{itemize} \item \emph{Agreement:} No two correct processes decide on
different values. \item \emph{Termination:} All correct processes
eventually decide on a value. \item \emph{Validity:} A decided value
is valid, i.e., it satisfies the predefined predicate denoted
\emph{valid()}. \end{itemize}
This variant of the Byzantine consensus problem has an application-specific
\emph{valid()} predicate to indicate whether a value is valid. In the context
of blockchain systems, for example, a value is not valid if it does not
contain an appropriate hash of the last value (block) added to the blockchain.

+ 32
- 0
spec/consensus/consensus-paper/homodel.sty View File

@ -0,0 +1,32 @@
\newcommand{\NC}{\mbox{\it NC}}
\newcommand{\HO}{\mbox{\it HO}}
\newcommand{\AS}{\mbox{\it AS}}
\newcommand{\SK}{\mbox{\it SK}}
\newcommand{\SHO}{\mbox{\it SHO}}
\newcommand{\AHO}{\mbox{\it AHO}}
\newcommand{\CONS}{\mbox{\it CONS}}
\newcommand{\K}{\mbox{\it K}}
\newcommand{\Alg}{\mathcal{A}}
\newcommand{\Pred}{\mathcal{P}}
\newcommand{\Spr}{S_p^r}
\newcommand{\Tpr}{T_p^r}
\newcommand{\mupr}{\vec{\mu}_p^{\,r}}
\newcommand{\MSpr}{S_p^{\rho}}
\newcommand{\MTpr}{T_p^{\rho}}
\newconstruct{\SEND}{$\Spr$:}{}{\ENDSEND}{}
\newconstruct{\TRAN}{$\Tpr$:}{}{\ENDTRAN}{}
\newconstruct{\ROUND}{\textbf{Round}}{\!\textbf{:}}{\ENDROUND}{}
\newconstruct{\VARIABLES}{\textbf{Variables:}}{}{\ENDVARIABLES}{}
\newconstruct{\INIT}{\textbf{Initialization:}}{}{\ENDINIT}{}
\newconstruct{\MSEND}{$\MSpr$:}{}{\ENDMSEND}{}
\newconstruct{\MTRAN}{$\MTpr$:}{}{\ENDMTRAN}{}
\newconstruct{\SROUND}{\textbf{Selection Round}}{\!\textbf{:}}{\ENDSROUND}{}
\newconstruct{\VROUND}{\textbf{Validation Round}}{\!\textbf{:}}{\ENDVROUND}{}
\newconstruct{\DROUND}{\textbf{Decision Round}}{\!\textbf{:}}{\ENDDROUND}{}

+ 138
- 0
spec/consensus/consensus-paper/intro.tex View File

@ -0,0 +1,138 @@
\section{Introduction} \label{sec:tendermint}
Consensus is a fundamental problem in distributed computing. It
is important because of it's role in State Machine Replication (SMR), a generic
approach for replicating services that can be modeled as a deterministic state
machine~\cite{Lam78:cacm, Sch90:survey}. The key idea of this approach is that
service replicas start in the same initial state, and then execute requests
(also called transactions) in the same order; thereby guaranteeing that
replicas stay in sync with each other. The role of consensus in the SMR
approach is ensuring that all replicas receive transactions in the same order.
Traditionally, deployments of SMR based systems are in data-center settings
(local area network), have a small number of replicas (three to seven) and are
typically part of a single administration domain (e.g., Chubby
\cite{Bur:osdi06}); therefore they handle benign (crash) failures only, as more
general forms of failure (in particular, malicious or Byzantine faults) are
considered to occur with only negligible probability.
The success of cryptocurrencies and blockchain systems in recent years (e.g.,
\cite{Nak2012:bitcoin, But2014:ethereum}) pose a whole new set of challenges on
the design and deployment of SMR based systems: reaching agreement over wide
area network, among large number of nodes (hundreds or thousands) that are not
part of the same administrative domain, and where a subset of nodes can behave
maliciously (Byzantine faults). Furthermore, contrary to the previous
data-center deployments where nodes are fully connected to each other, in
blockchain systems, a node is only connected to a subset of other nodes, so
communication is achieved by gossip-based peer-to-peer protocols.
The new requirements demand designs and algorithms that are not necessarily
present in the classical academic literature on Byzantine fault tolerant
consensus (or SMR) systems (e.g., \cite{DLS88:jacm, CL02:tcs}) as the primary
focus was different setup.
In this paper we describe a novel Byzantine-fault tolerant consensus algorithm
that is the core of the BFT SMR platform called Tendermint\footnote{The
Tendermint platform is available open source at
https://github.com/tendermint/tendermint.}. The Tendermint platform consists of
a high-performance BFT SMR implementation written in Go, a flexible interface
for
building arbitrary deterministic applications above the consensus, and a suite
of tools for deployment and management.
The Tendermint consensus algorithm is inspired by the PBFT SMR
algorithm~\cite{CL99:osdi} and the DLS algorithm for authenticated faults (the
Algorithm 2 from \cite{DLS88:jacm}). Similar to DLS algorithm, Tendermint
proceeds in
rounds\footnote{Tendermint is not presented in the basic round model of
\cite{DLS88:jacm}. Furthermore, we use the term round differently than in
\cite{DLS88:jacm}; in Tendermint a round denotes a sequence of communication
steps instead of a single communication step in \cite{DLS88:jacm}.}, where each
round has a dedicated proposer (also called coordinator or
leader) and a process proceeds to a new round as part of normal
processing (not only in case the proposer is faulty or suspected as being faulty
by enough processes as in PBFT).
The communication pattern of each round is very similar to the "normal" case
of PBFT. Therefore, in preferable conditions (correct proposer, timely and
reliable communication between correct processes), Tendermint decides in three
communication steps (the same as PBFT).
The major novelty and contribution of the Tendermint consensus algorithm is a
new termination mechanism. As explained in \cite{MHS09:opodis, RMS10:dsn}, the
existing BFT consensus (and SMR) algorithms for the partially synchronous
system model (for example PBFT~\cite{CL99:osdi}, \cite{DLS88:jacm},
\cite{MA06:tdsc}) typically relies on the communication pattern illustrated in
Figure~\ref{ch3:fig:coordinator-change} for termination. The
Figure~\ref{ch3:fig:coordinator-change} illustrates messages exchanged during
the proposer change when processes start a new round\footnote{There is no
consistent terminology in the distributed computing terminology on naming
sequence of communication steps that corresponds to a logical unit. It is
sometimes called a round, phase or a view.}. It guarantees that eventually (ie.
after some Global Stabilization Time, GST), there exists a round with a correct
proposer that will bring the system into a univalent configuration.
Intuitively, in a round in which the proposed value is accepted
by all correct processes, and communication between correct processes is
timely and reliable, all correct processes decide.
\begin{figure}[tbh!] \def\rdstretch{5} \def\ystretch{3} \centering
\begin{rounddiag}{4}{2} \round{1}{~} \rdmessage{1}{1}{$v_1$}
\rdmessage{2}{1}{$v_2$} \rdmessage{3}{1}{$v_3$} \rdmessage{4}{1}{$v_4$}
\round{2}{~} \rdmessage{1}{1}{$x, [v_{1..4}]$}
\rdmessage{1}{2}{$~~~~~~x, [v_{1..4}]$} \rdmessage{1}{3}{$~~~~~~~~x,
[v_{1..4}]$} \rdmessage{1}{4}{$~~~~~~~x, [v_{1..4}]$} \end{rounddiag}
\vspace{-5mm} \caption{\boldmath Proposer (coordinator) change: $p_1$ is the
new proposer.} \label{ch3:fig:coordinator-change} \end{figure}
To ensure that a proposed value is accepted by all correct
processes\footnote{The proposed value is not blindly accepted by correct
processes in BFT algorithms. A correct process always verifies if the proposed
value is safe to be accepted so that safety properties of consensus are not
violated.}
a proposer will 1) build the global state by receiving messages from other
processes, 2) select the safe value to propose and 3) send the selected value
together with the signed messages
received in the first step to support it. The
value $v_i$ that a correct process sends to the next proposer normally
corresponds to a value the process considers as acceptable for a decision:
\begin{itemize} \item in PBFT~\cite{CL99:osdi} and DLS~\cite{DLS88:jacm} it is
not the value itself but a set of $2f+1$ signed messages with the same
value id, \item in Fast Byzantine Paxos~\cite{MA06:tdsc} the value
itself is being sent. \end{itemize}
In both cases, using this mechanism in our system model (ie. high
number of nodes over gossip based network) would have high communication
complexity that increases with the number of processes: in the first case as
the message sent depends on the total number of processes, and in the second
case as the value (block of transactions) is sent by each process. The set of
messages received in the first step are normally piggybacked on the proposal
message (in the Figure~\ref{ch3:fig:coordinator-change} denoted with
$[v_{1..4}]$) to justify the choice of the selected value $x$. Note that
sending this message also does not scale with the number of processes in the
system.
We designed a novel termination mechanism for Tendermint that better suits the
system model we consider. It does not require additional communication (neither
sending new messages nor piggybacking information on the existing messages) and
it is fully based on the communication pattern that is very similar to the
normal case in PBFT \cite{CL99:osdi}. Therefore, there is only a single mode of
execution in Tendermint, i.e., there is no separation between the normal and
the recovery mode, which is the case in other PBFT-like protocols (e.g.,
\cite{CL99:osdi}, \cite{Ver09:spinning} or \cite{Cle09:aardvark}). We believe
this makes Tendermint simpler to understand and implement correctly.
Note that the orthogonal approach for reducing message complexity in order to
improve
scalability and decentralization (number of processes) of BFT consensus
algorithms is using advanced cryptography (for example Boneh-Lynn-Shacham (BLS)
signatures \cite{BLS2001:crypto}) as done for example in SBFT
\cite{Gue2018:sbft}.
The remainder of the paper is as follows: Section~\ref{sec:definitions} defines
the system model and gives the problem definitions. Tendermint
consensus algorithm is presented in Section~\ref{sec:tendermint} and the
proofs are given in Section~\ref{sec:proof}. We conclude in
Section~\ref{sec:conclusion}.

+ 1124
- 0
spec/consensus/consensus-paper/latex8.bst
File diff suppressed because it is too large
View File


+ 168
- 0
spec/consensus/consensus-paper/latex8.sty View File

@ -0,0 +1,168 @@
% ---------------------------------------------------------------
%
% $Id: latex8.sty,v 1.2 1995/09/15 15:31:13 ienne Exp $
%
% by Paolo.Ienne@di.epfl.ch
%
% ---------------------------------------------------------------
%
% no guarantee is given that the format corresponds perfectly to
% IEEE 8.5" x 11" Proceedings, but most features should be ok.
%
% ---------------------------------------------------------------
% with LaTeX2e:
% =============
%
% use as
% \documentclass[times,10pt,twocolumn]{article}
% \usepackage{latex8}
% \usepackage{times}
%
% ---------------------------------------------------------------
% with LaTeX 2.09:
% ================
%
% use as
% \documentstyle[times,art10,twocolumn,latex8]{article}
%
% ---------------------------------------------------------------
% with both versions:
% ===================
%
% specify \pagestyle{empty} to omit page numbers in the final
% version
%
% specify references as
% \bibliographystyle{latex8}
% \bibliography{...your files...}
%
% use Section{} and SubSection{} instead of standard section{}
% and subsection{} to obtain headings in the form
% "1.3. My heading"
%
% ---------------------------------------------------------------
\typeout{IEEE 8.5 x 11-Inch Proceedings Style `latex8.sty'.}
% ten point helvetica bold required for captions
% in some sites the name of the helvetica bold font may differ,
% change the name here:
\font\tenhv = phvb at 10pt
%\font\tenhv = phvb7t at 10pt
% eleven point times bold required for second-order headings
% \font\elvbf = cmbx10 scaled 1100
\font\elvbf = ptmb scaled 1100
% set dimensions of columns, gap between columns, and paragraph indent
\setlength{\textheight}{8.875in}
\setlength{\textwidth}{6.875in}
\setlength{\columnsep}{0.3125in}
\setlength{\topmargin}{0in}
\setlength{\headheight}{0in}
\setlength{\headsep}{0in}
\setlength{\parindent}{1pc}
\setlength{\oddsidemargin}{-.304in}
\setlength{\evensidemargin}{-.304in}
% memento from size10.clo
% \normalsize{\@setfontsize\normalsize\@xpt\@xiipt}
% \small{\@setfontsize\small\@ixpt{11}}
% \footnotesize{\@setfontsize\footnotesize\@viiipt{9.5}}
% \scriptsize{\@setfontsize\scriptsize\@viipt\@viiipt}
% \tiny{\@setfontsize\tiny\@vpt\@vipt}
% \large{\@setfontsize\large\@xiipt{14}}
% \Large{\@setfontsize\Large\@xivpt{18}}
% \LARGE{\@setfontsize\LARGE\@xviipt{22}}
% \huge{\@setfontsize\huge\@xxpt{25}}
% \Huge{\@setfontsize\Huge\@xxvpt{30}}
\def\@maketitle
{
\newpage
\null
\vskip .375in
\begin{center}
{\Large \bf \@title \par}
% additional two empty lines at the end of the title
\vspace*{24pt}
{
\large
\lineskip .5em
\begin{tabular}[t]{c}
\@author
\end{tabular}
\par
}
% additional small space at the end of the author name
\vskip .5em
{
\large
\begin{tabular}[t]{c}
\@affiliation
\end{tabular}
\par
\ifx \@empty \@email
\else
\begin{tabular}{r@{~}l}
E-mail: & {\tt \@email}
\end{tabular}
\par
\fi
}
% additional empty line at the end of the title block
\vspace*{12pt}
\end{center}
}
\def\abstract
{%
\centerline{\large\bf Abstract}%
\vspace*{12pt}%
\it%
}
\def\endabstract
{
% additional empty line at the end of the abstract
\vspace*{12pt}
}
\def\affiliation#1{\gdef\@affiliation{#1}} \gdef\@affiliation{}
\def\email#1{\gdef\@email{#1}}
\gdef\@email{}
\newlength{\@ctmp}
\newlength{\@figindent}
\setlength{\@figindent}{1pc}
\long\def\@makecaption#1#2{
\vskip 10pt
\setbox\@tempboxa\hbox{\tenhv\noindent #1.~#2}
\setlength{\@ctmp}{\hsize}
\addtolength{\@ctmp}{-\@figindent}\addtolength{\@ctmp}{-\@figindent}
% IF longer than one indented paragraph line
\ifdim \wd\@tempboxa >\@ctmp
% THEN set as an indented paragraph
\begin{list}{}{\leftmargin\@figindent \rightmargin\leftmargin}
\item[]\tenhv #1.~#2\par
\end{list}
\else
% ELSE center
\hbox to\hsize{\hfil\box\@tempboxa\hfil}
\fi}
% correct heading spacing and type
\def\section{\@startsection {section}{1}{\z@}
{14pt plus 2pt minus 2pt}{14pt plus 2pt minus 2pt} {\large\bf}}
\def\subsection{\@startsection {subsection}{2}{\z@}
{13pt plus 2pt minus 2pt}{13pt plus 2pt minus 2pt} {\elvbf}}
% add the period after section numbers
\newcommand{\Section}[1]{\section{\hskip -1em.~#1}}
\newcommand{\SubSection}[1]{\subsection{\hskip -1em.~#1}}
% end of file latex8.sty
% ---------------------------------------------------------------

+ 1659
- 0
spec/consensus/consensus-paper/lit.bib
File diff suppressed because it is too large
View File


+ 153
- 0
spec/consensus/consensus-paper/paper.tex View File

@ -0,0 +1,153 @@
%\documentclass[conference]{IEEEtran}
\documentclass[conference,onecolumn,draft,a4paper]{IEEEtran}
% Add the compsoc option for Computer Society conferences.
%
% If IEEEtran.cls has not been installed into the LaTeX system files,
% manually specify the path to it like:
% \documentclass[conference]{../sty/IEEEtran}
% *** GRAPHICS RELATED PACKAGES ***
%
\ifCLASSINFOpdf
\else
\fi
% correct bad hyphenation here
\hyphenation{op-tical net-works semi-conduc-tor}
%\usepackage[caption=false,font=footnotesize]{subfig}
\usepackage{tikz}
\usetikzlibrary{decorations,shapes,backgrounds,calc}
\tikzstyle{msg}=[->,black,>=latex]
\tikzstyle{rubber}=[|<->|]
\tikzstyle{announce}=[draw=blue,fill=blue,shape=diamond,right,minimum
height=2mm,minimum width=1.6667mm,inner sep=0pt]
\tikzstyle{decide}=[draw=red,fill=red,shape=isosceles triangle,right,minimum
height=2mm,minimum width=1.6667mm,inner sep=0pt,shape border rotate=90]
\tikzstyle{cast}=[draw=green!50!black,fill=green!50!black,shape=circle,left,minimum
height=2mm,minimum width=1.6667mm,inner sep=0pt]
\usepackage{multirow}
\usepackage{graphicx}
\usepackage{epstopdf}
\usepackage{amssymb}
\usepackage{rounddiag}
\graphicspath{{../}}
\usepackage{technote}
\usepackage{homodel}
\usepackage{enumerate}
%%\usepackage{ulem}\normalem
% to center caption
\usepackage{caption}
\newcommand{\textstretch}{1.4}
\newcommand{\algostretch}{1}
\newcommand{\eqnstretch}{0.5}
\newconstruct{\FOREACH}{\textbf{for each}}{\textbf{do}}{\ENDFOREACH}{}
%\newconstruct{\ON}{\textbf{on}}{\textbf{do}}{\ENDON}{\textbf{end on}}
\newcommand\With{\textbf{while}}
\newcommand\From{\textbf{from}}
\newcommand\Broadcast{\textbf{broadcast}}
\newcommand\PBroadcast{send}
\newcommand\UpCall{\textbf{UpCall}}
\newcommand\DownCall{\textbf{DownCall}}
\newcommand \Call{\textbf{Call}}
\newident{noop}
\newconstruct{\UPON}{\textbf{upon}}{\textbf{do}}{\ENDUPON}{}
\newcommand{\abcast}{\mathsf{to\mbox{\sf-}broadcast}}
\newcommand{\adeliver}{\mathsf{to\mbox{\sf-}deliver}}
\newcommand{\ABCAgreement}{\emph{TO-Agreement}}
\newcommand{\ABCIntegrity}{\emph{TO-Integrity}}
\newcommand{\ABCValidity}{\emph{TO-Validity}}
\newcommand{\ABCTotalOrder}{\emph{TO-Order}}
\newcommand{\ABCBoundedDelivery}{\emph{TO-Bounded Delivery}}
\newcommand{\tabc}{\mathit{atab\mbox{\sf-}cast}}
\newcommand{\anno}{\mathit{atab\mbox{\sf-}announce}}
\newcommand{\abort}{\mathit{atab\mbox{\sf-}abort}}
\newcommand{\tadel}{\mathit{atab\mbox{\sf-}deliver}}
\newcommand{\ATABAgreement}{\emph{ATAB-Agreement}}
\newcommand{\ATABAbort}{\emph{ATAB-Abort}}
\newcommand{\ATABIntegrity}{\emph{ATAB-Integrity}}
\newcommand{\ATABValidity}{\emph{ATAB-Validity}}
\newcommand{\ATABAnnounce}{\emph{ATAB-Announcement}}
\newcommand{\ATABTermination}{\emph{ATAB-Termination}}
%\newcommand{\ATABFastAnnounce}{\emph{ATAB-Fast-Announcement}}
%% Command for observations.
\newtheorem{observation}{Observation}
%% HO ALGORITHM DEFINITIONS
\newconstruct{\FUNCTION}{\textbf{Function}}{\textbf{:}}{\ENDFUNCTION}{}
%% Uncomment the following four lines to remove remarks and visible traces of
%% modifications in the document
%%\renewcommand{\sout}[1]{\relaxx}
%%\renewcommand{\uline}[1]{#1}
%% \renewcommand{\uwave}[1]{#1}
\renewcommand{\note}[2][default]{\relax}
%% The following commands can be used to generate TR or Conference version of the paper
\newcommand{\tr}[1]{}
\renewcommand{\tr}[1]{#1}
\newcommand{\onlypaper}[1]{#1}
%\renewcommand{\onlypaper}[1]{}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%\pagestyle{plain}
%\pagestyle{empty}
%% IEEE tweaks
%\setlength{\IEEEilabelindent}{.5\parindent}
%\setlength{\IEEEiednormlabelsep}{.5\parindent}
\begin{document}
%
% paper title
% can use linebreaks \\ within to get better formatting as desired
\title{The latest gossip on BFT consensus\vspace{-0.7\baselineskip}}
\author{\IEEEauthorblockN{\large Ethan Buchman, Jae Kwon and Zarko Milosevic\\}
\IEEEauthorblockN{\large Tendermint}\\
%\\\vspace{-0.5\baselineskip}
\IEEEauthorblockN{September 24, 2018}
}
% make the title area
\maketitle
\vspace*{0.5em}
\begin{abstract}
This paper presents Tendermint, a new protocol for ordering events in a distributed network under adversarial conditions. More commonly known as Byzantine Fault Tolerant (BFT) consensus or atomic broadcast, the problem has attracted significant attention in recent years due to the widespread success of blockchain-based digital currencies, such as Bitcoin and Ethereum, which successfully solved the problem in a public setting without a central authority. Tendermint modernizes classic academic work on the subject and simplifies the design of the BFT algorithm by relying on a peer-to-peer gossip protocol among nodes.
\end{abstract}
%\noindent \textbf{Keywords:} Blockchain, Byzantine Fault Tolerance, State Machine %Replication
\input{intro}
\input{definitions}
\input{consensus}
\input{proof}
\input{conclusion}
\bibliographystyle{IEEEtran}
\bibliography{lit}
%\appendix
\end{document}

+ 280
- 0
spec/consensus/consensus-paper/proof.tex View File

@ -0,0 +1,280 @@
\section{Proof of Tendermint consensus algorithm} \label{sec:proof}
\begin{lemma} \label{lemma:majority-intersection} For all $f\geq 0$, any two
sets of processes with voting power at least equal to $2f+1$ have at least one
correct process in common. \end{lemma}
\begin{proof} As the total voting power is equal to $n=3f+1$, we have $2(2f+1)
= n+f+1$. This means that the intersection of two sets with the voting
power equal to $2f+1$ contains at least $f+1$ voting power in common, \ie,
at least one correct process (as the total voting power of faulty processes
is $f$). The result follows directly from this. \end{proof}
\begin{lemma} \label{lemma:locked-decision_value-prevote-v} If $f+1$ correct
processes lock value $v$ in round $r_0$ ($lockedValue = v$ and $lockedRound =
r_0$), then in all rounds $r > r_0$, they send $\Prevote$ for $id(v)$ or
$\nil$. \end{lemma}
\begin{proof} We prove the result by induction on $r$.
\emph{Base step $r = r_0 + 1:$} Let's denote with $C$ the set of correct
processes with voting power equal to $f+1$. By the rules at
line~\ref{line:tab:recvProposal} and line~\ref{line:tab:acceptProposal}, the
processes from the set $C$ can't accept $\Proposal$ for any value different
from $v$ in round $r$, and therefore can't send a $\li{\Prevote,height_p,
r,id(v')}$ message, if $v' \neq v$. Therefore, the Lemma holds for the base
step.
\emph{Induction step from $r_1$ to $r_1+1$:} We assume that no process from the
set $C$ has sent $\Prevote$ for values different than $id(v)$ or $\nil$ until
round $r_1 + 1$. We now prove that the Lemma also holds for round $r_1 + 1$. As
processes from the set $C$ send $\Prevote$ for $id(v)$ or $\nil$ in rounds $r_0
\le r \le r_1$, by Lemma~\ref{lemma:majority-intersection} there is no value
$v' \neq v$ for which it is possible to receive $2f+1$ $\Prevote$ messages in
those rounds (i). Therefore, we have for all processes from the set $C$,
$lockedValue = v$ and $lockedRound \ge r_0$. Let's assume by a contradiction
that a process $q$ from the set $C$ sends $\Prevote$ in round $r_1 + 1$ for
value $id(v')$, where $v' \neq v$. This is possible only by
line~\ref{line:tab:prevote-higher-proposal}. Note that this implies that $q$
received $2f+1$ $\li{\Prevote,h_q, r,id(v')}$ messages, where $r > r_0$ and $r
< r_1 +1$ (see line~\ref{line:tab:cond-prevote-higher-proposal}). A
contradiction with (i) and Lemma~\ref{lemma:majority-intersection}.
\end{proof}
\begin{lemma} \label{lemma:agreement} Algorithm~\ref{alg:tendermint} satisfies
Agreement. \end{lemma}
\begin{proof} Let round $r_0$ be the first round of height $h$ such that some
correct process $p$ decides $v$. We now prove that if some correct process
$q$ decides $v'$ in some round $r \ge r_0$, then $v = v'$.
In case $r = r_0$, $q$ has received at least $2f+1$
$\li{\Precommit,h_p,r_0,id(v')}$ messages at line~\ref{line:tab:onDecideRule},
while $p$ has received at least $2f+1$ $\li{\Precommit,h_p,r_0,id(v)}$
messages. By Lemma~\ref{lemma:majority-intersection} two sets of messages of
voting power $2f+1$ intersect in at least one correct process. As a correct
process sends a single $\Precommit$ message in a round, then $v=v'$.
We prove the case $r > r_0$ by contradiction. By the
rule~\ref{line:tab:onDecideRule}, $p$ has received at least $2f+1$ voting-power
equivalent of $\li{\Precommit,h_p,r_0,id(v)}$ messages, i.e., at least $f+1$
voting-power equivalent correct processes have locked value $v$ in round $r_0$ and have
sent those messages (i). Let denote this set of messages with $C$. On the
other side, $q$ has received at least $2f+1$ voting power equivalent of
$\li{\Precommit,h_q, r,id(v')}$ messages. As the voting power of all faulty
processes is at most $f$, some correct process $c$ has sent one of those
messages. By the rule at line~\ref{line:tab:recvPrevote}, $c$ has locked value
$v'$ in round $r$ before sending $\li{\Precommit,h_q, r,id(v')}$. Therefore $c$
has received $2f+1$ $\Prevote$ messages for $id(v')$ in round $r > r_0$ (see
line~\ref{line:tab:recvPrevote}). By Lemma~\ref{lemma:majority-intersection}, a
process from the set $C$ has sent $\Prevote$ message for $id(v')$ in round $r$.
A contradiction with (i) and Lemma~\ref{lemma:locked-decision_value-prevote-v}.
\end{proof}
\begin{lemma} \label{lemma:agreement} Algorithm~\ref{alg:tendermint} satisfies
Validity. \end{lemma}
\begin{proof} Trivially follows from the rule at line
\ref{line:tab:validDecisionValue} which ensures that only valid values can be
decided. \end{proof}
\begin{lemma} \label{lemma:round-synchronisation} If we assume that:
\begin{enumerate}
\item a correct process $p$ is the first correct process to
enter a round $r>0$ at time $t > GST$ (for every correct process
$c$, $round_c \le r$ at time $t$)
\item the proposer of round $r$ is
a correct process $q$
\item for every correct process $c$,
$lockedRound_c \le validRound_q$ at time $t$
\item $\timeoutPropose(r)
> 2\Delta + \timeoutPrecommit(r-1)$, $\timeoutPrevote(r) > 2\Delta$ and
$\timeoutPrecommit(r) > 2\Delta$,
\end{enumerate}
then all correct processes decide in round $r$ before $t + 4\Delta +
\timeoutPrecommit(r-1)$.
\end{lemma}
\begin{proof} As $p$ is the first correct process to enter round $r$, it
executed the line~\ref{line:tab:nextRound} after $\timeoutPrecommit(r-1)$
expired. Therefore, $p$ received $2f+1$ $\Precommit$ messages in the round
$r-1$ before time $t$. By the \emph{Gossip communication} property, all
correct processes will receive those messages the latest at time $t +
\Delta$. Correct processes that are in rounds $< r-1$ at time $t$ will
enter round $r-1$ (see the rule at line~\ref{line:tab:nextRound2}) and
trigger $\timeoutPrecommit(r-1)$ (see rule~\ref{line:tab:startTimeoutPrecommit})
by time $t+\Delta$. Therefore, all correct processes will start round $r$
by time $t+\Delta+\timeoutPrecommit(r-1)$ (i).
In the worst case, the process $q$ is the last correct process to enter round
$r$, so $q$ starts round $r$ and sends $\Proposal$ message for some value $v$
at time $t + \Delta + \timeoutPrecommit(r-1)$. Therefore, all correct processes
receive the $\Proposal$ message from $q$ the latest by time $t + 2\Delta +
\timeoutPrecommit(r-1)$. Therefore, if $\timeoutPropose(r) > 2\Delta +
\timeoutPrecommit(r-1)$, all correct processes will receive $\Proposal$ message
before $\timeoutPropose(r)$ expires.
By (3) and the rules at line~\ref{line:tab:recvProposal} and
\ref{line:tab:acceptProposal}, all correct processes will accept the
$\Proposal$ message for value $v$ and will send a $\Prevote$ message for
$id(v)$ by time $t + 2\Delta + \timeoutPrecommit(r-1)$. Note that by the
\emph{Gossip communication} property, the $\Prevote$ messages needed to trigger
the rule at line~\ref{line:tab:acceptProposal} are received before time $t +
\Delta$.
By time $t + 3\Delta + \timeoutPrecommit(r-1)$, all correct processes will receive
$\Proposal$ for $v$ and $2f+1$ corresponding $\Prevote$ messages for $id(v)$.
By the rule at line~\ref{line:tab:recvPrevote}, all correct processes will send
a $\Precommit$ message (see line~\ref{line:tab:precommit-v}) for $id(v)$ by
time $t + 3\Delta + \timeoutPrecommit(r-1)$. Therefore, by time $t + 4\Delta +
\timeoutPrecommit(r-1)$, all correct processes will have received the $\Proposal$
for $v$ and $2f+1$ $\Precommit$ messages for $id(v)$, so they decide at
line~\ref{line:tab:decide} on $v$.
This scenario holds if every correct process $q$ sends a $\Precommit$ message
before $\timeoutPrevote(r)$ expires, and if $\timeoutPrecommit(r)$ does not expire
before $t + 4\Delta + \timeoutPrecommit(r-1)$. Let's assume that a correct process
$c_1$ is the first correct process to trigger $\timeoutPrevote(r)$ (see the rule
at line~\ref{line:tab:recvAny2/3Prevote}) at time $t_1 > t$. This implies that
before time $t_1$, $c_1$ received a $\Proposal$ ($step_{c_1}$ must be
$\prevote$ by the rule at line~\ref{line:tab:recvAny2/3Prevote}) and a set of
$2f+1$ $\Prevote$ messages. By time $t_1 + \Delta$, all correct processes will
receive those messages. Note that even if some correct process was in the
smaller round before time $t_1$, at time $t_1 + \Delta$ it will start round $r$
after receiving those messages (see the rule at
line~\ref{line:tab:skipRounds}). Therefore, all correct processes will send
their $\Prevote$ message for $id(v)$ by time $t_1 + \Delta$, and all correct
processes will receive those messages the by time $t_1 + 2\Delta$. Therefore,
as $\timeoutPrevote(r) > 2\Delta$, this ensures that all correct processes receive
$\Prevote$ messages from all correct processes before their respective local
$\timeoutPrevote(r)$ expire.
On the other hand, $\timeoutPrecommit(r)$ is triggered in a correct process $c_2$
after it receives any set of $2f+1$ $\Precommit$ messages for the first time.
Let's denote with $t_2 > t$ the earliest point in time $\timeoutPrecommit(r)$ is
triggered in some correct process $c_2$. This implies that $c_2$ has received
at least $f+1$ $\Precommit$ messages for $id(v)$ from correct processes, i.e.,
those processes have received $\Proposal$ for $v$ and $2f+1$ $\Prevote$
messages for $id(v)$ before time $t_2$. By the \emph{Gossip communication}
property, all correct processes will receive those messages by time $t_2 +
\Delta$, and will send $\Precommit$ messages for $id(v)$. Note that even if
some correct processes were at time $t_2$ in a round smaller than $r$, by the
rule at line~\ref{line:tab:skipRounds} they will enter round $r$ by time $t_2 +
\Delta$. Therefore, by time $t_2 + 2\Delta$, all correct processes will
receive $\Proposal$ for $v$ and $2f+1$ $\Precommit$ messages for $id(v)$. So if
$\timeoutPrecommit(r) > 2\Delta$, all correct processes will decide before the
timeout expires. \end{proof}
\begin{lemma} \label{lemma:validValue} If a correct process $p$ locks a value
$v$ at time $t_0 > GST$ in some round $r$ ($lockedValue = v$ and
$lockedRound = r$) and $\timeoutPrecommit(r) > 2\Delta$, then all correct
processes set $validValue$ to $v$ and $validRound$ to $r$ before starting
round $r+1$. \end{lemma}
\begin{proof} In order to prove this Lemma, we need to prove that if the
process $p$ locks a value $v$ at time $t_0$, then no correct process will
leave round $r$ before time $t_0 + \Delta$ (unless it has already set
$validValue$ to $v$ and $validRound$ to $r$). It is sufficient to prove
this, since by the \emph{Gossip communication} property the messages that
$p$ received at time $t_0$ and that triggered rule at
line~\ref{line:tab:recvPrevote} will be received by time $t_0 + \Delta$ by
all correct processes, so all correct processes that are still in round $r$
will set $validValue$ to $v$ and $validRound$ to $r$ (by the rule at
line~\ref{line:tab:recvPrevote}). To prove this, we need to compute the
earliest point in time a correct process could leave round $r$ without
updating $validValue$ to $v$ and $validRound$ to $r$ (we denote this time
with $t_1$). The Lemma is correct if $t_0 + \Delta < t_1$.
If the process $p$ locks a value $v$ at time $t_0$, this implies that $p$
received the valid $\Proposal$ message for $v$ and $2f+1$
$\li{\Prevote,h,r,id(v)}$ at time $t_0$. At least $f+1$ of those messages are
sent by correct processes. Let's denote this set of correct processes as $C$. By
Lemma~\ref{lemma:majority-intersection} any set of $2f+1$ $\Prevote$ messages
in round $r$ contains at least a single message from the set $C$.
Let's denote as time $t$ the earliest point in time a correct process, $c_1$, triggered
$\timeoutPrevote(r)$. This implies that $c_1$ received $2f+1$ $\Prevote$ messages
(see the rule at line \ref{line:tab:recvAny2/3Prevote}), where at least one of
those messages was sent by a process $c_2$ from the set $C$. Therefore, process
$c_2$ had received $\Proposal$ message before time $t$. By the \emph{Gossip
communication} property, all correct processes will receive $\Proposal$ and
$2f+1$ $\Prevote$ messages for round $r$ by time $t+\Delta$. The latest point
in time $p$ will trigger $\timeoutPrevote(r)$ is $t+\Delta$\footnote{Note that
even if $p$ was in smaller round at time $t$ it will start round $r$ by time
$t+\Delta$.}. So the latest point in time $p$ can lock the value $v$ in
round $r$ is $t_0 = t+\Delta+\timeoutPrevote(r)$ (as at this point
$\timeoutPrevote(r)$ expires, so a process sends $\Precommit$ $\nil$ and updates
$step$ to $\precommit$, see line \ref{line:tab:onTimeoutPrevote}).
Note that according to the Algorithm \ref{alg:tendermint}, a correct process
can not send a $\Precommit$ message before receiving $2f+1$ $\Prevote$
messages. Therefore, no correct process can send a $\Precommit$ message in
round $r$ before time $t$. If a correct process sends a $\Precommit$ message
for $\nil$, it implies that it has waited for the full duration of
$\timeoutPrevote(r)$ (see line
\ref{line:tab:precommit-nil-onTimeout})\footnote{The other case in which a
correct process $\Precommit$ for $\nil$ is after receiving $2f+1$ $Prevote$ for
$\nil$ messages, see the line \ref{line:tab:precommit-v-1}. By
Lemma~\ref{lemma:majority-intersection}, this is not possible in round $r$.}.
Therefore, no correct process can send $\Precommit$ for $\nil$ before time $t +
\timeoutPrevote(r)$ (*).
A correct process $q$ that enters round $r+1$ must wait (i) $\timeoutPrecommit(r)$
(see line \ref{line:tab:nextRound}) or (ii) receiving $f+1$ messages from the
round $r+1$ (see the line \ref{line:tab:skipRounds}). In the former case, $q$
receives $2f+1$ $\Precommit$ messages before starting $\timeoutPrecommit(r)$. If
at least a single $\Precommit$ message from a correct process (at least $f+1$
voting power equivalent of those messages is sent by correct processes) is for
$\nil$, then $q$ cannot start round $r+1$ before time $t_1 = t +
\timeoutPrevote(r) + \timeoutPrecommit(r)$ (see (*)). Therefore in this case we have:
$t_0 + \Delta < t_1$, i.e., $t+2\Delta+\timeoutPrevote(r) < t + \timeoutPrevote(r) +
\timeoutPrecommit(r)$, and this is true whenever $\timeoutPrecommit(r) > 2\Delta$, so
Lemma holds in this case.
If in the set of $2f+1$ $\Precommit$ messages $q$ receives, there is at least a
single $\Precommit$ for $id(v)$ message from a correct process $c$, then $q$
can start the round $r+1$ the earliest at time $t_1 = t+\timeoutPrecommit(r)$. In
this case, by the \emph{Gossip communication} property, all correct processes
will receive $\Proposal$ and $2f+1$ $\Prevote$ messages (that $c$ received
before time $t$) the latest at time $t+\Delta$. Therefore, $q$ will set
$validValue$ to $v$ and $validRound$ to $r$ the latest at time $t+\Delta$. As
$t+\Delta < t+\timeoutPrecommit(r)$, whenever $\timeoutPrecommit(r) > \Delta$, the
Lemma holds also in this case.
In case (ii), $q$ received at least a single message from a correct process $c$
from the round $r+1$. The earliest point in time $c$ could have started round
$r+1$ is $t+\timeoutPrecommit(r)$ in case it received a $\Precommit$ message for
$v$ from some correct process in the set of $2f+1$ $\Precommit$ messages it
received. The same reasoning as above holds also in this case, so $q$ set
$validValue$ to $v$ and $validRound$ to $r$ the latest by time $t+\Delta$. As
$t+\Delta < t+\timeoutPrecommit(r)$, whenever $\timeoutPrecommit(r) > \Delta$, the
Lemma holds also in this case. \end{proof}
\begin{lemma} \label{lemma:agreement} Algorithm~\ref{alg:tendermint} satisfies
Termination. \end{lemma}
\begin{proof} Lemma~\ref{lemma:round-synchronisation} defines a scenario in
which all correct processes decide. We now prove that within a bounded
duration after GST such a scenario will unfold. Let's assume that at time
$GST$ the highest round started by a correct process is $r_0$, and that
there exists a correct process $p$ such that the following holds: for every
correct process $c$, $lockedRound_c \le validRound_p$. Furthermore, we
assume that $p$ will be the proposer in some round $r_1 > r$ (this is
ensured by the $\coord$ function).
We have two cases to consider. In the first case, for all rounds $r \ge r_0$
and $r < r_1$, no correct process locks a value (set $lockedRound$ to $r$). So
in round $r_1$ we have the scenario from the
Lemma~\ref{lemma:round-synchronisation}, so all correct processes decides in
round $r_1$.
In the second case, a correct process locks a value $v$ in round $r_2$, where
$r_2 \ge r_0$ and $r_2 < r_1$. Let's assume that $r_2$ is the highest round
before $r_1$ in which some correct process $q$ locks a value. By Lemma
\ref{lemma:validValue} at the end of round $r_2$ the following holds for all
correct processes $c$: $validValue_c = lockedValue_q$ and $validRound_c = r_2$.
Then in round $r_1$, the conditions for the
Lemma~\ref{lemma:round-synchronisation} holds, so all correct processes decide.
\end{proof}

+ 62
- 0
spec/consensus/consensus-paper/rounddiag.sty View File

@ -0,0 +1,62 @@
% ROUNDDIAG STYLE
% for LaTeX version 2e
% by -- 2008 Martin Hutle <martin.hutle@epfl.ch>
%
% This style file is free software; you can redistribute it and/or
% modify it under the terms of the GNU Lesser General Public
% License as published by the Free Software Foundation; either
% version 2 of the License, or (at your option) any later version.
%
% This style file is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
% Lesser General Public License for more details.
%
% You should have received a copy of the GNU Lesser General Public
% License along with this style file; if not, write to the
% Free Software Foundation, Inc., 59 Temple Place - Suite 330,
% Boston, MA 02111-1307, USA.
%
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{rounddiag}
\typeout{Document Style `rounddiag' - provides simple round diagrams}
%
\RequirePackage{ifthen}
\RequirePackage{calc}
\RequirePackage{tikz}
\def\rdstretch{3}
\tikzstyle{msg}=[->,thick,>=latex]
\tikzstyle{rndline}=[dotted]
\tikzstyle{procline}=[dotted]
\newenvironment{rounddiag}[2]{
\begin{center}
\begin{tikzpicture}
\foreach \i in {1,...,#1}{
\draw[procline] (0,#1-\i) node[xshift=-1em]{$p_{\i}$} -- (#2*\rdstretch+1,#1-\i);
}
\foreach \i in {0,...,#2}{
\draw[rndline] (\i*\rdstretch+0.5,0) -- (\i*\rdstretch+0.5,#1-1);
}
\newcommand{\rdat}[2]{
(##2*\rdstretch+0.5,#1-##1)
}%
\newcommand{\round}[2]{%
\def\rdround{##1}
\ifthenelse{\equal{##2}{}}{}{
\node[yshift=-1em] at ({##1*\rdstretch+0.5-0.5*\rdstretch},0) {##2};
}
}%
\newcommand{\rdmessage}[3]{\draw[msg]
(\rdround*\rdstretch-\rdstretch+0.5,#1-##1) -- node[yshift=1.2ex]{##3}
(\rdround*\rdstretch+0.5,#1-##2);}%
\newcommand{\rdalltoall}{%
\foreach \i in {1,...,#1}{
\foreach \j in {1,...,#1}{
{ \rdmessage{\i}{\j}{}}}}}%
}{%
\end{tikzpicture}
\end{center}
}

+ 118
- 0
spec/consensus/consensus-paper/technote.sty View File

@ -0,0 +1,118 @@
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{technote}[2007/11/09]
\typeout{Template for quick notes with some useful definitions}
\RequirePackage{ifthen}
\RequirePackage{calc}
\RequirePackage{amsmath,amssymb,amsthm}
\RequirePackage{epsfig}
\RequirePackage{algorithm}
\RequirePackage[noend]{algorithmicplus}
\newboolean{technote@noedit}
\setboolean{technote@noedit}{false}
\DeclareOption{noedit}{\setboolean{technote@noedit}{true}}
\newcounter{technote@lang}
\setcounter{technote@lang}{0}
\DeclareOption{german}{\setcounter{technote@lang}{1}}
\DeclareOption{french}{\setcounter{technote@lang}{2}}
\DeclareOption{fullpage}{
\oddsidemargin -10mm % Margin on odd side pages (default=0mm)
\evensidemargin -10mm % Margin on even side pages (default=0mm)
\topmargin -10mm % Top margin space (default=16mm)
\headheight \baselineskip % Height of headers (default=0mm)
\headsep \baselineskip % Separation spc btw header and text (d=0mm)
\footskip 30pt % Separation spc btw text and footer (d=30pt)
\textheight 230mm % Total text height (default=200mm)
\textwidth 180mm % Total text width (default=160mm)
}
\renewcommand{\algorithmiccomment}[1]{\hfill/* #1 */}
\renewcommand{\algorithmiclnosize}{\scriptsize}
\newboolean{technote@truenumbers}
\setboolean{technote@truenumbers}{false}
\DeclareOption{truenumbers}{\setboolean{technote@truenumbers}{true}}
\ProcessOptions
\newcommand{\N}{\ifthenelse{\boolean{technote@truenumbers}}%
{\mbox{\rm I\hspace{-.5em}N}}%
{\mathbb{N}}}
\newcommand{\R}{\ifthenelse{\boolean{technote@truenumbers}}%
{\mbox{\rm I\hspace{-.2em}R}}%
{\mathbb{R}}}
\newcommand{\Z}{\mathbb{Z}}
\newcommand{\set}[1]{\left\{#1\right\}}
\newcommand{\mathsc}[1]{\mbox{\sc #1}}
\newcommand{\li}[1]{\langle#1\rangle}
\newcommand{\st}{\;s.t.\;}
\newcommand{\Real}{\R}
\newcommand{\Natural}{\N}
\newcommand{\Integer}{\Z}
% edit commands
\newcommand{\newedit}[2]{
\newcommand{#1}[2][default]{%
\ifthenelse{\boolean{technote@noedit}}{}{
\par\vspace{2mm}
\noindent
\begin{tabular}{|l|}\hline
\parbox{\linewidth-\tabcolsep*2}{{\bf #2:}\hfill\ifthenelse{\equal{##1}{default}}{}{##1}}\\\hline
\parbox{\linewidth-\tabcolsep*2}{\rule{0pt}{5mm}##2\rule[-2mm]{0pt}{2mm}}\\\hline
\end{tabular}
\par\vspace{2mm}
}
}
}
\newedit{\note}{Note}
\newedit{\comment}{Comment}
\newedit{\question}{Question}
\newedit{\content}{Content}
\newedit{\problem}{Problem}
\newcommand{\mnote}[1]{\marginpar{\scriptsize\it
\begin{minipage}[t]{0.8 in}
\raggedright #1
\end{minipage}}}
\newcommand{\Insert}[1]{\underline{#1}\marginpar{$|$}}
\newcommand{\Delete}[1]{\marginpar{$|$}
}
% lemma, theorem, etc.
\newtheorem{lemma}{Lemma}
\newtheorem{proposition}{Proposition}
\newtheorem{theorem}{Theorem}
\newtheorem{corollary}{Corollary}
\newtheorem{assumption}{Assumption}
\newtheorem{definition}{Definition}
\gdef\op|{\,|\;}
\gdef\op:{\,:\;}
\newcommand{\assign}{\leftarrow}
\newcommand{\inc}[1]{#1 \assign #1 + 1}
\newcommand{\isdef}{:=}
\newcommand{\ident}[1]{\mathit{#1}}
\def\newident#1{\expandafter\def\csname #1\endcsname{\ident{#1}}}
\newcommand{\eg}{{\it e.g.}}
\newcommand{\ie}{{\it i.e.}}
\newcommand{\apriori}{{\it apriori}}
\newcommand{\etal}{{\it et al.}}
\newcommand\ps@technote{%
\renewcommand\@oddhead{\theheader}%
\let\@evenhead\@oddhead
\renewcommand\@evenfoot
{\hfil\normalfont\textrm{\thepage}\hfil}%
\let\@oddfoot\@evenfoot
}

+ 352
- 0
spec/consensus/consensus.md View File

@ -0,0 +1,352 @@
---
order: 1
---
# Byzantine Consensus Algorithm
## Terms
- The network is composed of optionally connected _nodes_. Nodes
directly connected to a particular node are called _peers_.
- The consensus process in deciding the next block (at some _height_
`H`) is composed of one or many _rounds_.
- `NewHeight`, `Propose`, `Prevote`, `Precommit`, and `Commit`
represent state machine states of a round. (aka `RoundStep` or
just "step").
- A node is said to be _at_ a given height, round, and step, or at
`(H,R,S)`, or at `(H,R)` in short to omit the step.
- To _prevote_ or _precommit_ something means to broadcast a [prevote
vote](https://godoc.org/github.com/tendermint/tendermint/types#Vote)
or [first precommit
vote](https://godoc.org/github.com/tendermint/tendermint/types#FirstPrecommit)
for something.
- A vote _at_ `(H,R)` is a vote signed with the bytes for `H` and `R`
included in its [sign-bytes](../core/data_structures.md#vote).
- _+2/3_ is short for "more than 2/3"
- _1/3+_ is short for "1/3 or more"
- A set of +2/3 of prevotes for a particular block or `<nil>` at
`(H,R)` is called a _proof-of-lock-change_ or _PoLC_ for short.
## State Machine Overview
At each height of the blockchain a round-based protocol is run to
determine the next block. Each round is composed of three _steps_
(`Propose`, `Prevote`, and `Precommit`), along with two special steps
`Commit` and `NewHeight`.
In the optimal scenario, the order of steps is:
```md
NewHeight -> (Propose -> Prevote -> Precommit)+ -> Commit -> NewHeight ->...
```
The sequence `(Propose -> Prevote -> Precommit)` is called a _round_.
There may be more than one round required to commit a block at a given
height. Examples for why more rounds may be required include:
- The designated proposer was not online.
- The block proposed by the designated proposer was not valid.
- The block proposed by the designated proposer did not propagate
in time.
- The block proposed was valid, but +2/3 of prevotes for the proposed
block were not received in time for enough validator nodes by the
time they reached the `Precommit` step. Even though +2/3 of prevotes
are necessary to progress to the next step, at least one validator
may have voted `<nil>` or maliciously voted for something else.
- The block proposed was valid, and +2/3 of prevotes were received for
enough nodes, but +2/3 of precommits for the proposed block were not
received for enough validator nodes.
Some of these problems are resolved by moving onto the next round &
proposer. Others are resolved by increasing certain round timeout
parameters over each successive round.
## State Machine Diagram
```md
+-------------------------------------+
v |(Wait til `CommmitTime+timeoutCommit`)
+-----------+ +-----+-----+
+----------> | Propose +--------------+ | NewHeight |
| +-----------+ | +-----------+
| | ^
|(Else, after timeoutPrecommit) v |
+-----+-----+ +-----------+ |
| Precommit | <------------------------+ Prevote | |
+-----+-----+ +-----------+ |
|(When +2/3 Precommits for block found) |
v |
+--------------------------------------------------------------------+
| Commit |
| |
| * Set CommitTime = now; |
| * Wait for block, then stage/save/commit block; |
+--------------------------------------------------------------------+
```
# Background Gossip
A node may not have a corresponding validator private key, but it
nevertheless plays an active role in the consensus process by relaying
relevant meta-data, proposals, blocks, and votes to its peers. A node
that has the private keys of an active validator and is engaged in
signing votes is called a _validator-node_. All nodes (not just
validator-nodes) have an associated state (the current height, round,
and step) and work to make progress.
Between two nodes there exists a `Connection`, and multiplexed on top of
this connection are fairly throttled `Channel`s of information. An
epidemic gossip protocol is implemented among some of these channels to
bring peers up to speed on the most recent state of consensus. For
example,
- Nodes gossip `PartSet` parts of the current round's proposer's
proposed block. A LibSwift inspired algorithm is used to quickly
broadcast blocks across the gossip network.
- Nodes gossip prevote/precommit votes. A node `NODE_A` that is ahead
of `NODE_B` can send `NODE_B` prevotes or precommits for `NODE_B`'s
current (or future) round to enable it to progress forward.
- Nodes gossip prevotes for the proposed PoLC (proof-of-lock-change)
round if one is proposed.
- Nodes gossip to nodes lagging in blockchain height with block
[commits](https://godoc.org/github.com/tendermint/tendermint/types#Commit)
for older blocks.
- Nodes opportunistically gossip `ReceivedVote` messages to hint peers what
votes it already has.
- Nodes broadcast their current state to all neighboring peers. (but
is not gossiped further)
There's more, but let's not get ahead of ourselves here.
## Proposals
A proposal is signed and published by the designated proposer at each
round. The proposer is chosen by a deterministic and non-choking round
robin selection algorithm that selects proposers in proportion to their
voting power (see
[implementation](https://github.com/tendermint/tendermint/blob/master/types/validator_set.go)).
A proposal at `(H,R)` is composed of a block and an optional latest
`PoLC-Round < R` which is included iff the proposer knows of one. This
hints the network to allow nodes to unlock (when safe) to ensure the
liveness property.
## State Machine Spec
### Propose Step (height:H,round:R)
Upon entering `Propose`:
- The designated proposer proposes a block at `(H,R)`.
The `Propose` step ends:
- After `timeoutProposeR` after entering `Propose`. --> goto
`Prevote(H,R)`
- After receiving proposal block and all prevotes at `PoLC-Round`. -->
goto `Prevote(H,R)`
- After [common exit conditions](#common-exit-conditions)
### Prevote Step (height:H,round:R)
Upon entering `Prevote`, each validator broadcasts its prevote vote.
- First, if the validator is locked on a block since `LastLockRound`
but now has a PoLC for something else at round `PoLC-Round` where
`LastLockRound < PoLC-Round < R`, then it unlocks.
- If the validator is still locked on a block, it prevotes that.
- Else, if the proposed block from `Propose(H,R)` is good, it
prevotes that.
- Else, if the proposal is invalid or wasn't received on time, it
prevotes `<nil>`.
The `Prevote` step ends:
- After +2/3 prevotes for a particular block or `<nil>`. -->; goto
`Precommit(H,R)`
- After `timeoutPrevote` after receiving any +2/3 prevotes. --> goto
`Precommit(H,R)`
- After [common exit conditions](#common-exit-conditions)
### Precommit Step (height:H,round:R)
Upon entering `Precommit`, each validator broadcasts its precommit vote.
- If the validator has a PoLC at `(H,R)` for a particular block `B`, it
(re)locks (or changes lock to) and precommits `B` and sets
`LastLockRound = R`.
- Else, if the validator has a PoLC at `(H,R)` for `<nil>`, it unlocks
and precommits `<nil>`.
- Else, it keeps the lock unchanged and precommits `<nil>`.
A precommit for `<nil>` means "I didn’t see a PoLC for this round, but I
did get +2/3 prevotes and waited a bit".
The Precommit step ends:
- After +2/3 precommits for `<nil>`. --> goto `Propose(H,R+1)`
- After `timeoutPrecommit` after receiving any +2/3 precommits. --> goto
`Propose(H,R+1)`
- After [common exit conditions](#common-exit-conditions)
### Common exit conditions
- After +2/3 precommits for a particular block. --> goto
`Commit(H)`
- After any +2/3 prevotes received at `(H,R+x)`. --> goto
`Prevote(H,R+x)`
- After any +2/3 precommits received at `(H,R+x)`. --> goto
`Precommit(H,R+x)`
### Commit Step (height:H)
- Set `CommitTime = now()`
- Wait until block is received. --> goto `NewHeight(H+1)`
### NewHeight Step (height:H)
- Move `Precommits` to `LastCommit` and increment height.
- Set `StartTime = CommitTime+timeoutCommit`
- Wait until `StartTime` to receive straggler commits. --> goto
`Propose(H,0)`
## Proofs
### Proof of Safety
Assume that at most -1/3 of the voting power of validators is byzantine.
If a validator commits block `B` at round `R`, it's because it saw +2/3
of precommits at round `R`. This implies that 1/3+ of honest nodes are
still locked at round `R' > R`. These locked validators will remain
locked until they see a PoLC at `R' > R`, but this won't happen because
1/3+ are locked and honest, so at most -2/3 are available to vote for
anything other than `B`.
### Proof of Liveness
If 1/3+ honest validators are locked on two different blocks from
different rounds, a proposers' `PoLC-Round` will eventually cause nodes
locked from the earlier round to unlock. Eventually, the designated
proposer will be one that is aware of a PoLC at the later round. Also,
`timeoutProposalR` increments with round `R`, while the size of a
proposal are capped, so eventually the network is able to "fully gossip"
the whole proposal (e.g. the block & PoLC).
### Proof of Fork Accountability
Define the JSet (justification-vote-set) at height `H` of a validator
`V1` to be all the votes signed by the validator at `H` along with
justification PoLC prevotes for each lock change. For example, if `V1`
signed the following precommits: `Precommit(B1 @ round 0)`,
`Precommit(<nil> @ round 1)`, `Precommit(B2 @ round 4)` (note that no
precommits were signed for rounds 2 and 3, and that's ok),
`Precommit(B1 @ round 0)` must be justified by a PoLC at round 0, and
`Precommit(B2 @ round 4)` must be justified by a PoLC at round 4; but
the precommit for `<nil>` at round 1 is not a lock-change by definition
so the JSet for `V1` need not include any prevotes at round 1, 2, or 3
(unless `V1` happened to have prevoted for those rounds).
Further, define the JSet at height `H` of a set of validators `VSet` to
be the union of the JSets for each validator in `VSet`. For a given
commit by honest validators at round `R` for block `B` we can construct
a JSet to justify the commit for `B` at `R`. We say that a JSet
_justifies_ a commit at `(H,R)` if all the committers (validators in the
commit-set) are each justified in the JSet with no duplicitous vote
signatures (by the committers).
- **Lemma**: When a fork is detected by the existence of two
conflicting [commits](../core/data_structures.md#commit), the
union of the JSets for both commits (if they can be compiled) must
include double-signing by at least 1/3+ of the validator set.
**Proof**: The commit cannot be at the same round, because that
would immediately imply double-signing by 1/3+. Take the union of
the JSets of both commits. If there is no double-signing by at least
1/3+ of the validator set in the union, then no honest validator
could have precommitted any different block after the first commit.
Yet, +2/3 did. Reductio ad absurdum.
As a corollary, when there is a fork, an external process can determine
the blame by requiring each validator to justify all of its round votes.
Either we will find 1/3+ who cannot justify at least one of their votes,
and/or, we will find 1/3+ who had double-signed.
### Alternative algorithm
Alternatively, we can take the JSet of a commit to be the "full commit".
That is, if light clients and validators do not consider a block to be
committed unless the JSet of the commit is also known, then we get the
desirable property that if there ever is a fork (e.g. there are two
conflicting "full commits"), then 1/3+ of the validators are immediately
punishable for double-signing.
There are many ways to ensure that the gossip network efficiently share
the JSet of a commit. One solution is to add a new message type that
tells peers that this node has (or does not have) a +2/3 majority for B
(or) at (H,R), and a bitarray of which votes contributed towards that
majority. Peers can react by responding with appropriate votes.
We will implement such an algorithm for the next iteration of the
Tendermint consensus protocol.
Other potential improvements include adding more data in votes such as
the last known PoLC round that caused a lock change, and the last voted
round/step (or, we may require that validators not skip any votes). This
may make JSet verification/gossip logic easier to implement.
### Censorship Attacks
Due to the definition of a block
[commit](https://github.com/tendermint/tendermint/blob/master/docs/tendermint-core/validators.md), any 1/3+ coalition of
validators can halt the blockchain by not broadcasting their votes. Such
a coalition can also censor particular transactions by rejecting blocks
that include these transactions, though this would result in a
significant proportion of block proposals to be rejected, which would
slow down the rate of block commits of the blockchain, reducing its
utility and value. The malicious coalition might also broadcast votes in
a trickle so as to grind blockchain block commits to a near halt, or
engage in any combination of these attacks.
If a global active adversary were also involved, it can partition the
network in such a way that it may appear that the wrong subset of
validators were responsible for the slowdown. This is not just a
limitation of Tendermint, but rather a limitation of all consensus
protocols whose network is potentially controlled by an active
adversary.
### Overcoming Forks and Censorship Attacks
For these types of attacks, a subset of the validators through external
means should coordinate to sign a reorg-proposal that chooses a fork
(and any evidence thereof) and the initial subset of validators with
their signatures. Validators who sign such a reorg-proposal forego its
collateral on all other forks. Clients should verify the signatures on
the reorg-proposal, verify any evidence, and make a judgement or prompt
the end-user for a decision. For example, a phone wallet app may prompt
the user with a security warning, while a refrigerator may accept any
reorg-proposal signed by +1/2 of the original validators.
No non-synchronous Byzantine fault-tolerant algorithm can come to
consensus when 1/3+ of validators are dishonest, yet a fork assumes that
1/3+ of validators have already been dishonest by double-signing or
lock-changing without justification. So, signing the reorg-proposal is a
coordination problem that cannot be solved by any non-synchronous
protocol (i.e. automatically, and without making assumptions about the
reliability of the underlying network). It must be provided by means
external to the weakly-synchronous Tendermint consensus algorithm. For
now, we leave the problem of reorg-proposal coordination to human
coordination via internet media. Validators must take care to ensure
that there are no significant network partitions, to avoid situations
where two conflicting reorg-proposals are signed.
Assuming that the external coordination medium and protocol is robust,
it follows that forks are less of a concern than [censorship
attacks](#censorship-attacks).
### Canonical vs subjective commit
We distinguish between "canonical" and "subjective" commits. A subjective commit is what
each validator sees locally when they decide to commit a block. The canonical commit is
what is included by the proposer of the next block in the `LastCommit` field of
the block. This is what makes it canonical and ensures every validator agrees on the canonical commit,
even if it is different from the +2/3 votes a validator has seen, which caused the validator to
commit the respective block. Each block contains a canonical +2/3 commit for the previous
block.

+ 43
- 0
spec/consensus/creating-proposal.md View File

@ -0,0 +1,43 @@
---
order: 2
---
# Creating a proposal
A block consists of a header, transactions, votes (the commit),
and a list of evidence of malfeasance (ie. signing conflicting votes).
We include no more than 1/10th of the maximum block size
(`ConsensusParams.Block.MaxBytes`) of evidence with each block.
## Reaping transactions from the mempool
When we reap transactions from the mempool, we calculate maximum data
size by subtracting maximum header size (`MaxHeaderBytes`), the maximum
amino overhead for a block (`MaxAminoOverheadForBlock`), the size of
the last commit (if present) and evidence (if present). While reaping
we account for amino overhead for each transaction.
```go
func MaxDataBytes(maxBytes int64, valsCount, evidenceCount int) int64 {
return maxBytes -
MaxOverheadForBlock -
MaxHeaderBytes -
int64(valsCount)*MaxVoteBytes -
int64(evidenceCount)*MaxEvidenceBytes
}
```
## Validating transactions in the mempool
Before we accept a transaction in the mempool, we check if it's size is no more
than {MaxDataSize}. {MaxDataSize} is calculated using the same formula as
above, except we subtract the max number of evidence, {MaxNum} by the maximum size of evidence
```go
func MaxDataBytesUnknownEvidence(maxBytes int64, valsCount int) int64 {
return maxBytes -
MaxOverheadForBlock -
MaxHeaderBytes -
(maxNumEvidence * MaxEvidenceBytes)
}
```

+ 199
- 0
spec/consensus/evidence.md View File

@ -0,0 +1,199 @@
# Evidence
Evidence is an important component of Tendermint's security model. Whilst the core
consensus protocol provides correctness gaurantees for state machine replication
that can tolerate less than 1/3 failures, the evidence system looks to detect and
gossip byzantine faults whose combined power is greater than or equal to 1/3. It is worth noting that
the evidence system is designed purely to detect possible attacks, gossip them,
commit them on chain and inform the application running on top of Tendermint.
Evidence in itself does not punish "bad actors", this is left to the discretion
of the application. A common form of punishment is slashing where the validators
that were caught violating the protocol have all or a portion of their voting
power removed. Evidence, given the assumption that 1/3+ of the network is still
byzantine, is susceptible to censorship and should therefore be considered added
security on a "best effort" basis.
This document walks through the various forms of evidence, how they are detected,
gossiped, verified and committed.
> NOTE: Evidence here is internal to tendermint and should not be confused with
> application evidence
## Detection
### Equivocation
Equivocation is the most fundamental of byzantine faults. Simply put, to prevent
replication of state across all nodes, a validator tries to convince some subset
of nodes to commit one block whilst convincing another subset to commit a
different block. This is achieved by double voting (hence
`DuplicateVoteEvidence`). A successful duplicate vote attack requires greater
than 1/3 voting power and a (temporary) network partition between the aforementioned
subsets. This is because in consensus, votes are gossiped around. When a node
observes two conflicting votes from the same peer, it will use the two votes of
evidence and begin gossiping this evidence to other nodes. [Verification](#duplicatevoteevidence) is addressed further down.
```go
type DuplicateVoteEvidence struct {
VoteA Vote
VoteB Vote
// and abci specific fields
}
```
### Light Client Attacks
Light clients also comply with the 1/3+ security model, however, by using a
different, more lightweight verification method they are subject to a
different kind of 1/3+ attack whereby the byzantine validators could sign an
alternative light block that the light client will think is valid. Detection,
explained in greater detail
[here](../light-client/detection/detection_003_reviewed.md), involves comparison
with multiple other nodes in the hope that at least one is "honest". An "honest"
node will return a challenging light block for the light client to validate. If
this challenging light block also meets the
[validation criteria](../light-client/verification/verification_001_published.md)
then the light client sends the "forged" light block to the node.
[Verification](#lightclientattackevidence) is addressed further down.
```go
type LightClientAttackEvidence struct {
ConflictingBlock LightBlock
CommonHeight int64
// and abci specific fields
}
```
## Verification
If a node receives evidence, it will first try to verify it, then persist it.
Evidence of byzantine behavior should only be committed once (uniqueness) and
should be committed within a certain period from the point that it occurred
(timely). Timelines is defined by the `EvidenceParams`: `MaxAgeNumBlocks` and
`MaxAgeDuration`. In Proof of Stake chains where validators are bonded, evidence
age should be less than the unbonding period so validators still can be
punished. Given these two propoerties the following initial checks are made.
1. Has the evidence expired? This is done by taking the height of the `Vote`
within `DuplicateVoteEvidence` or `CommonHeight` within
`LightClientAttakEvidence`. The evidence height is then used to retrieve the
header and thus the time of the block that corresponds to the evidence. If
`CurrentHeight - MaxAgeNumBlocks > EvidenceHeight` && `CurrentTime -
MaxAgeDuration > EvidenceTime`, the evidence is considered expired and
ignored.
2. Has the evidence already been committed? The evidence pool tracks the hash of
all committed evidence and uses this to determine uniqueness. If a new
evidence has the same hash as a committed one, the new evidence will be
ignored.
### DuplicateVoteEvidence
Valid `DuplicateVoteEvidence` must adhere to the following rules:
- Validator Address, Height, Round and Type must be the same for both votes
- BlockID must be different for both votes (BlockID can be for a nil block)
- Validator must have been in the validator set at that height
- Vote signature must be correctly signed. This also uses `ChainID` so we know
that the fault occurred on this chain
### LightClientAttackEvidence
Valid Light Client Attack Evidence must adhere to the following rules:
- If the header of the light block is invalid, thus indicating a lunatic attack,
the node must check that they can use `verifySkipping` from their header at
the common height to the conflicting header
- If the header is valid, then the validator sets are the same and this is
either a form of equivocation or amnesia. We therefore check that 2/3 of the
validator set also signed the conflicting header.
- The nodes own header at the same height as the conflicting header must have a
different hash to the conflicting header.
- If the nodes latest header is less in height to the conflicting header, then
the node must check that the conflicting block has a time that is less than
this latest header (This is a forward lunatic attack).
## Gossiping
If a node verifies evidence it then broadcasts it to all peers, continously sending
the same evidence once every 10 seconds until the evidence is seen on chain or
expires.
## Commiting on Chain
Evidence takes strict priority over regular transactions, thus a block is filled
with evidence first and transactions take up the remainder of the space. To
mitigate the threat of an already punished node from spamming the network with
more evidence, the size of the evidence in a block can be capped by
`EvidenceParams.MaxBytes`. Nodes receiving blocks with evidence will validate
the evidence before sending `Prevote` and `Precommit` votes. The evidence pool
will usually cache verifications so that this process is much quicker.
## Sending Evidence to the Application
After evidence is committed, the block is then processed by the block executor
which delivers the evidence to the application via `EndBlock`. Evidence is
stripped of the actual proof, split up per faulty validator and only the
validator, height, time and evidence type is sent.
```proto
enum EvidenceType {
UNKNOWN = 0;
DUPLICATE_VOTE = 1;
LIGHT_CLIENT_ATTACK = 2;
}
message Evidence {
EvidenceType type = 1;
// The offending validator
Validator validator = 2 [(gogoproto.nullable) = false];
// The height when the offense occurred
int64 height = 3;
// The corresponding time where the offense occurred
google.protobuf.Timestamp time = 4 [
(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
// Total voting power of the validator set in case the ABCI application does
// not store historical validators.
// https://github.com/tendermint/tendermint/issues/4581
int64 total_voting_power = 5;
}
```
`DuplicateVoteEvidence` and `LightClientAttackEvidence` are self-contained in
the sense that the evidence can be used to derive the `abci.Evidence` that is
sent to the application. Because of this, extra fields are necessary:
```go
type DuplicateVoteEvidence struct {
VoteA *Vote
VoteB *Vote
// abci specific information
TotalVotingPower int64
ValidatorPower int64
Timestamp time.Time
}
type LightClientAttackEvidence struct {
ConflictingBlock *LightBlock
CommonHeight int64
// abci specific information
ByzantineValidators []*Validator
TotalVotingPower int64
Timestamp time.Time
}
```
These ABCI specific fields don't affect validity of the evidence itself but must
be consistent amongst nodes and agreed upon on chain. If evidence with the
incorrect abci information is sent, a node will create new evidence from it and
replace the ABCI fields with the correct information.

+ 9
- 0
spec/consensus/light-client/README.md View File

@ -0,0 +1,9 @@
---
order: 1
parent:
title: Light Client
order: false
---
# Tendermint Light Client Protocol
Deprecated, please see [light-client](../../light-client/README.md).

+ 3
- 0
spec/consensus/light-client/accountability.md View File

@ -0,0 +1,3 @@
# Fork accountability
Deprecated, please see [light-client/accountability](../../light-client/accountability/README.md).

BIN
spec/consensus/light-client/assets/light-node-image.png View File

Before After
Width: 3038  |  Height: 2901  |  Size: 119 KiB

+ 3
- 0
spec/consensus/light-client/detection.md View File

@ -0,0 +1,3 @@
# Detection
Deprecated, please see [light-client/detection](../../light-client/detection/README.md).

+ 3
- 0
spec/consensus/light-client/verification.md View File

@ -0,0 +1,3 @@
# Core Verification
Deprecated, please see [light-client/accountability](../../light-client/verification/README.md).

+ 157
- 0
spec/consensus/proposer-based-timestamp/README.md View File

@ -0,0 +1,157 @@
# Proposer-Based Timestamps (PBTS)
This section describes a version of the Tendermint consensus protocol
that uses proposer-based timestamps.
## Context
Tendermint provides a deterministic, Byzantine fault-tolerant, source of time,
defined by the `Time` field present in the headers of committed blocks.
In the current consensus implementation, the timestamp of a block is
computed by the [`BFTTime`][bfttime] algorithm:
- Validators include a timestamp in the `Precommit` messages they broadcast.
Timestamps are retrieved from the validators' local clocks,
with the only restriction that they must be **monotonic**:
- The timestamp of a `Precommit` message voting for a block
cannot be earlier than the `Time` field of that block;
- The timestamp of a block is deterministically computed from the timestamps of
a set of `Precommit` messages that certify the commit of the previous block.
This certificate, a set of `Precommit` messages from a round of the previous height,
is selected by the block's proposer and stored in the `Commit` field of the block:
- The block timestamp is the *median* of the timestamps of the `Precommit` messages
included in the `Commit` field, weighted by their voting power.
Block timestamps are **monotonic** because
timestamps of valid `Precommit` messages are monotonic;
Assuming that the voting power controlled by Byzantine validators is bounded by `f`,
the cumulative voting power of any valid `Commit` set must be at least `2f+1`.
As a result, the timestamp computed by `BFTTime` is not influenced by Byzantine validators,
as the weighted median of `Commit` timestamps comes from the clock of a non-faulty validator.
Tendermint does not make any assumptions regarding the clocks of (correct) validators,
as block timestamps have no impact in the consensus protocol.
However, the `Time` field of committed blocks is used by other components of Tendermint,
such as IBC, the evidence, staking, and slashing modules.
And it is used based on the common belief that block timestamps
should bear some resemblance to real time, which is **not guaranteed**.
A more comprehensive discussion of the limitations of `BFTTime`
can be found in the [first draft][main_v1] of this proposal.
Of particular interest is to possibility of having validators equipped with "faulty" clocks,
not fairly accurate with real time, that control more than `f` voting power,
plus the proposer's flexibility when selecting a `Commit` set,
and thus determining the timestamp for a block.
## Proposal
In the proposed solution, the timestamp of a block is assigned by its
proposer, according with its local clock.
In other words, the proposer of a block also *proposes* a timestamp for the block.
Validators can accept or reject a proposed block.
A block is only accepted if its timestamp is acceptable.
A proposed timestamp is acceptable if it is *received* within a certain time window,
determined by synchronous parameters.
PBTS therefore augments the system model considered by Tendermint with *synchronous assumptions*:
- **Synchronized clocks**: simultaneous clock reads at any two correct validators
differ by at most `PRECISION`;
- **Bounded message delays**: the end-to-end delay for delivering a message to all correct validators
is bounded by `MSGDELAY`.
This assumption is restricted to `Proposal` messages, broadcast by proposers.
`PRECISION` and `MSGDELAY` are consensus parameters, shared by all validators,
that define whether the timestamp of a block is acceptable.
Let `t` be the time, read from its local clock, at which a validator
receives, for the first time, a proposal with timestamp `ts`:
- **[Time-Validity]** The proposed timestamp `ts` received at local time `t`
is accepted if it satisfies the **timely** predicate:
> `ts - PRECISION <= t <= ts + MSGDELAY + PRECISION`
The left inequality of the *timely* predicate establishes that proposed timestamps
should be in the past, when adjusted by the clocks `PRECISION`.
The right inequality of the *timely* predicate establishes that proposed timestamps
should not be too much in the past, more precisely, not more than `MSGDELAY` in the past,
when adjusted by the clocks `PRECISION`.
A more detailed and formalized description is available in the
[System Model and Properties][sysmodel] document
## Implementation
The implementation of PBTS requires some changes in Tendermint consensus algorithm,
summarized below:
- A proposer timestamps a block with the current time, read from its local clock.
The block's timestamp represents the time at which it was assembled
(after the `getValue()` call in line 18 of the [arXiv][arXiv] algorithm):
- Block timestamps are definitive, meaning that the original timestamp
is retained when a block is re-proposed (line 16);
- To preserve monotonicity, a proposer might need to wait until its clock
reads a time greater than the timestamp of the previous block;
- A validator only prevotes for *timely* blocks,
that is, blocks whose timestamps are considered *timely* (compared to the original Tendermint consensus, a check is added to line 23).
If the block proposed in a round is considered *untimely*,
the validator prevotes `nil` (line 26):
- Validators register the time at which they received `Proposal` messages,
in order to evaluate the *timely* predicate;
- Blocks that are re-proposed because they received `2f+1 Prevotes`
in a previous round (line 28) are not subject to the *timely* predicate,
as they have already been evaluated as *timely* at a previous round.
The more complex change proposed regards blocks that can be re-proposed in multiple rounds.
The current solution improves the [first version of the specification][algorithm_v1] (that never had been implemented)
by simplifying the way this situation is handled,
from a recursive reasoning regarding valid blocks that are re-proposed.
The full solution is detailed and formalized in the [Protocol Specification][algorithm] document.
## Further details
- [System Model and Properties][sysmodel]
- [Protocol Specification][algorithm]
- [TLA+ Specification][proposertla] (first draft, not updated)
### Open issues
- [PBTS: evidence #355][issue355]: not really clear the context, probably not going to be solved.
- [PBTS: should synchrony parameters be adaptive? #371][issue371]
- [PBTS: Treat proposal and block parts explicitly in the spec #372][issue372]
- [PBTS: margins for proposal times assigned by Byzantine proposers #377][issue377]
### Closed issues
- [Proposer time - fix message filter condition #353][issue353]
- [PBTS: association between timely predicate and timeout_commit #370][issue370]
[main_v1]: ./v1/pbts_001_draft.md
[algorithm]: ./pbts-algorithm_002_draft.md
[algorithm_v1]: ./v1/pbts-algorithm_001_draft.md
[sysmodel]: ./pbts-sysmodel_002_draft.md
[sysmodel_v1]: ./v1/pbts-sysmodel_001_draft.md
[proposertla]: ./tla/TendermintPBT_001_draft.tla
[bfttime]: https://github.com/tendermint/tendermint/blob/master/spec/consensus/bft-time.md
[arXiv]: https://arxiv.org/pdf/1807.04938.pdf
[issue353]: https://github.com/tendermint/spec/issues/353
[issue355]: https://github.com/tendermint/spec/issues/355
[issue370]: https://github.com/tendermint/spec/issues/370
[issue371]: https://github.com/tendermint/spec/issues/371
[issue372]: https://github.com/tendermint/spec/issues/372
[issue377]: https://github.com/tendermint/spec/issues/377

+ 148
- 0
spec/consensus/proposer-based-timestamp/pbts-algorithm_002_draft.md View File

@ -0,0 +1,148 @@
# PBTS: Protocol Specification
## Proposal Time
PBTS computes for a proposed value `v` the proposal time `v.time`, with bounded difference to the actual real-time the proposed value was generated.
The proposal time is read from the clock of the process that proposes a value for the first time, its original proposer.
With PBTS, therefore, we assume that processes have access to **synchronized clocks**.
The proper definition of what it means can be found in the [system model][sysmodel],
but essentially we assume that two correct processes do not simultaneous read from their clocks
time values that differ more than `PRECISION`, which is a system parameter.
### Proposal times are definitive
When a value `v` is produced by a process, it also assigns the associated proposal time `v.time`.
If the same value `v` is then re-proposed in a subsequent round of consensus,
it retains its original time, assigned by its original proposer.
A value `v` should re-proposed when it becomes locked by the network, i.e., when it receives `2f + 1 PREVOTES` in a round `r` of consensus.
This means that processes with `2f + 1`-equivalent voting power accepted, in round `r`, both `v` and its associated time `v.time`.
Since the originally proposed value and its associated time were considered valid, there is no reason for reassigning `v.time`.
In the [first version][algorithm_v1] of this specification, proposals were defined as pairs `(v, time)`.
In addition, the same value `v` could be proposed, in different rounds, but would be associated to distinct times each time it was reproposed.
Since this possibility does not exist in this second specification, the proposal time became part of the proposed value.
With this simplification, several small changes to the [arXiv][arXiv] algorithm are no longer required.
## Time Monotonicity
Values decided in successive heights of consensus must have increasing times, so:
- Monotonicity: for any process `p` and any two decided heights `h` and `h'`, if `h > h'` then `decision_p[h].time > decision_p[h'].time`.
For ensuring time monotonicity, it is enough to ensure that a value `v` proposed by process `p` at height `h_p` has `v.time > decision_p[h_p-1].time`.
So, if process `p` is the proposer of a round of height `h_p` and reads from its clock a time `now_p <= decision_p[h_p-1]`,
it should postpone the generation of its proposal until `now_p > decision_p[h_p-1]`.
> Although it should be considered, this scenario is unlikely during regular operation,
as from `decision_p[h_p-1].time` and the start of height `h_p`, a complete consensus instance need to terminate.
Notice that monotonicity is not introduced by this proposal, being already ensured by [`BFTTime`][bfttime].
In `BFTTime`, the `Timestamp` field of every `Precommit` message of height `h_p` sent by a correct process is required to be larger than `decision_p[h_p-1].time`, as one of such `Timestamp` fields becomes the time assigned to a value proposed at height `h_p`.
The time monotonicity of values proposed in heights of consensus is verified by the `valid()` predicate, to which every proposed value is submitted.
A value rejected by the `valid()` implementation is not accepted by any correct process.
## Timely Proposals
PBTS introduces a new requirement for a process to accept a proposal: the proposal must be `timely`.
It is a temporal requirement, associated with the following synchrony (that is, timing)
[assumptions][sysmodel] regarding the behavior of processes and the network:
- Synchronized clocks: the values simultaneously read from clocks of any two correct processes differ by at most `PRECISION`;
- Bounded transmission delays: the real time interval between the sending of a proposal at a correct process, and the reception of the proposal at any correct process is upper bounded by `MSGDELAY`.
#### **[PBTS-RECEPTION-STEP.1]**
Let `now_p` be the time, read from the clock of process `p`, at which `p` receives the proposed value `v`.
The proposal is considered `timely` by `p` when:
1. `now_p >= v.time - PRECISION`
1. `now_p <= v.time + MSGDELAY + PRECISION`
The first condition derives from the fact that the generation and sending of `v` precedes its reception.
The minimum receiving time `now_p` for `v` be considered `timely` by `p` is derived from the extreme scenario when
the clock of `p` is `PRECISION` *behind* of the clock of the proposer of `v`, and the proposal's transmission delay is `0` (minimum).
The second condition derives from the assumption of an upper bound for the transmission delay of a proposal.
The maximum receiving time `now_p` for `v` be considered `timely` by `p` is derived from the extreme scenario when
the clock of `p` is `PRECISION` *ahead* of the clock of the proposer of `v`, and the proposal's transmission delay is `MSGDELAY` (maximum).
## Updated Consensus Algorithm
The following changes are proposed for the algorithm in the [arXiv paper][arXiv].
#### New `StartRound`
There are two additions to the `propose` round step when executed by the `proposer` of a round:
1. to ensure time monotonicity, the proposer does not propose a value until its current local time becomes greater than the previously decided value's time
1. when the proposer produce a new proposal it sets the proposal's time to its current local time
- no changes are made to the logic when a proposer has a non-nil `validValue`, which retains its original proposal time.
#### **[PBTS-ALG-STARTROUND.1]**
```go
function StartRound(round) {
round_p ← round
step_p ← propose
if proposer(h_p, round_p) = p {
wait until now_p > decision_p[h_p-1].time // time monotonicity
if validValue_p != nil {
proposal ← validValue_p
} else {
proposal ← getValue()
proposal.time ← now_p // proposal time
}
broadcast ⟨PROPOSAL, h_p, round_p, proposal, validRound_p⟩
} else {
schedule OnTimeoutPropose(h_p,round_p) to be executed after timeoutPropose(round_p)
}
}
```
#### New Rule Replacing Lines 22 - 27
The rule on line 22 applies to values `v` proposed for the first time, i.e., for proposals not backed by `2f + 1 PREVOTE`s for `v` in a previous round.
The `PROPOSAL` message, in this case, carry `-1` in its `validRound` field.
The new rule for issuing a `PREVOTE` for a proposed value `v` requires the value to be `timely`.
As the `timely` predicate is evaluated in the moment that the value is received,
as part of a `PROPOSAL` message, we require the `PROPOSAL` message to be `timely`.
#### **[PBTS-ALG-UPON-PROP.1]**
```go
upon timely(⟨PROPOSAL, h_p, round_p, v, −1⟩) from proposer(h_p, round_p) while step_p = propose do {
if valid(v) ∧ (lockedRound_p = −1 ∨ lockedValue_p = v) {
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
}
else {
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
}
step_p ← prevote
}
```
#### Rules at Lines 28 - 33 remain unchanged
The rule on line 28 applies to values `v` proposed again in the current round because its proposer received `2f + 1 PREVOTE`s for `v` in a previous round `vr`.
This means that there was a round `r <= vr` in which `2f + 1` processes accepted `v` for the first time, and so sent `PREVOTE`s for `v`.
Which, in turn, means that these processes executed the line 22 of the algorithm, and therefore judged `v` as a `timely` proposal.
In other words, we don't need to verify whether `v` is a timely proposal because at least `f + 1` processes judged `v` as `timely` in a previous round,
and because, since `v` was re-proposed as a `validValue` (line 16), `v.time` has not being updated from its original proposal.
**All other rules remains unchanged.**
Back to [main document][main].
[main]: ./README.md
[algorithm_v1]: ./v1/pbts-algorithm_001_draft.md
[sysmodel]: ./pbts-sysmodel_002_draft.md
[bfttime]: https://github.com/tendermint/tendermint/blob/master/spec/consensus/bft-time.md
[arXiv]: https://arxiv.org/pdf/1807.04938.pdf

+ 258
- 0
spec/consensus/proposer-based-timestamp/pbts-sysmodel_002_draft.md View File

@ -0,0 +1,258 @@
# PBTS: System Model and Properties
## System Model
#### **[PBTS-CLOCK-NEWTON.0]**
There is a reference Newtonian real-time `t` (UTC).
No process has direct access to this reference time, used only for specification purposes.
### Synchronized clocks
Processes are assumed to be equipped with synchronized clocks.
This requires processes to periodically synchronize their local clocks with an
external and trusted source of the time (e.g. NTP servers).
Each synchronization cycle aligns the process local clock with the external
source of time, making it a *fairly accurate* source of real time.
The periodic (re)synchronization aims to correct the *drift* of local clocks,
which tend to pace slightly faster or slower than the real time.
To avoid an excessive level detail in the parameters and guarantees of
synchronized clocks, we adopt a single system parameter `PRECISION` to
encapsulate the potential inaccuracy of the synchronization mechanisms,
and drifts of local clocks from real time.
#### **[PBTS-CLOCK-PRECISION.0]**
There exists a system parameter `PRECISION`, such that
for any two processes `p` and `q`, with local clocks `C_p` and `C_q`,
that read their local clocks at the same real-time `t`, we have:
- If `p` and `q` are equipped with synchronized clocks, then `|C_p(t) - C_q(t)| < PRECISION`
`PRECISION` thus bounds the difference on the times simultaneously read by processes
from their local clocks, so that their clocks can be considered synchronized.
#### Accuracy
The [first draft][sysmodel_v1] of this specification included a second clock-related parameter, `ACCURACY`,
that relates the values read by processes from their synchronized clocks with real time:
- If `p` is a process is equipped with a synchronized clock, then at real time
`t` it reads from its clock time `C_p(t)` with `|C_p(t) - t| < ACCURACY`
The adoption of `ACCURACY` as the upper bound on the difference between clock
readings and real time, however, renders the `PRECISION` parameter redundant.
In fact, if we assume that clocks readings are at most `ACCURACY` from real
time, we would therefore be assuming that they cannot be more than `2 * ACCURACY`
apart from each other, thus establishing a worst-case upper bound for `PRECISION`.
The approach we take is to assume that processes clocks are periodically
synchronized with an external source of time, thus improving their accuracy.
This allows us to adopt a relaxed version of the above `ACCURACY` definition:
##### **[PBTS-CLOCK-FAIR.0]**
- At real time `t` there is at least one correct process `p` which clock marks
`C_p(t)` with `|C_p(t) - t| < ACCURACY`
Then, through [PBTS-CLOCK-PRECISION] we can extend this relation of clock times
with real time to every correct process, which will have a clock with accuracy
bound by `ACCURACY + PRECISION`.
But, for the sake of simpler specification we can assume that the `PRECISION`,
which is a worst-case parameter that applies to all correct processes,
includes the best `ACCURACY` achieved by any of them.
### Message Delays
The assumption that processes have access to synchronized clocks ensures that proposal times
assigned by *correct processes* have a bounded relation with the real time.
It is not enough, however, to identify (and reject) proposal times proposed by Byzantine processes.
To properly evaluate whether the time assigned to a proposal is consistent with the real time,
we need some information regarding the time it takes for a message carrying a proposal
to reach all its (correct) destinations.
More precisely, the *maximum delay* for delivering a proposal to its destinations allows
defining a lower bound, a *minimum time* that a correct process assigns to proposal.
While *minimum delay* for delivering a proposal to a destination allows defining
an upper bound, the *maximum time* assigned to a proposal.
#### **[PBTS-MSG-D.0]**
There exists a system parameter `MSGDELAY` for end-to-end delays of messages carrying proposals,
such for any two correct processes `p` and `q`, and any real time `t`:
- If `p` sends a message `m` carrying a proposal at time `ts`,
then if `q` receives the message and learns the proposal,
`q` does that at time `t` such that `ts <= t <= ts + MSGDELAY`.
While we don't want to impose particular restrictions regarding the format of `m`,
we need to assume that their size is upper bounded.
In practice, using messages with a fixed-size to carry proposals allows
for a more accurate estimation of `MSGDELAY`, and therefore is advised.
## Problem Statement
In this section we define the properties of Tendermint consensus
(cf. the [arXiv paper][arXiv]) in this new system model.
#### **[PBTS-PROPOSE.0]**
A proposer proposes a consensus value `v` with an associated proposal time `v.time`.
#### **[PBTS-INV-AGREEMENT.0]**
[Agreement] No two correct processes decide on different values `v`. (This implies that no two correct processes decide on different proposal times `v.time`.)
#### **[PBTS-INV-VALID.0]**
[Validity] If a correct process decides on value `v`,
then `v` satisfies a predefined `valid` predicate.
#### **[PBTS-INV-TIMELY.0]**
[Time-Validity] If a correct process decides on value `v`,
then the associated proposal time `v.time` satisfies a predefined `timely` predicate.
> Both [Validity] and [Time-Validity] must be observed even if up to `2f` validators are faulty.
### Timely proposals
The `timely` predicate is evaluated when a process receives a proposal.
Let `now_p` be time a process `p` reads from its local clock when `p` receives a proposal.
Let `v` be the proposed value and `v.time` the proposal time.
The proposal is considered `timely` by `p` if:
#### **[PBTS-RECEPTION-STEP.1]**
1. `now_p >= v.time - PRECISION` and
1. `now_p <= v.time + MSGDELAY + PRECISION`
### Timely Proof-of-Locks
We denote by `POL(v,r)` a *Proof-of-Lock* of value `v` at the round `r` of consensus.
`POL(v,r)` consists of a set of `PREVOTE` messages of round `r` for the value `v`
from processes whose cumulative voting power is at least `2f + 1`.
#### **[PBTS-TIMELY-POL.1]**
If
- there is a valid `POL(v,r*)` for height `h`, and
- `r*` is the lowest-numbered round `r` of height `h` for which there is a valid `POL(v,r)`, and
- `POL(v,r*)` contains a `PREVOTE` message from at least one correct process,
Then, where `p` is a such correct process:
- `p` received a `PROPOSE` message of round `r*` and height `h`, and
- the `PROPOSE` message contained a proposal for value `v` with proposal time `v.time`, and
- a correct process `p` considered the proposal `timely`.
The round `r*` above defined will be, in most cases,
the round in which `v` was originally proposed, and when `v.time` was assigned,
using a `PROPOSE` message with `POLRound = -1`.
In any case, at least one correct process must consider the proposal `timely` at round `r*`
to enable a valid `POL(v,r*)` to be observed.
### Derived Proof-of-Locks
#### **[PBTS-DERIVED-POL.1]**
If
- there is a valid `POL(v,r)` for height `h`, and
- `POL(v,r)` contains a `PREVOTE` message from at least one correct process,
Then
- there is a valid `POL(v,r*)` for height `h`, with `r* <= r`, and
- `POL(v,r*)` contains a `PREVOTE` message from at least one correct process, and
- a correct process considered the proposal for `v` `timely` at round `r*`.
The above relation derives from a recursion on the round number `r`.
It is trivially observed when `r = r*`, the base of the recursion,
when a timely `POL(v,r*)` is obtained.
We need to ensure that, once a timely `POL(v,r*)` is obtained,
it is possible to obtain a valid `POL(v,r)` with `r > r*`,
without the need of satisfying the `timely` predicate (again) in round `r`.
In fact, since rounds are started in order, it is not likely that
a proposal time `v.time`, assigned at round `r*`,
will still be considered `timely` when the round `r > r*` is in progress.
In other words, the algorithm should ensure that once a `POL(v,r*)` attests
that the proposal for `v` is `timely`,
further valid `POL(v,r)` with `r > r*` can be obtained,
even though processes do not consider the proposal for `v` `timely` any longer.
> This can be achieved if the proposer of round `r' > r*` proposes `v` in a `PROPOSE` message
with `POLRound = r*`, and at least one correct processes is aware of a `POL(v,r*)`.
> From this point, if a valid `POL(v,r')` is achieved, it can replace the adopted `POL(v,r*)`.
### SAFETY
The safety of the algorithm requires a *timely* proof-of-lock for a decided value,
either directly evaluated by a correct process,
or indirectly received through a derived proof-of-lock.
#### **[PBTS-CONSENSUS-TIME-VALID.0]**
If
- there is a valid commit `C` for height `k` and round `r`, and
- `C` contains a `PRECOMMIT` message from at least one correct process
Then, where `p` is one such correct process:
- since `p` is correct, `p` received a valid `POL(v,r)`, and
- `POL(v,r)` contains a `PREVOTE` message from at least one correct process, and
- `POL(v,r)` is derived from a timely `POL(v,r*)` with `r* <= r`, and
- `POL(v,r*)` contains a `PREVOTE` message from at least one correct process, and
- a correct process considered a proposal for `v` `timely` at round `r*`.
### LIVENESS
In terms of liveness, we need to ensure that a proposal broadcast by a correct process
will be considered `timely` by any correct process that is ready to accept that proposal.
So, if:
- the proposer `p` of a round `r` is correct,
- there is no `POL(v',r')` for any value `v'` and any round `r' < r`,
- `p` proposes a valid value `v` and sets `v.time` to the time it reads from its local clock,
Then let `q` be a correct process that receives `p`'s proposal, we have:
- `q` receives `p`'s proposal after its clock reads `v.time - PRECISION`, and
- if `q` is at or joins round `r` while `p`'s proposal is being transmitted,
then `q` receives `p`'s proposal before its clock reads `v.time + MSGDELAY + PRECISION`
> Note that, before `GST`, we cannot ensure that every correct process receives `p`'s proposals, nor that it does it while ready to accept a round `r` proposal.
A correct process `q` as above defined must then consider `p`'s proposal `timely`.
It will then broadcast a `PREVOTE` message for `v` at round `r`,
thus enabling, from the Time-Validity point of view, `v` to be eventually decided.
#### Under-estimated `MSGDELAY`s
The liveness assumptions of PBTS are conditioned by a conservative and clever
choice of the timing parameters, specially of `MSGDELAY`.
In fact, if the transmission delay for a message carrying a proposal is wrongly
estimated, correct processes may never consider a valid proposal as `timely`.
To circumvent this liveness issue, which could result from a misconfiguration,
we assume that the `MSGDELAY` parameter can be increased as rounds do not
succeed on deciding a value, possibly because no proposal is considered
`timely` by enough processes.
The precise behavior for this workaround is under [discussion](https://github.com/tendermint/spec/issues/371).
Back to [main document][main].
[main]: ./README.md
[algorithm]: ./pbts-algorithm_002_draft.md
[sysmodel]: ./pbts-sysmodel_002_draft.md
[sysmodel_v1]: ./v1/pbts-sysmodel_001_draft.md
[arXiv]: https://arxiv.org/pdf/1807.04938.pdf

+ 597
- 0
spec/consensus/proposer-based-timestamp/tla/TendermintPBT_001_draft.tla View File

@ -0,0 +1,597 @@
-------------------- MODULE TendermintPBT_001_draft ---------------------------
(*
A TLA+ specification of a simplified Tendermint consensus, with added clocks
and proposer-based timestamps. This TLA+ specification extends and modifies
the Tendermint TLA+ specification for fork accountability:
https://github.com/tendermint/spec/blob/master/spec/light-client/accountability/TendermintAcc_004_draft.tla
* Version 1. A preliminary specification.
Zarko Milosevic, Igor Konnov, Informal Systems, 2019-2020.
Ilina Stoilkovska, Josef Widder, Informal Systems, 2021.
*)
EXTENDS Integers, FiniteSets
(********************* PROTOCOL PARAMETERS **********************************)
CONSTANTS
Corr, \* the set of correct processes
Faulty, \* the set of Byzantine processes, may be empty
N, \* the total number of processes: correct, defective, and Byzantine
T, \* an upper bound on the number of Byzantine processes
ValidValues, \* the set of valid values, proposed both by correct and faulty
InvalidValues, \* the set of invalid values, never proposed by the correct ones
MaxRound, \* the maximal round number
MaxTimestamp, \* the maximal value of the clock tick
Delay, \* message delay
Precision, \* clock precision: the maximal difference between two local clocks
Accuracy, \* clock accuracy: the maximal difference between a local clock and the real time
Proposer, \* the proposer function from 0..NRounds to 1..N
ClockDrift \* is there clock drift between the local clocks and the global clock
ASSUME(N = Cardinality(Corr \union Faulty))
(*************************** DEFINITIONS ************************************)
AllProcs == Corr \union Faulty \* the set of all processes
Rounds == 0..MaxRound \* the set of potential rounds
Timestamps == 0..MaxTimestamp \* the set of clock ticks
NilRound == -1 \* a special value to denote a nil round, outside of Rounds
NilTimestamp == -1 \* a special value to denote a nil timestamp, outside of Ticks
RoundsOrNil == Rounds \union {NilRound}
Values == ValidValues \union InvalidValues \* the set of all values
NilValue == "None" \* a special value for a nil round, outside of Values
Proposals == Values \X Timestamps
NilProposal == <<NilValue, NilTimestamp>>
ValuesOrNil == Values \union {NilValue}
Decisions == Values \X Timestamps \X Rounds
NilDecision == <<NilValue, NilTimestamp, NilRound>>
\* a value hash is modeled as identity
Id(v) == v
\* The validity predicate
IsValid(v) == v \in ValidValues
\* the two thresholds that are used in the algorithm
THRESHOLD1 == T + 1 \* at least one process is not faulty
THRESHOLD2 == 2 * T + 1 \* a quorum when having N > 3 * T
Min(S) == CHOOSE x \in S : \A y \in S : x <= y
Max(S) == CHOOSE x \in S : \A y \in S : y <= x
(********************* TYPE ANNOTATIONS FOR APALACHE **************************)
\* the operator for type annotations
a <: b == a
\* the type of message records
MT == [type |-> STRING, src |-> STRING, round |-> Int,
proposal |-> <<STRING, Int>>, validRound |-> Int, id |-> <<STRING, Int>>]
RP == <<STRING, MT>>
\* a type annotation for a message
AsMsg(m) == m <: MT
\* a type annotation for a set of messages
SetOfMsgs(S) == S <: {MT}
\* a type annotation for an empty set of messages
EmptyMsgSet == SetOfMsgs({})
SetOfRcvProp(S) == S <: {RP}
EmptyRcvProp == SetOfRcvProp({})
SetOfProc(S) == S <: {STRING}
EmptyProcSet == SetOfProc({})
(********************* PROTOCOL STATE VARIABLES ******************************)
VARIABLES
round, \* a process round number: Corr -> Rounds
localClock, \* a process local clock: Corr -> Ticks
realTime, \* a reference Newtonian real time
step, \* a process step: Corr -> { "PROPOSE", "PREVOTE", "PRECOMMIT", "DECIDED" }
decision, \* process decision: Corr -> ValuesOrNil
lockedValue, \* a locked value: Corr -> ValuesOrNil
lockedRound, \* a locked round: Corr -> RoundsOrNil
validValue, \* a valid value: Corr -> ValuesOrNil
validRound \* a valid round: Corr -> RoundsOrNil
\* book-keeping variables
VARIABLES
msgsPropose, \* PROPOSE messages broadcast in the system, Rounds -> Messages
msgsPrevote, \* PREVOTE messages broadcast in the system, Rounds -> Messages
msgsPrecommit, \* PRECOMMIT messages broadcast in the system, Rounds -> Messages
receivedTimelyProposal, \* used to keep track when a process receives a timely PROPOSAL message, {<<Corr, Messages>>}
inspectedProposal, \* used to keep track when a process tries to receive a message, [Rounds -> <<Corr, Messages>>]
evidence, \* the messages that were used by the correct processes to make transitions
action, \* we use this variable to see which action was taken
beginConsensus, \* the minimum of the local clocks in the initial state, Int
endConsensus, \* the local time when a decision is made, [Corr -> Int]
lastBeginConsensus, \* the maximum of the local clocks in the initial state, Int
proposalTime, \* the real time when a proposer proposes in a round, [Rounds -> Int]
proposalReceivedTime \* the real time when a correct process first receives a proposal message in a round, [Rounds -> Int]
(* to see a type invariant, check TendermintAccInv3 *)
\* a handy definition used in UNCHANGED
vars == <<round, step, decision, lockedValue, lockedRound,
validValue, validRound, evidence, msgsPropose, msgsPrevote, msgsPrecommit,
localClock, realTime, receivedTimelyProposal, inspectedProposal, action,
beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
(********************* PROTOCOL INITIALIZATION ******************************)
FaultyProposals(r) ==
SetOfMsgs([type: {"PROPOSAL"}, src: Faulty,
round: {r}, proposal: Proposals, validRound: RoundsOrNil])
AllFaultyProposals ==
SetOfMsgs([type: {"PROPOSAL"}, src: Faulty,
round: Rounds, proposal: Proposals, validRound: RoundsOrNil])
FaultyPrevotes(r) ==
SetOfMsgs([type: {"PREVOTE"}, src: Faulty, round: {r}, id: Proposals])
AllFaultyPrevotes ==
SetOfMsgs([type: {"PREVOTE"}, src: Faulty, round: Rounds, id: Proposals])
FaultyPrecommits(r) ==
SetOfMsgs([type: {"PRECOMMIT"}, src: Faulty, round: {r}, id: Proposals])
AllFaultyPrecommits ==
SetOfMsgs([type: {"PRECOMMIT"}, src: Faulty, round: Rounds, id: Proposals])
AllProposals ==
SetOfMsgs([type: {"PROPOSAL"}, src: AllProcs,
round: Rounds, proposal: Proposals, validRound: RoundsOrNil])
RoundProposals(r) ==
SetOfMsgs([type: {"PROPOSAL"}, src: AllProcs,
round: {r}, proposal: Proposals, validRound: RoundsOrNil])
BenignRoundsInMessages(msgfun) ==
\* the message function never contains a message for a wrong round
\A r \in Rounds:
\A m \in msgfun[r]:
r = m.round
\* The initial states of the protocol. Some faults can be in the system already.
Init ==
/\ round = [p \in Corr |-> 0]
/\ \/ /\ ~ClockDrift
/\ localClock \in [Corr -> 0..Accuracy]
\/ /\ ClockDrift
/\ localClock = [p \in Corr |-> 0]
/\ realTime = 0
/\ step = [p \in Corr |-> "PROPOSE"]
/\ decision = [p \in Corr |-> NilDecision]
/\ lockedValue = [p \in Corr |-> NilValue]
/\ lockedRound = [p \in Corr |-> NilRound]
/\ validValue = [p \in Corr |-> NilValue]
/\ validRound = [p \in Corr |-> NilRound]
/\ msgsPropose \in [Rounds -> SUBSET AllFaultyProposals]
/\ msgsPrevote \in [Rounds -> SUBSET AllFaultyPrevotes]
/\ msgsPrecommit \in [Rounds -> SUBSET AllFaultyPrecommits]
/\ receivedTimelyProposal = EmptyRcvProp
/\ inspectedProposal = [r \in Rounds |-> EmptyProcSet]
/\ BenignRoundsInMessages(msgsPropose)
/\ BenignRoundsInMessages(msgsPrevote)
/\ BenignRoundsInMessages(msgsPrecommit)
/\ evidence = EmptyMsgSet
/\ action' = "Init"
/\ beginConsensus = Min({localClock[p] : p \in Corr})
/\ endConsensus = [p \in Corr |-> NilTimestamp]
/\ lastBeginConsensus = Max({localClock[p] : p \in Corr})
/\ proposalTime = [r \in Rounds |-> NilTimestamp]
/\ proposalReceivedTime = [r \in Rounds |-> NilTimestamp]
(************************ MESSAGE PASSING ********************************)
BroadcastProposal(pSrc, pRound, pProposal, pValidRound) ==
LET newMsg ==
AsMsg([type |-> "PROPOSAL", src |-> pSrc, round |-> pRound,
proposal |-> pProposal, validRound |-> pValidRound])
IN
msgsPropose' = [msgsPropose EXCEPT ![pRound] = msgsPropose[pRound] \union {newMsg}]
BroadcastPrevote(pSrc, pRound, pId) ==
LET newMsg == AsMsg([type |-> "PREVOTE",
src |-> pSrc, round |-> pRound, id |-> pId])
IN
msgsPrevote' = [msgsPrevote EXCEPT ![pRound] = msgsPrevote[pRound] \union {newMsg}]
BroadcastPrecommit(pSrc, pRound, pId) ==
LET newMsg == AsMsg([type |-> "PRECOMMIT",
src |-> pSrc, round |-> pRound, id |-> pId])
IN
msgsPrecommit' = [msgsPrecommit EXCEPT ![pRound] = msgsPrecommit[pRound] \union {newMsg}]
(***************************** TIME **************************************)
\* [PBTS-CLOCK-PRECISION.0]
SynchronizedLocalClocks ==
\A p \in Corr : \A q \in Corr :
p /= q =>
\/ /\ localClock[p] >= localClock[q]
/\ localClock[p] - localClock[q] < Precision
\/ /\ localClock[p] < localClock[q]
/\ localClock[q] - localClock[p] < Precision
\* [PBTS-PROPOSE.0]
Proposal(v, t) ==
<<v, t>>
\* [PBTS-DECISION-ROUND.0]
Decision(v, t, r) ==
<<v, t, r>>
(**************** MESSAGE PROCESSING TRANSITIONS *************************)
\* lines 12-13
StartRound(p, r) ==
/\ step[p] /= "DECIDED" \* a decided process does not participate in consensus
/\ round' = [round EXCEPT ![p] = r]
/\ step' = [step EXCEPT ![p] = "PROPOSE"]
\* lines 14-19, a proposal may be sent later
InsertProposal(p) ==
LET r == round[p] IN
/\ p = Proposer[r]
/\ step[p] = "PROPOSE"
\* if the proposer is sending a proposal, then there are no other proposals
\* by the correct processes for the same round
/\ \A m \in msgsPropose[r]: m.src /= p
/\ \E v \in ValidValues:
LET proposal == IF validValue[p] /= NilValue
THEN Proposal(validValue[p], localClock[p])
ELSE Proposal(v, localClock[p]) IN
/\ BroadcastProposal(p, round[p], proposal, validRound[p])
/\ proposalTime' = [proposalTime EXCEPT ![r] = realTime]
/\ UNCHANGED <<evidence, round, decision, lockedValue, lockedRound,
validValue, step, validRound, msgsPrevote, msgsPrecommit,
localClock, realTime, receivedTimelyProposal, inspectedProposal,
beginConsensus, endConsensus, lastBeginConsensus, proposalReceivedTime>>
/\ action' = "InsertProposal"
\* a new action used to filter messages that are not on time
\* [PBTS-RECEPTION-STEP.0]
ReceiveProposal(p) ==
\E v \in Values, t \in Timestamps:
/\ LET r == round[p] IN
LET msg ==
AsMsg([type |-> "PROPOSAL", src |-> Proposer[round[p]],
round |-> round[p], proposal |-> Proposal(v, t), validRound |-> NilRound]) IN
/\ msg \in msgsPropose[round[p]]
/\ p \notin inspectedProposal[r]
/\ <<p, msg>> \notin receivedTimelyProposal
/\ inspectedProposal' = [inspectedProposal EXCEPT ![r] = @ \union {p}]
/\ \/ /\ localClock[p] - Precision < t
/\ t < localClock[p] + Precision + Delay
/\ receivedTimelyProposal' = receivedTimelyProposal \union {<<p, msg>>}
/\ \/ /\ proposalReceivedTime[r] = NilTimestamp
/\ proposalReceivedTime' = [proposalReceivedTime EXCEPT ![r] = realTime]
\/ /\ proposalReceivedTime[r] /= NilTimestamp
/\ UNCHANGED proposalReceivedTime
\/ /\ \/ localClock[p] - Precision >= t
\/ t >= localClock[p] + Precision + Delay
/\ UNCHANGED <<receivedTimelyProposal, proposalReceivedTime>>
/\ UNCHANGED <<round, step, decision, lockedValue, lockedRound,
validValue, validRound, evidence, msgsPropose, msgsPrevote, msgsPrecommit,
localClock, realTime, beginConsensus, endConsensus, lastBeginConsensus, proposalTime>>
/\ action' = "ReceiveProposal"
\* lines 22-27
UponProposalInPropose(p) ==
\E v \in Values, t \in Timestamps:
/\ step[p] = "PROPOSE" (* line 22 *)
/\ LET msg ==
AsMsg([type |-> "PROPOSAL", src |-> Proposer[round[p]],
round |-> round[p], proposal |-> Proposal(v, t), validRound |-> NilRound]) IN
/\ <<p, msg>> \in receivedTimelyProposal \* updated line 22
/\ evidence' = {msg} \union evidence
/\ LET mid == (* line 23 *)
IF IsValid(v) /\ (lockedRound[p] = NilRound \/ lockedValue[p] = v)
THEN Id(Proposal(v, t))
ELSE NilProposal
IN
BroadcastPrevote(p, round[p], mid) \* lines 24-26
/\ step' = [step EXCEPT ![p] = "PREVOTE"]
/\ UNCHANGED <<round, decision, lockedValue, lockedRound,
validValue, validRound, msgsPropose, msgsPrecommit,
localClock, realTime, receivedTimelyProposal, inspectedProposal,
beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
/\ action' = "UponProposalInPropose"
\* lines 28-33
\* [PBTS-ALG-OLD-PREVOTE.0]
UponProposalInProposeAndPrevote(p) ==
\E v \in Values, t1 \in Timestamps, t2 \in Timestamps, vr \in Rounds:
/\ step[p] = "PROPOSE" /\ 0 <= vr /\ vr < round[p] \* line 28, the while part
/\ LET msg ==
AsMsg([type |-> "PROPOSAL", src |-> Proposer[round[p]],
round |-> round[p], proposal |-> Proposal(v, t1), validRound |-> vr])
IN
/\ <<p, msg>> \in receivedTimelyProposal \* updated line 28
/\ LET PV == { m \in msgsPrevote[vr]: m.id = Id(Proposal(v, t2)) } IN
/\ Cardinality(PV) >= THRESHOLD2 \* line 28
/\ evidence' = PV \union {msg} \union evidence
/\ LET mid == (* line 29 *)
IF IsValid(v) /\ (lockedRound[p] <= vr \/ lockedValue[p] = v)
THEN Id(Proposal(v, t1))
ELSE NilProposal
IN
BroadcastPrevote(p, round[p], mid) \* lines 24-26
/\ step' = [step EXCEPT ![p] = "PREVOTE"]
/\ UNCHANGED <<round, decision, lockedValue, lockedRound,
validValue, validRound, msgsPropose, msgsPrecommit,
localClock, realTime, receivedTimelyProposal, inspectedProposal,
beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
/\ action' = "UponProposalInProposeAndPrevote"
\* lines 34-35 + lines 61-64 (onTimeoutPrevote)
UponQuorumOfPrevotesAny(p) ==
/\ step[p] = "PREVOTE" \* line 34 and 61
/\ \E MyEvidence \in SUBSET msgsPrevote[round[p]]:
\* find the unique voters in the evidence
LET Voters == { m.src: m \in MyEvidence } IN
\* compare the number of the unique voters against the threshold
/\ Cardinality(Voters) >= THRESHOLD2 \* line 34
/\ evidence' = MyEvidence \union evidence
/\ BroadcastPrecommit(p, round[p], NilProposal)
/\ step' = [step EXCEPT ![p] = "PRECOMMIT"]
/\ UNCHANGED <<round, decision, lockedValue, lockedRound,
validValue, validRound, msgsPropose, msgsPrevote,
localClock, realTime, receivedTimelyProposal, inspectedProposal,
beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
/\ action' = "UponQuorumOfPrevotesAny"
\* lines 36-46
\* [PBTS-ALG-NEW-PREVOTE.0]
UponProposalInPrevoteOrCommitAndPrevote(p) ==
\E v \in ValidValues, t \in Timestamps, vr \in RoundsOrNil:
/\ step[p] \in {"PREVOTE", "PRECOMMIT"} \* line 36
/\ LET msg ==
AsMsg([type |-> "PROPOSAL", src |-> Proposer[round[p]],
round |-> round[p], proposal |-> Proposal(v, t), validRound |-> vr]) IN
/\ <<p, msg>> \in receivedTimelyProposal \* updated line 36
/\ LET PV == { m \in msgsPrevote[round[p]]: m.id = Id(Proposal(v, t)) } IN
/\ Cardinality(PV) >= THRESHOLD2 \* line 36
/\ evidence' = PV \union {msg} \union evidence
/\ IF step[p] = "PREVOTE"
THEN \* lines 38-41:
/\ lockedValue' = [lockedValue EXCEPT ![p] = v]
/\ lockedRound' = [lockedRound EXCEPT ![p] = round[p]]
/\ BroadcastPrecommit(p, round[p], Id(Proposal(v, t)))
/\ step' = [step EXCEPT ![p] = "PRECOMMIT"]
ELSE
UNCHANGED <<lockedValue, lockedRound, msgsPrecommit, step>>
\* lines 42-43
/\ validValue' = [validValue EXCEPT ![p] = v]
/\ validRound' = [validRound EXCEPT ![p] = round[p]]
/\ UNCHANGED <<round, decision, msgsPropose, msgsPrevote,
localClock, realTime, receivedTimelyProposal, inspectedProposal,
beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
/\ action' = "UponProposalInPrevoteOrCommitAndPrevote"
\* lines 47-48 + 65-67 (onTimeoutPrecommit)
UponQuorumOfPrecommitsAny(p) ==
/\ \E MyEvidence \in SUBSET msgsPrecommit[round[p]]:
\* find the unique committers in the evidence
LET Committers == { m.src: m \in MyEvidence } IN
\* compare the number of the unique committers against the threshold
/\ Cardinality(Committers) >= THRESHOLD2 \* line 47
/\ evidence' = MyEvidence \union evidence
/\ round[p] + 1 \in Rounds
/\ StartRound(p, round[p] + 1)
/\ UNCHANGED <<decision, lockedValue, lockedRound, validValue,
validRound, msgsPropose, msgsPrevote, msgsPrecommit,
localClock, realTime, receivedTimelyProposal, inspectedProposal,
beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
/\ action' = "UponQuorumOfPrecommitsAny"
\* lines 49-54
\* [PBTS-ALG-DECIDE.0]
UponProposalInPrecommitNoDecision(p) ==
/\ decision[p] = NilDecision \* line 49
/\ \E v \in ValidValues, t \in Timestamps (* line 50*) , r \in Rounds, vr \in RoundsOrNil:
/\ LET msg == AsMsg([type |-> "PROPOSAL", src |-> Proposer[r],
round |-> r, proposal |-> Proposal(v, t), validRound |-> vr]) IN
/\ msg \in msgsPropose[r] \* line 49
/\ p \in inspectedProposal[r]
/\ LET PV == { m \in msgsPrecommit[r]: m.id = Id(Proposal(v, t)) } IN
/\ Cardinality(PV) >= THRESHOLD2 \* line 49
/\ evidence' = PV \union {msg} \union evidence
/\ decision' = [decision EXCEPT ![p] = Decision(v, t, round[p])] \* update the decision, line 51
\* The original algorithm does not have 'DECIDED', but it increments the height.
\* We introduced 'DECIDED' here to prevent the process from changing its decision.
/\ endConsensus' = [endConsensus EXCEPT ![p] = localClock[p]]
/\ step' = [step EXCEPT ![p] = "DECIDED"]
/\ UNCHANGED <<round, lockedValue, lockedRound, validValue,
validRound, msgsPropose, msgsPrevote, msgsPrecommit,
localClock, realTime, receivedTimelyProposal, inspectedProposal,
beginConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
/\ action' = "UponProposalInPrecommitNoDecision"
\* the actions below are not essential for safety, but added for completeness
\* lines 20-21 + 57-60
OnTimeoutPropose(p) ==
/\ step[p] = "PROPOSE"
/\ p /= Proposer[round[p]]
/\ BroadcastPrevote(p, round[p], NilProposal)
/\ step' = [step EXCEPT ![p] = "PREVOTE"]
/\ UNCHANGED <<round, lockedValue, lockedRound, validValue,
validRound, decision, evidence, msgsPropose, msgsPrecommit,
localClock, realTime, receivedTimelyProposal, inspectedProposal,
beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
/\ action' = "OnTimeoutPropose"
\* lines 44-46
OnQuorumOfNilPrevotes(p) ==
/\ step[p] = "PREVOTE"
/\ LET PV == { m \in msgsPrevote[round[p]]: m.id = Id(NilProposal) } IN
/\ Cardinality(PV) >= THRESHOLD2 \* line 36
/\ evidence' = PV \union evidence
/\ BroadcastPrecommit(p, round[p], Id(NilProposal))
/\ step' = [step EXCEPT ![p] = "PRECOMMIT"]
/\ UNCHANGED <<round, lockedValue, lockedRound, validValue,
validRound, decision, msgsPropose, msgsPrevote,
localClock, realTime, receivedTimelyProposal, inspectedProposal,
beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
/\ action' = "OnQuorumOfNilPrevotes"
\* lines 55-56
OnRoundCatchup(p) ==
\E r \in {rr \in Rounds: rr > round[p]}:
LET RoundMsgs == msgsPropose[r] \union msgsPrevote[r] \union msgsPrecommit[r] IN
\E MyEvidence \in SUBSET RoundMsgs:
LET Faster == { m.src: m \in MyEvidence } IN
/\ Cardinality(Faster) >= THRESHOLD1
/\ evidence' = MyEvidence \union evidence
/\ StartRound(p, r)
/\ UNCHANGED <<decision, lockedValue, lockedRound, validValue,
validRound, msgsPropose, msgsPrevote, msgsPrecommit,
localClock, realTime, receivedTimelyProposal, inspectedProposal,
beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
/\ action' = "OnRoundCatchup"
(********************* PROTOCOL TRANSITIONS ******************************)
\* advance the global clock
AdvanceRealTime ==
/\ realTime < MaxTimestamp
/\ realTime' = realTime + 1
/\ \/ /\ ~ClockDrift
/\ localClock' = [p \in Corr |-> localClock[p] + 1]
\/ /\ ClockDrift
/\ UNCHANGED localClock
/\ UNCHANGED <<round, step, decision, lockedValue, lockedRound,
validValue, validRound, evidence, msgsPropose, msgsPrevote, msgsPrecommit,
localClock, receivedTimelyProposal, inspectedProposal,
beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
/\ action' = "AdvanceRealTime"
\* advance the local clock of node p
AdvanceLocalClock(p) ==
/\ localClock[p] < MaxTimestamp
/\ localClock' = [localClock EXCEPT ![p] = @ + 1]
/\ UNCHANGED <<round, step, decision, lockedValue, lockedRound,
validValue, validRound, evidence, msgsPropose, msgsPrevote, msgsPrecommit,
realTime, receivedTimelyProposal, inspectedProposal,
beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
/\ action' = "AdvanceLocalClock"
\* process timely messages
MessageProcessing(p) ==
\* start round
\/ InsertProposal(p)
\* reception step
\/ ReceiveProposal(p)
\* processing step
\/ UponProposalInPropose(p)
\/ UponProposalInProposeAndPrevote(p)
\/ UponQuorumOfPrevotesAny(p)
\/ UponProposalInPrevoteOrCommitAndPrevote(p)
\/ UponQuorumOfPrecommitsAny(p)
\/ UponProposalInPrecommitNoDecision(p)
\* the actions below are not essential for safety, but added for completeness
\/ OnTimeoutPropose(p)
\/ OnQuorumOfNilPrevotes(p)
\/ OnRoundCatchup(p)
(*
* A system transition. In this specificatiom, the system may eventually deadlock,
* e.g., when all processes decide. This is expected behavior, as we focus on safety.
*)
Next ==
\/ AdvanceRealTime
\/ /\ ClockDrift
/\ \E p \in Corr: AdvanceLocalClock(p)
\/ /\ SynchronizedLocalClocks
/\ \E p \in Corr: MessageProcessing(p)
-----------------------------------------------------------------------------
(*************************** INVARIANTS *************************************)
\* [PBTS-INV-AGREEMENT.0]
AgreementOnValue ==
\A p, q \in Corr:
/\ decision[p] /= NilDecision
/\ decision[q] /= NilDecision
=> \E v \in ValidValues, t1 \in Timestamps, t2 \in Timestamps, r1 \in Rounds, r2 \in Rounds :
/\ decision[p] = Decision(v, t1, r1)
/\ decision[q] = Decision(v, t2, r2)
\* [PBTS-INV-TIME-AGR.0]
AgreementOnTime ==
\A p, q \in Corr:
\A v1 \in ValidValues, v2 \in ValidValues, t1 \in Timestamps, t2 \in Timestamps, r \in Rounds :
/\ decision[p] = Decision(v1, t1, r)
/\ decision[q] = Decision(v2, t2, r)
=> t1 = t2
\* [PBTS-CONSENSUS-TIME-VALID.0]
ConsensusTimeValid ==
\A p \in Corr, t \in Timestamps :
\* if a process decides on v and t
(\E v \in ValidValues, r \in Rounds : decision[p] = Decision(v, t, r))
\* then
=> /\ beginConsensus - Precision <= t
/\ t < endConsensus[p] + Precision + Delay
\* [PBTS-CONSENSUS-SAFE-VALID-CORR-PROP.0]
ConsensusSafeValidCorrProp ==
\A v \in ValidValues, t \in Timestamps :
\* if the proposer in the first round is correct
(/\ Proposer[0] \in Corr
\* and there exists a process that decided on v, t
/\ \E p \in Corr, r \in Rounds : decision[p] = Decision(v, t, r))
\* then t is between the minimal and maximal initial local time
=> /\ beginConsensus <= t
/\ t <= lastBeginConsensus
\* [PBTS-CONSENSUS-REALTIME-VALID-CORR.0]
ConsensusRealTimeValidCorr ==
\A t \in Timestamps, r \in Rounds :
(/\ \E p \in Corr, v \in ValidValues : decision[p] = Decision(v, t, r)
/\ proposalTime[r] /= NilTimestamp)
=> /\ proposalTime[r] - Accuracy < t
/\ t < proposalTime[r] + Accuracy
\* [PBTS-CONSENSUS-REALTIME-VALID.0]
ConsensusRealTimeValid ==
\A t \in Timestamps, r \in Rounds :
(\E p \in Corr, v \in ValidValues : decision[p] = Decision(v, t, r))
=> /\ proposalReceivedTime[r] - Accuracy - Precision < t
/\ t < proposalReceivedTime[r] + Accuracy + Precision + Delay
\* [PBTS-MSG-FAIR.0]
BoundedDelay ==
\A r \in Rounds :
(/\ proposalTime[r] /= NilTimestamp
/\ proposalTime[r] + Delay < realTime)
=> inspectedProposal[r] = Corr
\* [PBTS-CONSENSUS-TIME-LIVE.0]
ConsensusTimeLive ==
\A r \in Rounds, p \in Corr :
(/\ proposalTime[r] /= NilTimestamp
/\ proposalTime[r] + Delay < realTime
/\ Proposer[r] \in Corr
/\ round[p] <= r)
=> \E msg \in RoundProposals(r) : <<p, msg>> \in receivedTimelyProposal
\* a conjunction of all invariants
Inv ==
/\ AgreementOnValue
/\ AgreementOnTime
/\ ConsensusTimeValid
/\ ConsensusSafeValidCorrProp
/\ ConsensusRealTimeValid
/\ ConsensusRealTimeValidCorr
/\ BoundedDelay
Liveness ==
ConsensusTimeLive
=============================================================================

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

Loading…
Cancel
Save