@ -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 |
@ -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' |
@ -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 |
@ -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 }} |
@ -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 |
@ -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 |
@ -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 |
@ -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> |
@ -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) |
@ -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) |
@ -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) |
@ -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 . |
@ -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 | |
@ -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) |
@ -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 | |
@ -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) |
@ -1,3 +0,0 @@ | |||
# Consensus | |||
See the [consensus spec](https://github.com/tendermint/spec/tree/master/spec/consensus). |
@ -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/ |
@ -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`. |
@ -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); | |||
} |
@ -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; | |||
} | |||
} |
@ -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,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; | |||
} | |||
} |
@ -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]; | |||
} |
@ -0,0 +1,7 @@ | |||
syntax = "proto3"; | |||
package tendermint.libs.bits; | |||
message BitArray { | |||
int64 bits = 1; | |||
repeated uint64 elems = 2; | |||
} |
@ -0,0 +1,12 @@ | |||
syntax = "proto3"; | |||
package tendermint.mempool; | |||
message Txs { | |||
repeated bytes txs = 1; | |||
} | |||
message Message { | |||
oneof sum { | |||
Txs txs = 1; | |||
} | |||
} |
@ -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; | |||
} |
@ -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; | |||
} | |||
} |
@ -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; | |||
} |
@ -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]; | |||
} |
@ -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,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]; | |||
} |
@ -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; | |||
} |
@ -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; | |||
} |
@ -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; | |||
} |
@ -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; | |||
} |
@ -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. |
@ -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 |
@ -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$ |
@ -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). |
@ -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)$] |
@ -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`. |
@ -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. |
@ -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. |
@ -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. |
@ -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. |
@ -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. |
@ -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 |
@ -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. |
@ -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. |
@ -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). |
@ -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. |
@ -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` |
@ -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}} |
@ -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. |
@ -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. | |||
@ -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. |
@ -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}{} |
@ -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}. | |||
@ -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 | |||
% --------------------------------------------------------------- |
@ -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} |
@ -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} | |||
@ -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} | |||
} |
@ -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 | |||
} |
@ -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. |
@ -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) | |||
} | |||
``` |
@ -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. |
@ -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). |
@ -0,0 +1,3 @@ | |||
# Fork accountability | |||
Deprecated, please see [light-client/accountability](../../light-client/accountability/README.md). |
@ -0,0 +1,3 @@ | |||
# Detection | |||
Deprecated, please see [light-client/detection](../../light-client/detection/README.md). |
@ -0,0 +1,3 @@ | |||
# Core Verification | |||
Deprecated, please see [light-client/accountability](../../light-client/verification/README.md). |
@ -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 |
@ -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 |
@ -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 |
@ -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 | |||
============================================================================= |