@ -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,17 @@ | |||
name: Check Markdown links | |||
on: | |||
push: | |||
branches: | |||
- master | |||
pull_request: | |||
branches: [master] | |||
jobs: | |||
markdown-link-check: | |||
runs-on: ubuntu-latest | |||
steps: | |||
- uses: actions/checkout@master | |||
- uses: gaurav-nelson/github-action-markdown-link-check@1.0.13 | |||
with: | |||
check-modified-files-only: 'yes' |
@ -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.13.0 | |||
with: | |||
registry: ${{ env.REGISTRY }} | |||
username: ${{ github.actor }} | |||
password: ${{ secrets.GITHUB_TOKEN }} | |||
- name: Build and publish image | |||
uses: docker/build-push-action@v2.9.0 | |||
with: | |||
context: ./proto | |||
file: ./proto/Dockerfile | |||
push: ${{ github.event_name != 'pull_request' }} | |||
tags: ${{ steps.prep.outputs.tags }} |
@ -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,209 @@ | |||
// Code generated by mockery. DO NOT EDIT. | |||
package mocks | |||
import ( | |||
mock "github.com/stretchr/testify/mock" | |||
types "github.com/tendermint/tendermint/abci/types" | |||
) | |||
// Application is an autogenerated mock type for the Application type | |||
type Application struct { | |||
mock.Mock | |||
} | |||
// ApplySnapshotChunk provides a mock function with given fields: _a0 | |||
func (_m *Application) ApplySnapshotChunk(_a0 types.RequestApplySnapshotChunk) types.ResponseApplySnapshotChunk { | |||
ret := _m.Called(_a0) | |||
var r0 types.ResponseApplySnapshotChunk | |||
if rf, ok := ret.Get(0).(func(types.RequestApplySnapshotChunk) types.ResponseApplySnapshotChunk); ok { | |||
r0 = rf(_a0) | |||
} else { | |||
r0 = ret.Get(0).(types.ResponseApplySnapshotChunk) | |||
} | |||
return r0 | |||
} | |||
// CheckTx provides a mock function with given fields: _a0 | |||
func (_m *Application) CheckTx(_a0 types.RequestCheckTx) types.ResponseCheckTx { | |||
ret := _m.Called(_a0) | |||
var r0 types.ResponseCheckTx | |||
if rf, ok := ret.Get(0).(func(types.RequestCheckTx) types.ResponseCheckTx); ok { | |||
r0 = rf(_a0) | |||
} else { | |||
r0 = ret.Get(0).(types.ResponseCheckTx) | |||
} | |||
return r0 | |||
} | |||
// Commit provides a mock function with given fields: | |||
func (_m *Application) Commit() types.ResponseCommit { | |||
ret := _m.Called() | |||
var r0 types.ResponseCommit | |||
if rf, ok := ret.Get(0).(func() types.ResponseCommit); ok { | |||
r0 = rf() | |||
} else { | |||
r0 = ret.Get(0).(types.ResponseCommit) | |||
} | |||
return r0 | |||
} | |||
// ExtendVote provides a mock function with given fields: _a0 | |||
func (_m *Application) ExtendVote(_a0 types.RequestExtendVote) types.ResponseExtendVote { | |||
ret := _m.Called(_a0) | |||
var r0 types.ResponseExtendVote | |||
if rf, ok := ret.Get(0).(func(types.RequestExtendVote) types.ResponseExtendVote); ok { | |||
r0 = rf(_a0) | |||
} else { | |||
r0 = ret.Get(0).(types.ResponseExtendVote) | |||
} | |||
return r0 | |||
} | |||
// FinalizeBlock provides a mock function with given fields: _a0 | |||
func (_m *Application) FinalizeBlock(_a0 types.RequestFinalizeBlock) types.ResponseFinalizeBlock { | |||
ret := _m.Called(_a0) | |||
var r0 types.ResponseFinalizeBlock | |||
if rf, ok := ret.Get(0).(func(types.RequestFinalizeBlock) types.ResponseFinalizeBlock); ok { | |||
r0 = rf(_a0) | |||
} else { | |||
r0 = ret.Get(0).(types.ResponseFinalizeBlock) | |||
} | |||
return r0 | |||
} | |||
// Info provides a mock function with given fields: _a0 | |||
func (_m *Application) Info(_a0 types.RequestInfo) types.ResponseInfo { | |||
ret := _m.Called(_a0) | |||
var r0 types.ResponseInfo | |||
if rf, ok := ret.Get(0).(func(types.RequestInfo) types.ResponseInfo); ok { | |||
r0 = rf(_a0) | |||
} else { | |||
r0 = ret.Get(0).(types.ResponseInfo) | |||
} | |||
return r0 | |||
} | |||
// InitChain provides a mock function with given fields: _a0 | |||
func (_m *Application) InitChain(_a0 types.RequestInitChain) types.ResponseInitChain { | |||
ret := _m.Called(_a0) | |||
var r0 types.ResponseInitChain | |||
if rf, ok := ret.Get(0).(func(types.RequestInitChain) types.ResponseInitChain); ok { | |||
r0 = rf(_a0) | |||
} else { | |||
r0 = ret.Get(0).(types.ResponseInitChain) | |||
} | |||
return r0 | |||
} | |||
// ListSnapshots provides a mock function with given fields: _a0 | |||
func (_m *Application) ListSnapshots(_a0 types.RequestListSnapshots) types.ResponseListSnapshots { | |||
ret := _m.Called(_a0) | |||
var r0 types.ResponseListSnapshots | |||
if rf, ok := ret.Get(0).(func(types.RequestListSnapshots) types.ResponseListSnapshots); ok { | |||
r0 = rf(_a0) | |||
} else { | |||
r0 = ret.Get(0).(types.ResponseListSnapshots) | |||
} | |||
return r0 | |||
} | |||
// LoadSnapshotChunk provides a mock function with given fields: _a0 | |||
func (_m *Application) LoadSnapshotChunk(_a0 types.RequestLoadSnapshotChunk) types.ResponseLoadSnapshotChunk { | |||
ret := _m.Called(_a0) | |||
var r0 types.ResponseLoadSnapshotChunk | |||
if rf, ok := ret.Get(0).(func(types.RequestLoadSnapshotChunk) types.ResponseLoadSnapshotChunk); ok { | |||
r0 = rf(_a0) | |||
} else { | |||
r0 = ret.Get(0).(types.ResponseLoadSnapshotChunk) | |||
} | |||
return r0 | |||
} | |||
// OfferSnapshot provides a mock function with given fields: _a0 | |||
func (_m *Application) OfferSnapshot(_a0 types.RequestOfferSnapshot) types.ResponseOfferSnapshot { | |||
ret := _m.Called(_a0) | |||
var r0 types.ResponseOfferSnapshot | |||
if rf, ok := ret.Get(0).(func(types.RequestOfferSnapshot) types.ResponseOfferSnapshot); ok { | |||
r0 = rf(_a0) | |||
} else { | |||
r0 = ret.Get(0).(types.ResponseOfferSnapshot) | |||
} | |||
return r0 | |||
} | |||
// PrepareProposal provides a mock function with given fields: _a0 | |||
func (_m *Application) PrepareProposal(_a0 types.RequestPrepareProposal) types.ResponsePrepareProposal { | |||
ret := _m.Called(_a0) | |||
var r0 types.ResponsePrepareProposal | |||
if rf, ok := ret.Get(0).(func(types.RequestPrepareProposal) types.ResponsePrepareProposal); ok { | |||
r0 = rf(_a0) | |||
} else { | |||
r0 = ret.Get(0).(types.ResponsePrepareProposal) | |||
} | |||
return r0 | |||
} | |||
// ProcessProposal provides a mock function with given fields: _a0 | |||
func (_m *Application) ProcessProposal(_a0 types.RequestProcessProposal) types.ResponseProcessProposal { | |||
ret := _m.Called(_a0) | |||
var r0 types.ResponseProcessProposal | |||
if rf, ok := ret.Get(0).(func(types.RequestProcessProposal) types.ResponseProcessProposal); ok { | |||
r0 = rf(_a0) | |||
} else { | |||
r0 = ret.Get(0).(types.ResponseProcessProposal) | |||
} | |||
return r0 | |||
} | |||
// Query provides a mock function with given fields: _a0 | |||
func (_m *Application) Query(_a0 types.RequestQuery) types.ResponseQuery { | |||
ret := _m.Called(_a0) | |||
var r0 types.ResponseQuery | |||
if rf, ok := ret.Get(0).(func(types.RequestQuery) types.ResponseQuery); ok { | |||
r0 = rf(_a0) | |||
} else { | |||
r0 = ret.Get(0).(types.ResponseQuery) | |||
} | |||
return r0 | |||
} | |||
// VerifyVoteExtension provides a mock function with given fields: _a0 | |||
func (_m *Application) VerifyVoteExtension(_a0 types.RequestVerifyVoteExtension) types.ResponseVerifyVoteExtension { | |||
ret := _m.Called(_a0) | |||
var r0 types.ResponseVerifyVoteExtension | |||
if rf, ok := ret.Get(0).(func(types.RequestVerifyVoteExtension) types.ResponseVerifyVoteExtension); ok { | |||
r0 = rf(_a0) | |||
} else { | |||
r0 = ret.Get(0).(types.ResponseVerifyVoteExtension) | |||
} | |||
return r0 | |||
} |
@ -0,0 +1,189 @@ | |||
package mocks | |||
import ( | |||
types "github.com/tendermint/tendermint/abci/types" | |||
) | |||
// BaseMock provides a wrapper around the generated Application mock and a BaseApplication. | |||
// BaseMock first tries to use the mock's implementation of the method. | |||
// If no functionality was provided for the mock by the user, BaseMock dispatches | |||
// to the BaseApplication and uses its functionality. | |||
// BaseMock allows users to provide mocked functionality for only the methods that matter | |||
// for their test while avoiding a panic if the code calls Application methods that are | |||
// not relevant to the test. | |||
type BaseMock struct { | |||
base *types.BaseApplication | |||
*Application | |||
} | |||
func NewBaseMock() BaseMock { | |||
return BaseMock{ | |||
base: types.NewBaseApplication(), | |||
Application: new(Application), | |||
} | |||
} | |||
// Info/Query Connection | |||
// Return application info | |||
func (m BaseMock) Info(input types.RequestInfo) types.ResponseInfo { | |||
var ret types.ResponseInfo | |||
defer func() { | |||
if r := recover(); r != nil { | |||
ret = m.base.Info(input) | |||
} | |||
}() | |||
ret = m.Application.Info(input) | |||
return ret | |||
} | |||
func (m BaseMock) Query(input types.RequestQuery) types.ResponseQuery { | |||
var ret types.ResponseQuery | |||
defer func() { | |||
if r := recover(); r != nil { | |||
ret = m.base.Query(input) | |||
} | |||
}() | |||
ret = m.Application.Query(input) | |||
return ret | |||
} | |||
// Mempool Connection | |||
// Validate a tx for the mempool | |||
func (m BaseMock) CheckTx(input types.RequestCheckTx) types.ResponseCheckTx { | |||
var ret types.ResponseCheckTx | |||
defer func() { | |||
if r := recover(); r != nil { | |||
ret = m.base.CheckTx(input) | |||
} | |||
}() | |||
ret = m.Application.CheckTx(input) | |||
return ret | |||
} | |||
// Consensus Connection | |||
// Initialize blockchain w validators/other info from TendermintCore | |||
func (m BaseMock) InitChain(input types.RequestInitChain) types.ResponseInitChain { | |||
var ret types.ResponseInitChain | |||
defer func() { | |||
if r := recover(); r != nil { | |||
ret = m.base.InitChain(input) | |||
} | |||
}() | |||
ret = m.Application.InitChain(input) | |||
return ret | |||
} | |||
func (m BaseMock) PrepareProposal(input types.RequestPrepareProposal) types.ResponsePrepareProposal { | |||
var ret types.ResponsePrepareProposal | |||
defer func() { | |||
if r := recover(); r != nil { | |||
ret = m.base.PrepareProposal(input) | |||
} | |||
}() | |||
ret = m.Application.PrepareProposal(input) | |||
return ret | |||
} | |||
func (m BaseMock) ProcessProposal(input types.RequestProcessProposal) types.ResponseProcessProposal { | |||
var ret types.ResponseProcessProposal | |||
defer func() { | |||
if r := recover(); r != nil { | |||
ret = m.base.ProcessProposal(input) | |||
} | |||
}() | |||
ret = m.Application.ProcessProposal(input) | |||
return ret | |||
} | |||
// Commit the state and return the application Merkle root hash | |||
func (m BaseMock) Commit() types.ResponseCommit { | |||
var ret types.ResponseCommit | |||
defer func() { | |||
if r := recover(); r != nil { | |||
ret = m.base.Commit() | |||
} | |||
}() | |||
ret = m.Application.Commit() | |||
return ret | |||
} | |||
// Create application specific vote extension | |||
func (m BaseMock) ExtendVote(input types.RequestExtendVote) types.ResponseExtendVote { | |||
var ret types.ResponseExtendVote | |||
defer func() { | |||
if r := recover(); r != nil { | |||
ret = m.base.ExtendVote(input) | |||
} | |||
}() | |||
ret = m.Application.ExtendVote(input) | |||
return ret | |||
} | |||
// Verify application's vote extension data | |||
func (m BaseMock) VerifyVoteExtension(input types.RequestVerifyVoteExtension) types.ResponseVerifyVoteExtension { | |||
var ret types.ResponseVerifyVoteExtension | |||
defer func() { | |||
if r := recover(); r != nil { | |||
ret = m.base.VerifyVoteExtension(input) | |||
} | |||
}() | |||
ret = m.Application.VerifyVoteExtension(input) | |||
return ret | |||
} | |||
// State Sync Connection | |||
// List available snapshots | |||
func (m BaseMock) ListSnapshots(input types.RequestListSnapshots) types.ResponseListSnapshots { | |||
var ret types.ResponseListSnapshots | |||
defer func() { | |||
if r := recover(); r != nil { | |||
ret = m.base.ListSnapshots(input) | |||
} | |||
}() | |||
ret = m.Application.ListSnapshots(input) | |||
return ret | |||
} | |||
func (m BaseMock) OfferSnapshot(input types.RequestOfferSnapshot) types.ResponseOfferSnapshot { | |||
var ret types.ResponseOfferSnapshot | |||
defer func() { | |||
if r := recover(); r != nil { | |||
ret = m.base.OfferSnapshot(input) | |||
} | |||
}() | |||
ret = m.Application.OfferSnapshot(input) | |||
return ret | |||
} | |||
func (m BaseMock) LoadSnapshotChunk(input types.RequestLoadSnapshotChunk) types.ResponseLoadSnapshotChunk { | |||
var ret types.ResponseLoadSnapshotChunk | |||
defer func() { | |||
if r := recover(); r != nil { | |||
ret = m.base.LoadSnapshotChunk(input) | |||
} | |||
}() | |||
ret = m.Application.LoadSnapshotChunk(input) | |||
return ret | |||
} | |||
func (m BaseMock) ApplySnapshotChunk(input types.RequestApplySnapshotChunk) types.ResponseApplySnapshotChunk { | |||
var ret types.ResponseApplySnapshotChunk | |||
defer func() { | |||
if r := recover(); r != nil { | |||
ret = m.base.ApplySnapshotChunk(input) | |||
} | |||
}() | |||
ret = m.Application.ApplySnapshotChunk(input) | |||
return ret | |||
} | |||
func (m BaseMock) FinalizeBlock(input types.RequestFinalizeBlock) types.ResponseFinalizeBlock { | |||
var ret types.ResponseFinalizeBlock | |||
defer func() { | |||
if r := recover(); r != nil { | |||
ret = m.base.FinalizeBlock(input) | |||
} | |||
}() | |||
ret = m.Application.FinalizeBlock(input) | |||
return ret | |||
} |
@ -0,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 @@ | |||
/master/ /v0.35/ |
@ -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/tendermint/blob/master/docs/architecture/adr-077-block-retention.md). | |||
The RFC allowed applications to set a block retention height; an upper bound on | |||
what blocks would be pruned. However nodes who state sync past this upper bound | |||
(which is necessary as snapshots must be saved within the trusting period for | |||
the assisting light client to verify) have no means of backfilling the blocks | |||
to meet the retention limit. This could be a problem as nodes who state sync and | |||
then eventually switch to consensus (or fast sync) may not have the block and | |||
validator history to verify evidence causing them to panic if they see 2/3 | |||
commit on what the node believes to be an invalid block. | |||
Thus, this RFC sets out to instil a minimum block history invariant amongst | |||
honest nodes. | |||
## Proposal | |||
A backfill mechanism can simply be defined as an algorithm for fetching, | |||
verifying and storing, headers and validator sets of a height prior to the | |||
current base of the node's blockchain. In matching the terminology used for | |||
other data retrieving protocols (i.e. fast sync and state sync), we | |||
call this method **ReverseSync**. | |||
We will define the mechanism in four sections: | |||
- Usage | |||
- Design | |||
- Verification | |||
- Termination | |||
### Usage | |||
For now, we focus purely on the case of a state syncing node, whom after | |||
syncing to a height will need to verify historical data in order to be capable | |||
of processing new blocks. We can denote the earliest height that the node will | |||
need to verify and store in order to be able to verify any evidence that might | |||
arise as the `max_historical_height`/`time`. Both height and time are necessary | |||
as this maps to the BFT time used for evidence expiration. After acquiring | |||
`State`, we calculate these parameters as: | |||
```go | |||
max_historical_height = max(state.InitialHeight, state.LastBlockHeight - state.ConsensusParams.EvidenceAgeHeight) | |||
max_historical_time = max(GenesisTime, state.LastBlockTime.Sub(state.ConsensusParams.EvidenceAgeTime)) | |||
``` | |||
Before starting either fast sync or consensus, we then run the following | |||
synchronous process: | |||
```go | |||
func ReverseSync(max_historical_height int64, max_historical_time time.Time) error | |||
``` | |||
Where we fetch and verify blocks until a block `A` where | |||
`A.Height <= max_historical_height` and `A.Time <= max_historical_time`. | |||
Upon successfully reverse syncing, a node can now safely continue. As this | |||
feature is only used as part of state sync, one can think of this as merely an | |||
extension to it. | |||
In the future we may want to extend this functionality to allow nodes to fetch | |||
historical blocks for reasons of accountability or data accessibility. | |||
### Design | |||
This section will provide a high level overview of some of the more important | |||
characteristics of the design, saving the more tedious details as an ADR. | |||
#### P2P | |||
Implementation of this RFC will require the addition of a new channel and two | |||
new messages. | |||
```proto | |||
message LightBlockRequest { | |||
uint64 height = 1; | |||
} | |||
``` | |||
```proto | |||
message LightBlockResponse { | |||
Header header = 1; | |||
Commit commit = 2; | |||
ValidatorSet validator_set = 3; | |||
} | |||
``` | |||
The P2P path may also enable P2P networked light clients and a state sync that | |||
also doesn't need to rely on RPC. | |||
### Verification | |||
ReverseSync is used to fetch the following data structures: | |||
- `Header` | |||
- `Commit` | |||
- `ValidatorSet` | |||
Nodes will also need to be able to verify these. This can be achieved by first | |||
retrieving the header at the base height from the block store. From this trusted | |||
header, the node hashes each of the three data structures and checks that they are correct. | |||
1. The trusted header's last block ID matches the hash of the new header | |||
```go | |||
header[height].LastBlockID == hash(header[height-1]) | |||
``` | |||
2. The trusted header's last commit hash matches the hash of the new commit | |||
```go | |||
header[height].LastCommitHash == hash(commit[height-1]) | |||
``` | |||
3. Given that the node now trusts the new header, check that the header's validator set | |||
hash matches the hash of the validator set | |||
```go | |||
header[height-1].ValidatorsHash == hash(validatorSet[height-1]) | |||
``` | |||
### Termination | |||
ReverseSync draws a lot of parallels with fast sync. An important consideration | |||
for fast sync that also extends to ReverseSync is termination. ReverseSync will | |||
finish it's task when one of the following conditions have been met: | |||
1. It reaches a block `A` where `A.Height <= max_historical_height` and | |||
`A.Time <= max_historical_time`. | |||
2. None of it's peers reports to have the block at the height below the | |||
processes current block. | |||
3. A global timeout. | |||
This implies that we can't guarantee adequate history and thus the term | |||
"invariant" can't be used in the strictest sense. In the case that the first | |||
condition isn't met, the node will log an error and optimistically attempt | |||
to continue with either fast sync or consensus. | |||
## Alternative Solutions | |||
The need for a minimum block history invariant stems purely from the need to | |||
validate evidence (although there may be some application relevant needs as | |||
well). Because of this, an alternative, could be to simply trust whatever the | |||
2/3+ majority has agreed upon and in the case where a node is at the head of the | |||
blockchain, you simply abstain from voting. | |||
As it stands, if 2/3+ vote on evidence you can't verify, in the same manner if | |||
2/3+ vote on a header that a node sees as invalid (perhaps due to a different | |||
app hash), the node will halt. | |||
Another alternative is the method with which the relevant data is retrieved. | |||
Instead of introducing new messages to the P2P layer, RPC could have been used | |||
instead. | |||
The aforementioned data is already available via the following RPC endpoints: | |||
`/commit` for `Header`'s' and `/validators` for `ValidatorSet`'s'. It was | |||
decided predominantly due to the instability of the current RPC infrastructure | |||
that P2P be used instead. | |||
## Status | |||
Proposed | |||
## Consequences | |||
### Positive | |||
- Ensures a minimum block history invariant for honest nodes. This will allow | |||
nodes to verify evidence. | |||
### Negative | |||
- Statesync will be slower as more processing is required. | |||
### Neutral | |||
- By having validator sets served through p2p, this would make it easier to | |||
extend p2p support to light clients and state sync. | |||
- In the future, it may also be possible to extend this feature to allow for | |||
nodes to freely fetch and verify prior blocks | |||
## References | |||
- [RFC-001: Block retention](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-077-block-retention.md) | |||
- [Original issue](https://github.com/tendermint/tendermint/issues/4629) |
@ -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,352 @@ | |||
# RFC 012: Event Indexing Revisited | |||
## Changelog | |||
- 11-Feb-2022: Add terminological notes. | |||
- 10-Feb-2022: Updated from review feedback. | |||
- 07-Feb-2022: Initial draft (@creachadair) | |||
## Abstract | |||
A Tendermint node allows ABCI events associated with block and transaction | |||
processing to be "indexed" into persistent storage. The original Tendermint | |||
implementation provided a fixed, built-in [proprietary indexer][kv-index] for | |||
such events. | |||
In response to user requests to customize indexing, [ADR 065][adr065] | |||
introduced an "event sink" interface that allows developers (at least in | |||
theory) to plug in alternative index storage. | |||
Although ADR-065 was a good first step toward customization, its implementation | |||
model does not satisfy all the user requirements. Moreover, this approach | |||
leaves some existing technical issues with indexing unsolved. | |||
This RFC documents these concerns, and discusses some potential approaches to | |||
solving them. This RFC does _not_ propose a specific technical decision. It is | |||
meant to unify and focus some of the disparate discussions of the topic. | |||
## Background | |||
We begin with some important terminological context. The term "event" in | |||
Tendermint can be confusing, as the same word is used for multiple related but | |||
distinct concepts: | |||
1. **ABCI Events** refer to the key-value metadata attached to blocks and | |||
transactions by the application. These values are represented by the ABCI | |||
`Event` protobuf message type. | |||
2. **Consensus Events** refer to the data published by the Tendermint node to | |||
its pubsub bus in response to various consensus state transitions and other | |||
important activities, such as round updates, votes, transaction delivery, | |||
and block completion. | |||
This confusion is compounded because some "consensus event" values also have | |||
"ABCI event" metadata attached to them. Notably, block and transaction items | |||
typically have ABCI metadata assigned by the application. | |||
Indexers and RPC clients subscribed to the pubsub bus receive **consensus | |||
events**, but they identify which ones to care about using query expressions | |||
that match against the **ABCI events** associated with them. | |||
In the discussion that follows, we will use the term **event item** to refer to | |||
a datum published to or received from the pubsub bus, and **ABCI event** or | |||
**event metadata** to refer to the key/value annotations. | |||
**Indexing** in this context means recording the association between certain | |||
ABCI metadata and the blocks or transactions they're attached to. The ABCI | |||
metadata typically carry application-specific details like sender and recipient | |||
addresses, catgory tags, and so forth, that are not part of consensus but are | |||
used by UI tools to find and display transactions of interest. | |||
The consensus node records the blocks and transactions as part of its block | |||
store, but does not persist the application metadata. Metadata persistence is | |||
the task of the indexer, which can be (optionally) enabled by the node | |||
operator. | |||
### History | |||
The [original indexer][kv-index] built in to Tendermint stored index data in an | |||
embedded [`tm-db` database][tmdb] with a proprietary key layout. | |||
In [ADR 065][adr065], we noted that this implementation has both performance | |||
and scaling problems under load. Moreover, the only practical way to query the | |||
index data is via the [query filter language][query] used for event | |||
subscription. [Issue #1161][i1161] appears to be a motivational context for that ADR. | |||
To mitigate both of these concerns, we introduced the [`EventSink`][esink] | |||
interface, combining the original transaction and block indexer interfaces | |||
along with some service plumbing. Using this interface, a developer can plug | |||
in an indexer that uses a more efficient storage engine, and provides a more | |||
expressive query language. As a proof-of-concept, we built a [PostgreSQL event | |||
sink][psql] that exports data to a [PostgreSQL database][postgres]. | |||
Although this approach addressed some of the immediate concerns, there are | |||
several issues for custom indexing that have not been fully addressed. Here we | |||
will discuss them in more detail. | |||
For further context, including links to user reports and related work, see also | |||
the [Pluggable custom event indexing tracking issue][i7135] issue. | |||
### Issue 1: Tight Coupling | |||
The `EventSink` interface supports multiple implementations, but plugging in | |||
implementations still requires tight integration with the node. In particular: | |||
- Any custom indexer must either be written in Go and compiled in to the | |||
Tendermint binary, or the developer must write a Go shim to communicate with | |||
the implementation and build that into the Tendermint binary. | |||
- This means to support a custom indexer, it either has to be integrated into | |||
the Tendermint core repository, or every installation that uses that indexer | |||
must fetch or build a patched version of Tendermint. | |||
The problem with integrating indexers into Tendermint Core is that every user | |||
of Tendermint Core takes a dependency on all supported indexers, including | |||
those they never use. Even if the unused code is disabled with build tags, | |||
users have to remember to do this or potentially be exposed to security issues | |||
that may arise in any of the custom indexers. This is a risk for Tendermint, | |||
which is a trust-critical component of all applications built on it. | |||
The problem with _not_ integrating indexers into Tendermint Core is that any | |||
developer who wants to use a particular indexer must now fetch or build a | |||
patched version of the core code that includes the custom indexer. Besides | |||
being inconvenient, this makes it harder for users to upgrade their node, since | |||
they need to either re-apply their patches directly or wait for an intermediary | |||
to do it for them. | |||
Even for developers who have written their applications in Go and link with the | |||
consensus node directly (e.g., using the [Cosmos SDK][sdk]), these issues add a | |||
potentially significant complication to the build process. | |||
### Issue 2: Legacy Compatibility | |||
The `EventSink` interface retains several limitations of the original | |||
proprietary indexer. These include: | |||
- The indexer has no control over which event items are reported. Only the | |||
exact block and transaction events that were reported to the original indexer | |||
are reported to a custom indexer. | |||
- The interface requires the implementation to define methods for the legacy | |||
search and query API. This requirement comes from the integation with the | |||
[event subscription RPC API][event-rpc], but actually supporting these | |||
methods is not trivial. | |||
At present, only the original KV indexer implements the query methods. Even the | |||
proof-of-concept PostgreSQL implementation simply reports errors for all calls | |||
to these methods. | |||
Even for a plugin written in Go, implementing these methods "correctly" would | |||
require parsing and translating the custom query language over whatever storage | |||
platform the indexer uses. | |||
For a plugin _not_ written in Go, even beyond the cost of integration the | |||
developer would have to re-implement the entire query language. | |||
### Issue 3: Indexing Delays Consensus | |||
Within the node, indexing hooks in to the same internal pubsub dispatcher that | |||
is used to export event items to the [event subscription RPC API][event-rpc]. | |||
In contrast with RPC subscribers, however, indexing is a "privileged" | |||
subscriber: If an RPC subscriber is "too slow", the node may terminate the | |||
subscription and disconnect the client. That means that RPC subscribers may | |||
lose (miss) event items. The indexer, however, is "unbuffered", and the | |||
publisher will never drop or disconnect from it. If the indexer is slow, the | |||
publisher will block until it returns, to ensure that no event items are lost. | |||
In practice, this means that the performance of the indexer has a direct effect | |||
on the performance of the consensus node: If the indexer is slow or stalls, it | |||
will slow or halt the progress of consensus. Users have already reported this | |||
problem even with the built-in indexer (see, for example, [#7247][i7247]). | |||
Extending this concern to arbitrary user-defined custom indexers gives that | |||
risk a much larger surface area. | |||
## Discussion | |||
It is not possible to simultaneously guarantee that publishing event items will | |||
not delay consensus, and also that all event items of interest are always | |||
completely indexed. | |||
Therefore, our choice is between eliminating delay (and minimizing loss) or | |||
eliminating loss (and minimizing delay). Currently, we take the second | |||
approach, which has led to user complaints about consensus delays due to | |||
indexing and subscription overhead. | |||
- If we agree that consensus performance supersedes index completeness, our | |||
design choices are to constrain the likelihood and frequency of missing event | |||
items. | |||
- If we decide that consensus performance is more important than index | |||
completeness, our option is to minimize overhead on the event delivery path | |||
and document that indexer plugins constrain the rate of consensus. | |||
Since we have user reports requesting both properties, we have to choose one or | |||
the other. Since the primary job of the consensus engine is to correctly, | |||
robustly, reliablly, and efficiently replicate application state across the | |||
network, I believe the correct choice is to favor consensus performance. | |||
An important consideration for this decision is that a node does not index | |||
application metadata separately: If indexing is disabled, there is no built-in | |||
mechanism to go back and replay or reconstruct the data that an indexer would | |||
have stored. The node _does_ store the blockchain itself (i.e., the blocks and | |||
their transactions), so potentially some use cases currently handled by the | |||
indexer could be handled by the node. For example, allowing clients to ask | |||
whether a given transaction ID has been committed to a block could in principle | |||
be done without an indexer, since it does not depend on application metadata. | |||
Inevitably, a question will arise whether we could implement both strategies | |||
and toggle between them with a flag. That would be a worst-case scenario, | |||
requiring us to maintain the complexity of two very-different operational | |||
concerns. If our goal is that Tendermint should be as simple, efficient, and | |||
trustworthy as posible, there is not a strong case for making these options | |||
configurable: We should pick a side and commit to it. | |||
### Design Principles | |||
Although there is no unique "best" solution to the issues described above, | |||
there are some specific principles that a solution should include: | |||
1. **A custom indexer should not require integration into Tendermint core.** A | |||
developer or node operator can create, build, deploy, and use a custom | |||
indexer with a stock build of the Tendermint consensus node. | |||
2. **Custom indexers cannot stall consensus.** An indexer that is slow or | |||
stalls cannot slow down or prevent core consensus from making progress. | |||
The plugin interface must give node operators control over the tolerances | |||
for acceptable indexer performance, and the means to detect when indexers | |||
are falling outside those tolerances, but indexer failures should "fail | |||
safe" with respect to consensus (even if that means the indexer may miss | |||
some data, in sufficiently-extreme circumstances). | |||
3. **Custom indexers control which event items they index.** A custom indexer | |||
is not limited to only the current transaction and block events, but can | |||
observe any event item published by the node. | |||
4. **Custom indexing is forward-compatible.** Adding new event item types or | |||
metadata to the consensus node should not require existing custom indexers | |||
to be rebuilt or modified, unless they want to take advantage of the new | |||
data. | |||
5. **Indexers are responsible for answering queries.** An indexer plugin is not | |||
required to support the legacy query filter language, nor to be compatible | |||
with the legacy RPC endpoints for accessing them. Any APIs for clients to | |||
query a custom index are the responsibility of the indexer, not the node. | |||
### Open Questions | |||
Given the constraints outlined above, there are important design questions we | |||
must answer to guide any specific changes: | |||
1. **What is an acceptable probability that, given sufficiently extreme | |||
operational issues, an indexer might miss some number of events?** | |||
There are two parts to this question: One is what constitutes an extreme | |||
operational problem, the other is how likely we are to miss some number of | |||
events items. | |||
- If the consensus is that no event item must ever be missed, no matter how | |||
bad the operational circumstances, then we _must_ accept that indexing can | |||
slow or halt consensus arbitrarily. It is impossible to guarantee complete | |||
index coverage without potentially unbounded delays. | |||
- Otherwise, how much data can we afford to lose and how often? For example, | |||
if we can ensure no event item will be lost unless the indexer halts for | |||
at least five minutes, is that acceptable? What probabilities and time | |||
ranges are reasonable for real production environments? | |||
2. **What level of operational overhead is acceptable to impose on node | |||
operators to support indexing?** | |||
Are node operators willing to configure and run custom indexers as sidecar | |||
type processes alongside a node? How much indexer setup above and beyond the | |||
work of setting up the underlying node in isolation is tractable in | |||
production networks? | |||
The answer to this question also informs the question of whether we should | |||
keep an "in-process" indexing option, and to what extent that option needs | |||
to satisfy the suggested design principles. | |||
Relatedly, to what extent do we need to be concerned about the cost of | |||
encoding and sending event items to an external process (e.g., as JSON blobs | |||
or protobuf wire messages)? Given that the node already encodes event items | |||
as JSON for subscription purposes, the overhead would be negligible for the | |||
node itself, but the indexer would have to decode to process the results. | |||
3. **What (if any) query APIs does the consensus node need to export, | |||
independent of the indexer implementation?** | |||
One typical example is whether the node should be able to answer queries | |||
like "is this transaction ID in a block?" Currently, a node cannot answer | |||
this query _unless_ it runs the built-in KV indexer. Does the node need to | |||
continue to support that query even for nodes that disable the KV indexer, | |||
or which use a custom indexer? | |||
### Informal Design Intent | |||
The design principles described above implicate several components of the | |||
Tendermint node, beyond just the indexer. In the context of [ADR 075][adr075], | |||
we are re-working the RPC event subscription API to improve some of the UX | |||
issues discussed above for RPC clients. It is our expectation that a solution | |||
for pluggable custom indexing will take advantage of some of the same work. | |||
On that basis, the design approach I am considering for custom indexing looks | |||
something like this (subject to refinement): | |||
1. A custom indexer runs as a separate process from the node. | |||
2. The indexer subscribes to event items via the ADR 075 events API. | |||
This means indexers would receive event payloads as JSON rather than | |||
protobuf, but since we already have to support JSON encoding for the RPC | |||
interface anyway, that should not increase complexity for the node. | |||
3. The existing PostgreSQL indexer gets reworked to have this form, and no | |||
longer built as part of the Tendermint core binary. | |||
We can retain the code in the core repository as a proof-of-concept, or | |||
perhaps create a separate repository with contributed indexers and move it | |||
there. | |||
4. (Possibly) Deprecate and remove the legacy KV indexer, or disable it by | |||
default. If we decide to remove it, we can also remove the legacy RPC | |||
endpoints for querying the KV indexer. | |||
If we plan to do this, we should also investigate providing a way for | |||
clients to query whether a given transaction ID has landed in a block. That | |||
serves a common need, and currently _only_ works if the KV indexer is | |||
enabled, but could be addressed more simply using the other data a node | |||
already has stored, without having to answer more general queries. | |||
## References | |||
- [ADR 065: Custom Event Indexing][adr065] | |||
- [ADR 075: RPC Event Subscription Interface][adr075] | |||
- [Cosmos SDK][sdk] | |||
- [Event subscription RPC][event-rpc] | |||
- [KV transaction indexer][kv-index] | |||
- [Pluggable custom event indexing][i7135] (#7135) | |||
- [PostgreSQL event sink][psql] | |||
- [PostgreSQL database][postgres] | |||
- [Query filter language][query] | |||
- [Stream events to postgres for indexing][i1161] (#1161) | |||
- [Unbuffered event subscription slow down the consensus][i7247] (#7247) | |||
- [`EventSink` interface][esink] | |||
- [`tm-db` library][tmdb] | |||
[adr065]: https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-065-custom-event-indexing.md | |||
[adr075]: https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-075-rpc-subscription.md | |||
[esink]: https://pkg.go.dev/github.com/tendermint/tendermint/internal/state/indexer#EventSink | |||
[event-rpc]: https://docs.tendermint.com/master/rpc/#/Websocket/subscribe | |||
[i1161]: https://github.com/tendermint/tendermint/issues/1161 | |||
[i7135]: https://github.com/tendermint/tendermint/issues/7135 | |||
[i7247]: https://github.com/tendermint/tendermint/issues/7247 | |||
[kv-index]: https://github.com/tendermint/tendermint/blob/master/internal/state/indexer/tx/kv | |||
[postgres]: https://postgresql.org/ | |||
[psql]: https://github.com/tendermint/tendermint/blob/master/internal/state/indexer/sink/psql | |||
[psql]: https://github.com/tendermint/tendermint/blob/master/internal/state/indexer/sink/psql | |||
[query]: https://pkg.go.dev/github.com/tendermint/tendermint/internal/pubsub/query/syntax | |||
[sdk]: https://github.com/cosmos/cosmos-sdk | |||
[tmdb]: https://pkg.go.dev/github.com/tendermint/tm-db#DB |
@ -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,4 +1,3 @@ | |||
master master | |||
v0.33.x v0.33 | |||
v0.34.x v0.34 | |||
v0.35.x v0.35 |