mirror of
https://github.com/tendermint/tendermint.git
synced 2026-07-29 19:42:51 +00:00
spec: migrate v0.35 spec to tendermint
This commit is contained in:
+3
-1
@@ -7,4 +7,6 @@
|
||||
# global owners are only requested if there isn't a more specific
|
||||
# codeowner specified below. For this reason, the global codeowners
|
||||
# are often repeated in package-level definitions.
|
||||
* @ebuchman @cmwaters @tychoish @williambanfield @creachadair
|
||||
* @ebuchman @cmwaters @tychoish @williambanfield @creachadair @sergio-mena @jmalicevic @thanethomson @samricotta
|
||||
|
||||
/spec @josef-widder @milosevic @cason
|
||||
|
||||
@@ -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,8 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "11:00"
|
||||
open-pull-requests-limit: 10
|
||||
+8
-1
@@ -10,7 +10,7 @@
|
||||
.idea/
|
||||
.revision
|
||||
.tendermint
|
||||
.tendermint-lite
|
||||
.tendermint-light
|
||||
.terraform
|
||||
.vagrant
|
||||
.vendor-new/
|
||||
@@ -47,3 +47,10 @@ test/fuzz/**/corpus
|
||||
test/fuzz/**/crashers
|
||||
test/fuzz/**/suppressions
|
||||
test/fuzz/**/*.zip
|
||||
*.aux
|
||||
*.bbl
|
||||
*.blg
|
||||
*.pdf
|
||||
*.gz
|
||||
*.dvi
|
||||
.idea
|
||||
|
||||
@@ -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
|
||||
@@ -83,14 +83,29 @@ $(BUILDDIR)/:
|
||||
proto-all: proto-gen proto-lint proto-check-breaking
|
||||
.PHONY: proto-all
|
||||
|
||||
proto-gen:
|
||||
@echo "Generating Go packages for .proto files"
|
||||
@$(DOCKER_PROTO) sh ./scripts/protocgen.sh
|
||||
check-proto-deps:
|
||||
ifeq (,$(shell which protoc-gen-gogofaster))
|
||||
$(error "gogofaster plugin for protoc is required. Run 'go install github.com/gogo/protobuf/protoc-gen-gogofaster@latest' to install")
|
||||
endif
|
||||
.PHONY: check-proto-deps
|
||||
|
||||
check-proto-format-deps:
|
||||
ifeq (,$(shell which clang-format))
|
||||
$(error "clang-format is required for Protobuf formatting. See instructions for your platform on how to install it.")
|
||||
endif
|
||||
.PHONY: check-proto-format-deps
|
||||
|
||||
proto-gen: check-proto-deps
|
||||
@echo "Generating Protobuf files"
|
||||
@go run github.com/bufbuild/buf/cmd/buf generate
|
||||
@mv ./proto/tendermint/abci/types.pb.go ./abci/types/
|
||||
.PHONY: proto-gen
|
||||
|
||||
proto-lint:
|
||||
@echo "Running lint checks for .proto files"
|
||||
@$(DOCKER_PROTO) buf lint --error-format=json
|
||||
# These targets are provided for convenience and are intended for local
|
||||
# execution only.
|
||||
proto-lint: check-proto-deps
|
||||
@echo "Linting Protobuf files"
|
||||
@go run github.com/bufbuild/buf/cmd/buf lint
|
||||
.PHONY: proto-lint
|
||||
|
||||
proto-format:
|
||||
@@ -98,16 +113,14 @@ proto-format:
|
||||
@$(DOCKER_PROTO) find ./ -not -path "./third_party/*" -name '*.proto' -exec clang-format -i {} \;
|
||||
.PHONY: proto-format
|
||||
|
||||
proto-check-breaking:
|
||||
@echo "Checking for breaking changes in .proto files"
|
||||
@$(DOCKER_PROTO) buf breaking --against .git#branch=$(BASE_BRANCH)
|
||||
proto-check-breaking: check-proto-deps
|
||||
@echo "Checking for breaking changes in Protobuf files against local branch"
|
||||
@echo "Note: This is only useful if your changes have not yet been committed."
|
||||
@echo " Otherwise read up on buf's \"breaking\" command usage:"
|
||||
@echo " https://docs.buf.build/breaking/usage"
|
||||
@go run github.com/bufbuild/buf/cmd/buf breaking --against ".git"
|
||||
.PHONY: proto-check-breaking
|
||||
|
||||
proto-check-breaking-ci:
|
||||
@echo "Checking for breaking changes in .proto files"
|
||||
@$(DOCKER_PROTO) buf breaking --against $(HTTPS_GIT)#branch=$(BASE_BRANCH)
|
||||
.PHONY: proto-check-breaking-ci
|
||||
|
||||
###############################################################################
|
||||
### Build ABCI ###
|
||||
###############################################################################
|
||||
|
||||
+160
-40
@@ -7715,7 +7715,10 @@ func (m *Request) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -7797,7 +7800,10 @@ func (m *RequestEcho) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -7847,7 +7853,10 @@ func (m *RequestFlush) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -7999,7 +8008,10 @@ func (m *RequestInfo) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -8237,7 +8249,10 @@ func (m *RequestInitChain) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -8392,7 +8407,10 @@ func (m *RequestQuery) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -8576,7 +8594,10 @@ func (m *RequestBeginBlock) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -8679,7 +8700,10 @@ func (m *RequestCheckTx) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -8763,7 +8787,10 @@ func (m *RequestDeliverTx) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -8832,7 +8859,10 @@ func (m *RequestEndBlock) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -8882,7 +8912,10 @@ func (m *RequestCommit) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -8932,7 +8965,10 @@ func (m *RequestListSnapshots) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -9052,7 +9088,10 @@ func (m *RequestOfferSnapshot) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -9159,7 +9198,10 @@ func (m *RequestLoadSnapshotChunk) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -9294,7 +9336,10 @@ func (m *RequestApplySnapshotChunk) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -9869,7 +9914,10 @@ func (m *Response) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -9951,7 +9999,10 @@ func (m *ResponseException) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -10033,7 +10084,10 @@ func (m *ResponseEcho) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -10083,7 +10137,10 @@ func (m *ResponseFlush) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -10269,7 +10326,10 @@ func (m *ResponseInfo) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -10423,7 +10483,10 @@ func (m *ResponseInitChain) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -10730,7 +10793,10 @@ func (m *ResponseQuery) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -10814,7 +10880,10 @@ func (m *ResponseBeginBlock) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -11168,7 +11237,10 @@ func (m *ResponseCheckTx) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -11439,7 +11511,10 @@ func (m *ResponseDeliverTx) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -11593,7 +11668,10 @@ func (m *ResponseEndBlock) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -11696,7 +11774,10 @@ func (m *ResponseCommit) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -11780,7 +11861,10 @@ func (m *ResponseListSnapshots) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -11849,7 +11933,10 @@ func (m *ResponseOfferSnapshot) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -11933,7 +12020,10 @@ func (m *ResponseLoadSnapshotChunk) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -12110,7 +12200,10 @@ func (m *ResponseApplySnapshotChunk) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -12213,7 +12306,10 @@ func (m *LastCommitInfo) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -12329,7 +12425,10 @@ func (m *Event) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -12463,7 +12562,10 @@ func (m *EventAttribute) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -12618,7 +12720,10 @@ func (m *TxResult) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -12721,7 +12826,10 @@ func (m *Validator) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -12823,7 +12931,10 @@ func (m *ValidatorUpdate) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -12926,7 +13037,10 @@ func (m *VoteInfo) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -13099,7 +13213,10 @@ func (m *Evidence) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -13274,7 +13391,10 @@ func (m *Snapshot) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version: v1
|
||||
directories:
|
||||
- proto
|
||||
@@ -0,0 +1,108 @@
|
||||
# RFC 001: 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
|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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,81 @@
|
||||
# RFC 002: 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)
|
||||
|
||||
## 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,56 @@
|
||||
# RFC 003: Ed25519 Verification
|
||||
|
||||
## Changelog
|
||||
|
||||
- August 21, 2020: initialized
|
||||
|
||||
## 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,252 @@
|
||||
# RFC 004: ABCI++
|
||||
|
||||
## Changelog
|
||||
|
||||
- January 11, 2020: initialized
|
||||
|
||||
## 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,202 @@
|
||||
# RFC 004: ReverseSync - fetching historical data
|
||||
|
||||
## Changelog
|
||||
|
||||
- 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)
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
order: 1
|
||||
parent:
|
||||
order: false
|
||||
---
|
||||
|
||||
# Requests for Comments
|
||||
|
||||
A Request for Comments (RFC) is a record of discussion on an open-ended topic
|
||||
related to the design and implementation of Tendermint Core, for which no
|
||||
immediate decision is required.
|
||||
|
||||
The purpose of an RFC is to serve as a historical record of a high-level
|
||||
discussion that might otherwise only be recorded in an ad hoc way (for example,
|
||||
via gists or Google docs) that are difficult to discover for someone after the
|
||||
fact. An RFC _may_ give rise to more specific architectural _decisions_ for
|
||||
Tendermint, but those decisions must be recorded separately in [Architecture
|
||||
Decision Records (ADR)](./../architecture).
|
||||
|
||||
As a rule of thumb, if you can articulate a specific question that needs to be
|
||||
answered, write an ADR. If you need to explore the topic and get input from
|
||||
others to know what questions need to be answered, an RFC may be appropriate.
|
||||
|
||||
## RFC Content
|
||||
|
||||
An RFC should provide:
|
||||
|
||||
- A **changelog**, documenting when and how the RFC has changed.
|
||||
- An **abstract**, briefly summarizing the topic so the reader can quickly tell
|
||||
whether it is relevant to their interest.
|
||||
- Any **background** a reader will need to understand and participate in the
|
||||
substance of the discussion (links to other documents are fine here).
|
||||
- The **discussion**, the primary content of the document.
|
||||
|
||||
The [rfc-template.md](./rfc-template.md) file includes placeholders for these
|
||||
sections.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [001-block-retention](./001-block-retention.md)
|
||||
- [002-nonzero-genesis](./002-nonzero-genesis.md)
|
||||
- [003-ed25519-verification](./003-ed25519-verification.md)
|
||||
- [004-abci++](./004-abci++.md)
|
||||
- [005-reverse-sync](./005-reverse-sync.md)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.7 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
@@ -0,0 +1,39 @@
|
||||
# RFC {RFC-NUMBER}: {TITLE}
|
||||
|
||||
## Changelog
|
||||
|
||||
- {date}: {changelog}
|
||||
|
||||
## Author(s)
|
||||
|
||||
- {First Name} {github handle}
|
||||
|
||||
## Context
|
||||
|
||||
> This section contains all the context one needs to understand the current state, and why there is a problem. It should be as succinct as possible and introduce the high level idea behind the solution.
|
||||
|
||||
## Proposal
|
||||
|
||||
> It should contain a detailed breakdown of how the problem should be resolved including diagrams and other supporting materials needed to present the case and implementation roadmap for the proposed changes. The reader should be able to fully understand the proposal. This section should be broken up using ## subsections as needed.
|
||||
|
||||
## Status
|
||||
|
||||
> A decision may be "proposed" if it hasn't been agreed upon yet, or "accepted" once it is agreed upon. If a later RFC changes or reverses a decision, it may be marked as "deprecated" or "superseded" with a reference to its replacement.
|
||||
|
||||
{Deprecated|Proposed|Accepted}
|
||||
|
||||
## Consequences
|
||||
|
||||
> This section describes the consequences, after applying the decision. All consequences should be summarized here, not just the "positive" ones.
|
||||
|
||||
### Positive
|
||||
|
||||
### Negative
|
||||
|
||||
### Neutral
|
||||
|
||||
## References
|
||||
|
||||
> Are there any relevant PR comments, issues that led up to this, or articles referenced for why we made the given design choice? If so link them here!
|
||||
|
||||
- {reference link}
|
||||
@@ -8,6 +8,7 @@ require (
|
||||
github.com/adlio/schema v1.3.3
|
||||
github.com/btcsuite/btcd v0.22.1
|
||||
github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce
|
||||
github.com/bufbuild/buf v1.3.1
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
|
||||
github.com/creachadair/atomicfile v0.2.6
|
||||
github.com/creachadair/taskgroup v0.3.2
|
||||
@@ -47,9 +48,8 @@ require (
|
||||
github.com/vektra/mockery/v2 v2.14.0
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
|
||||
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
|
||||
google.golang.org/grpc v1.48.0
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
|
||||
google.golang.org/grpc v1.47.0
|
||||
gotest.tools v2.2.0+incompatible // indirect
|
||||
pgregory.net/rapid v0.4.7
|
||||
)
|
||||
|
||||
@@ -145,6 +145,8 @@ github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVj
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o=
|
||||
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
||||
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
@@ -176,6 +178,8 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku
|
||||
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/bufbuild/buf v1.3.1 h1:AelWcENnbNEjwxmQXIZaU51GHgnWQ8Mc94kZdDUKgRs=
|
||||
github.com/bufbuild/buf v1.3.1/go.mod h1:CTRUb23N+zlm1U8ZIBKz0Sqluk++qQloB2i/MZNZHIs=
|
||||
github.com/butuzov/ireturn v0.1.1 h1:QvrO2QF2+/Cx1WA/vETCIYBKtRjc30vesdoPUNo1EbY=
|
||||
github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc=
|
||||
github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||
@@ -228,10 +232,12 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
||||
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creachadair/atomicfile v0.2.6 h1:FgYxYvGcqREApTY8Nxg8msM6P/KVKK3ob5h9FaRUTNg=
|
||||
github.com/creachadair/atomicfile v0.2.6/go.mod h1:BRq8Une6ckFneYXZQ+kO7p1ZZP3I2fzVzf28JxrIkBc=
|
||||
@@ -381,6 +387,8 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
|
||||
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
|
||||
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
@@ -398,6 +406,7 @@ github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4er
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
@@ -614,11 +623,17 @@ github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||
github.com/jdxcode/netrc v0.0.0-20210204082910-926c7f70242a h1:d4+I1YEKVmWZrgkt6jpXBnLgV2ZjO0YxEtLDdfIZfH4=
|
||||
github.com/jdxcode/netrc v0.0.0-20210204082910-926c7f70242a/go.mod h1:Zi/ZFkEqFHTm7qkjyNJjaWH4LQA9LQhGJyF0lTYGpxw=
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jgautheron/goconst v1.5.1 h1:HxVbL1MhydKs8R8n/HE5NPvzfaYmQJA3o879lE4+WcM=
|
||||
github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4=
|
||||
github.com/jhump/protocompile v0.0.0-20220216033700-d705409f108f h1:BNuUg9k2EiJmlMwjoef3e8vZLHplbVw6DrjGFjLL+Yo=
|
||||
github.com/jhump/protocompile v0.0.0-20220216033700-d705409f108f/go.mod h1:qr2b5kx4HbFS7/g4uYO5qv9ei8303JMsC7ESbYiqr2Q=
|
||||
github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4=
|
||||
github.com/jhump/protoreflect v1.11.1-0.20220213155251-0c2aedc66cf4 h1:E2CdxLXYSn6Zrj2+u8DWrwMJW3YZLSWtM/7kIL8OL18=
|
||||
github.com/jhump/protoreflect v1.11.1-0.20220213155251-0c2aedc66cf4/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E=
|
||||
github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs=
|
||||
github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c=
|
||||
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48=
|
||||
@@ -661,6 +676,10 @@ github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6
|
||||
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.1 h1:y9FcTHGyrebwfP0ZZqFiaxTaiDnUrGkJkI+f583BL1A=
|
||||
github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
|
||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@@ -812,6 +831,7 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/oasisprotocol/curve25519-voi v0.0.0-20210609091139-0a56a4bca00b h1:MKwruh+HeCSKWphkxuzvRzU4QzDkg7yiPkDVV0cDFgI=
|
||||
github.com/oasisprotocol/curve25519-voi v0.0.0-20210609091139-0a56a4bca00b/go.mod h1:TLJifjWF6eotcfzDjKZsDqWJ+73Uvj/N85MvVyrvynM=
|
||||
github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=
|
||||
@@ -861,6 +881,7 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9
|
||||
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
@@ -879,12 +900,16 @@ github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7
|
||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
github.com/pkg/profile v1.6.0 h1:hUDfIISABYI59DyeB3OTay/HxSRwTQ8rB/H83k6r5dM=
|
||||
github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@@ -957,8 +982,10 @@ github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs=
|
||||
github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U=
|
||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryancurrah/gomodguard v1.2.3 h1:ww2fsjqocGCAFamzvv/b8IsRduuHHeK2MHTcTxZTQX8=
|
||||
github.com/ryancurrah/gomodguard v1.2.3/go.mod h1:rYbA/4Tg5c54mV1sv4sQTP5WOPBcoLtnBZ7/TEhXAbg=
|
||||
@@ -1164,24 +1191,32 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
|
||||
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
|
||||
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
|
||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@@ -1358,8 +1393,9 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1441,6 +1477,7 @@ golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -1801,6 +1838,7 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
|
||||
@@ -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](../docs/rfc/README.md) may be requested.
|
||||
1a. Submission of an RFC as a pull request should be made to facilitate further discussion.
|
||||
1b. Merge the RFC.
|
||||
2. Make the necessary changes to the `.proto` file(s), [core data structures](../spec/core/data_structures.md) and/or [ABCI protocol](../spec/abci/apps.md).
|
||||
3. Rebuild the Go protocol buffers by running `make proto-gen`. Ensure that the project builds correctly by running `make build`.
|
||||
@@ -1,16 +1,11 @@
|
||||
version: v1beta1
|
||||
|
||||
build:
|
||||
roots:
|
||||
- proto
|
||||
- third_party/proto
|
||||
version: v1
|
||||
deps:
|
||||
- buf.build/gogo/protobuf
|
||||
breaking:
|
||||
use:
|
||||
- FILE
|
||||
lint:
|
||||
use:
|
||||
- BASIC
|
||||
- FILE_LOWER_SNAKE_CASE
|
||||
- UNARY_RPC
|
||||
ignore:
|
||||
- gogoproto
|
||||
breaking:
|
||||
use:
|
||||
- FILE
|
||||
@@ -899,7 +899,10 @@ func (m *BlockRequest) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -968,7 +971,10 @@ func (m *NoBlockResponse) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1054,7 +1060,10 @@ func (m *BlockResponse) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1104,7 +1113,10 @@ func (m *StatusRequest) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1192,7 +1204,10 @@ func (m *StatusResponse) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1417,7 +1432,10 @@ func (m *Message) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -1932,7 +1932,10 @@ func (m *NewRoundStep) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2109,7 +2112,10 @@ func (m *NewValidBlock) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2192,7 +2198,10 @@ func (m *Proposal) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2313,7 +2322,10 @@ func (m *ProposalPOL) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2434,7 +2446,10 @@ func (m *BlockPart) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2520,7 +2535,10 @@ func (m *Vote) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2646,7 +2664,10 @@ func (m *HasVote) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2786,7 +2807,10 @@ func (m *VoteSetMaj23) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2959,7 +2983,10 @@ func (m *VoteSetBits) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -3324,7 +3351,10 @@ func (m *Message) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -921,7 +921,10 @@ func (m *MsgInfo) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthWal
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthWal
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1061,7 +1064,10 @@ func (m *TimeoutInfo) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthWal
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthWal
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1130,7 +1136,10 @@ func (m *EndHeight) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthWal
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthWal
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1320,7 +1329,10 @@ func (m *WALMessage) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthWal
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthWal
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1439,7 +1451,10 @@ func (m *TimedWALMessage) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthWal
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthWal
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -687,7 +687,10 @@ func (m *PublicKey) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthKeys
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthKeys
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -820,7 +820,10 @@ func (m *Proof) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthProof
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthProof
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -940,7 +943,10 @@ func (m *ValueOp) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthProof
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthProof
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1086,7 +1092,10 @@ func (m *DominoOp) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthProof
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthProof
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1236,7 +1245,10 @@ func (m *ProofOp) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthProof
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthProof
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1320,7 +1332,10 @@ func (m *ProofOps) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthProof
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthProof
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -307,7 +307,10 @@ func (m *BitArray) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -370,7 +370,10 @@ func (m *Txs) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -455,7 +458,10 @@ func (m *Message) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -723,7 +723,10 @@ func (m *PacketPing) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthConn
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthConn
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -773,7 +776,10 @@ func (m *PacketPong) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthConn
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthConn
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -896,7 +902,10 @@ func (m *PacketMsg) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthConn
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthConn
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1051,7 +1060,10 @@ func (m *Packet) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthConn
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthConn
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1168,7 +1180,10 @@ func (m *AuthSigMessage) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthConn
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthConn
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -1025,7 +1025,10 @@ func (m *PexAddress) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthPex
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthPex
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1075,7 +1078,10 @@ func (m *PexRequest) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthPex
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthPex
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1159,7 +1165,10 @@ func (m *PexResponse) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthPex
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthPex
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1241,7 +1250,10 @@ func (m *PexAddressV2) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthPex
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthPex
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1291,7 +1303,10 @@ func (m *PexRequestV2) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthPex
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthPex
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1375,7 +1390,10 @@ func (m *PexResponseV2) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthPex
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthPex
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1565,7 +1583,10 @@ func (m *PexMessage) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthPex
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthPex
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -938,7 +938,10 @@ func (m *ProtocolVersion) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1248,7 +1251,10 @@ func (m *NodeInfo) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1362,7 +1368,10 @@ func (m *NodeInfoOther) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1534,7 +1543,10 @@ func (m *PeerInfo) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1707,7 +1719,10 @@ func (m *PeerAddressInfo) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -1708,7 +1708,10 @@ func (m *RemoteSignerError) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1790,7 +1793,10 @@ func (m *PubKeyRequest) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1909,7 +1915,10 @@ func (m *PubKeyResponse) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2027,7 +2036,10 @@ func (m *SignVoteRequest) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2146,7 +2158,10 @@ func (m *SignedVoteResponse) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2264,7 +2279,10 @@ func (m *SignProposalRequest) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2383,7 +2401,10 @@ func (m *SignedProposalResponse) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2433,7 +2454,10 @@ func (m *PingRequest) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2483,7 +2507,10 @@ func (m *PingResponse) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2813,7 +2840,10 @@ func (m *Message) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2930,7 +2960,10 @@ func (m *AuthSigMessage) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -0,0 +1,936 @@
|
||||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: tendermint/rpc/grpc/types.proto
|
||||
|
||||
package coregrpc
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
types "github.com/tendermint/tendermint/abci/types"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
io "io"
|
||||
math "math"
|
||||
math_bits "math/bits"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type RequestPing struct {
|
||||
}
|
||||
|
||||
func (m *RequestPing) Reset() { *m = RequestPing{} }
|
||||
func (m *RequestPing) String() string { return proto.CompactTextString(m) }
|
||||
func (*RequestPing) ProtoMessage() {}
|
||||
func (*RequestPing) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_0ffff5682c662b95, []int{0}
|
||||
}
|
||||
func (m *RequestPing) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *RequestPing) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_RequestPing.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *RequestPing) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_RequestPing.Merge(m, src)
|
||||
}
|
||||
func (m *RequestPing) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *RequestPing) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_RequestPing.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_RequestPing proto.InternalMessageInfo
|
||||
|
||||
type RequestBroadcastTx struct {
|
||||
Tx []byte `protobuf:"bytes,1,opt,name=tx,proto3" json:"tx,omitempty"`
|
||||
}
|
||||
|
||||
func (m *RequestBroadcastTx) Reset() { *m = RequestBroadcastTx{} }
|
||||
func (m *RequestBroadcastTx) String() string { return proto.CompactTextString(m) }
|
||||
func (*RequestBroadcastTx) ProtoMessage() {}
|
||||
func (*RequestBroadcastTx) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_0ffff5682c662b95, []int{1}
|
||||
}
|
||||
func (m *RequestBroadcastTx) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *RequestBroadcastTx) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_RequestBroadcastTx.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *RequestBroadcastTx) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_RequestBroadcastTx.Merge(m, src)
|
||||
}
|
||||
func (m *RequestBroadcastTx) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *RequestBroadcastTx) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_RequestBroadcastTx.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_RequestBroadcastTx proto.InternalMessageInfo
|
||||
|
||||
func (m *RequestBroadcastTx) GetTx() []byte {
|
||||
if m != nil {
|
||||
return m.Tx
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ResponsePing struct {
|
||||
}
|
||||
|
||||
func (m *ResponsePing) Reset() { *m = ResponsePing{} }
|
||||
func (m *ResponsePing) String() string { return proto.CompactTextString(m) }
|
||||
func (*ResponsePing) ProtoMessage() {}
|
||||
func (*ResponsePing) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_0ffff5682c662b95, []int{2}
|
||||
}
|
||||
func (m *ResponsePing) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *ResponsePing) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_ResponsePing.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *ResponsePing) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ResponsePing.Merge(m, src)
|
||||
}
|
||||
func (m *ResponsePing) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *ResponsePing) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_ResponsePing.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_ResponsePing proto.InternalMessageInfo
|
||||
|
||||
type ResponseBroadcastTx struct {
|
||||
CheckTx *types.ResponseCheckTx `protobuf:"bytes,1,opt,name=check_tx,json=checkTx,proto3" json:"check_tx,omitempty"`
|
||||
DeliverTx *types.ResponseDeliverTx `protobuf:"bytes,2,opt,name=deliver_tx,json=deliverTx,proto3" json:"deliver_tx,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ResponseBroadcastTx) Reset() { *m = ResponseBroadcastTx{} }
|
||||
func (m *ResponseBroadcastTx) String() string { return proto.CompactTextString(m) }
|
||||
func (*ResponseBroadcastTx) ProtoMessage() {}
|
||||
func (*ResponseBroadcastTx) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_0ffff5682c662b95, []int{3}
|
||||
}
|
||||
func (m *ResponseBroadcastTx) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *ResponseBroadcastTx) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_ResponseBroadcastTx.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *ResponseBroadcastTx) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ResponseBroadcastTx.Merge(m, src)
|
||||
}
|
||||
func (m *ResponseBroadcastTx) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *ResponseBroadcastTx) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_ResponseBroadcastTx.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_ResponseBroadcastTx proto.InternalMessageInfo
|
||||
|
||||
func (m *ResponseBroadcastTx) GetCheckTx() *types.ResponseCheckTx {
|
||||
if m != nil {
|
||||
return m.CheckTx
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ResponseBroadcastTx) GetDeliverTx() *types.ResponseDeliverTx {
|
||||
if m != nil {
|
||||
return m.DeliverTx
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*RequestPing)(nil), "tendermint.rpc.grpc.RequestPing")
|
||||
proto.RegisterType((*RequestBroadcastTx)(nil), "tendermint.rpc.grpc.RequestBroadcastTx")
|
||||
proto.RegisterType((*ResponsePing)(nil), "tendermint.rpc.grpc.ResponsePing")
|
||||
proto.RegisterType((*ResponseBroadcastTx)(nil), "tendermint.rpc.grpc.ResponseBroadcastTx")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("tendermint/rpc/grpc/types.proto", fileDescriptor_0ffff5682c662b95) }
|
||||
|
||||
var fileDescriptor_0ffff5682c662b95 = []byte{
|
||||
// 316 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x2f, 0x49, 0xcd, 0x4b,
|
||||
0x49, 0x2d, 0xca, 0xcd, 0xcc, 0x2b, 0xd1, 0x2f, 0x2a, 0x48, 0xd6, 0x4f, 0x07, 0x11, 0x25, 0x95,
|
||||
0x05, 0xa9, 0xc5, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0xc2, 0x08, 0x05, 0x7a, 0x45, 0x05,
|
||||
0xc9, 0x7a, 0x20, 0x05, 0x52, 0xd2, 0x48, 0xba, 0x12, 0x93, 0x92, 0x33, 0x91, 0x75, 0x28, 0xf1,
|
||||
0x72, 0x71, 0x07, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0x04, 0x64, 0xe6, 0xa5, 0x2b, 0xa9, 0x70,
|
||||
0x09, 0x41, 0xb9, 0x4e, 0x45, 0xf9, 0x89, 0x29, 0xc9, 0x89, 0xc5, 0x25, 0x21, 0x15, 0x42, 0x7c,
|
||||
0x5c, 0x4c, 0x25, 0x15, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x3c, 0x41, 0x4c, 0x25, 0x15, 0x4a, 0x7c,
|
||||
0x5c, 0x3c, 0x41, 0xa9, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9, 0x60, 0x5d, 0x53, 0x19, 0xb9, 0x84,
|
||||
0x61, 0x02, 0xc8, 0xfa, 0xac, 0xb9, 0x38, 0x92, 0x33, 0x52, 0x93, 0xb3, 0xe3, 0xa1, 0xba, 0xb9,
|
||||
0x8d, 0x14, 0xf4, 0x90, 0x5c, 0x08, 0x72, 0x8c, 0x1e, 0x4c, 0x9f, 0x33, 0x48, 0x61, 0x48, 0x45,
|
||||
0x10, 0x7b, 0x32, 0x84, 0x21, 0xe4, 0xc8, 0xc5, 0x95, 0x92, 0x9a, 0x93, 0x59, 0x96, 0x5a, 0x04,
|
||||
0xd2, 0xce, 0x04, 0xd6, 0xae, 0x84, 0x53, 0xbb, 0x0b, 0x44, 0x69, 0x48, 0x45, 0x10, 0x67, 0x0a,
|
||||
0x8c, 0x69, 0xb4, 0x97, 0x91, 0x8b, 0x07, 0xee, 0x1e, 0xc7, 0x00, 0x4f, 0x21, 0x6f, 0x2e, 0x16,
|
||||
0x90, 0x83, 0x85, 0x50, 0x9c, 0x01, 0x0b, 0x28, 0x3d, 0xa4, 0x80, 0x90, 0x52, 0xc4, 0xa1, 0x02,
|
||||
0xe1, 0x6b, 0xa1, 0x04, 0x2e, 0x6e, 0x64, 0xcf, 0xaa, 0xe3, 0x33, 0x13, 0x49, 0xa1, 0x94, 0x06,
|
||||
0x5e, 0xa3, 0x91, 0x54, 0x3a, 0xf9, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83,
|
||||
0x47, 0x72, 0x8c, 0x13, 0x1e, 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43,
|
||||
0x94, 0x51, 0x7a, 0x66, 0x49, 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0xae, 0x3e, 0x52, 0xf4, 0x62,
|
||||
0x49, 0x1f, 0xd6, 0xc9, 0xf9, 0x45, 0xa9, 0x20, 0x46, 0x12, 0x1b, 0x38, 0xc6, 0x8d, 0x01, 0x01,
|
||||
0x00, 0x00, 0xff, 0xff, 0xf6, 0x4b, 0x02, 0xd8, 0x46, 0x02, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// BroadcastAPIClient is the client API for BroadcastAPI service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
|
||||
type BroadcastAPIClient interface {
|
||||
Ping(ctx context.Context, in *RequestPing, opts ...grpc.CallOption) (*ResponsePing, error)
|
||||
BroadcastTx(ctx context.Context, in *RequestBroadcastTx, opts ...grpc.CallOption) (*ResponseBroadcastTx, error)
|
||||
}
|
||||
|
||||
type broadcastAPIClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewBroadcastAPIClient(cc *grpc.ClientConn) BroadcastAPIClient {
|
||||
return &broadcastAPIClient{cc}
|
||||
}
|
||||
|
||||
func (c *broadcastAPIClient) Ping(ctx context.Context, in *RequestPing, opts ...grpc.CallOption) (*ResponsePing, error) {
|
||||
out := new(ResponsePing)
|
||||
err := c.cc.Invoke(ctx, "/tendermint.rpc.grpc.BroadcastAPI/Ping", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *broadcastAPIClient) BroadcastTx(ctx context.Context, in *RequestBroadcastTx, opts ...grpc.CallOption) (*ResponseBroadcastTx, error) {
|
||||
out := new(ResponseBroadcastTx)
|
||||
err := c.cc.Invoke(ctx, "/tendermint.rpc.grpc.BroadcastAPI/BroadcastTx", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// BroadcastAPIServer is the server API for BroadcastAPI service.
|
||||
type BroadcastAPIServer interface {
|
||||
Ping(context.Context, *RequestPing) (*ResponsePing, error)
|
||||
BroadcastTx(context.Context, *RequestBroadcastTx) (*ResponseBroadcastTx, error)
|
||||
}
|
||||
|
||||
// UnimplementedBroadcastAPIServer can be embedded to have forward compatible implementations.
|
||||
type UnimplementedBroadcastAPIServer struct {
|
||||
}
|
||||
|
||||
func (*UnimplementedBroadcastAPIServer) Ping(ctx context.Context, req *RequestPing) (*ResponsePing, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented")
|
||||
}
|
||||
func (*UnimplementedBroadcastAPIServer) BroadcastTx(ctx context.Context, req *RequestBroadcastTx) (*ResponseBroadcastTx, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method BroadcastTx not implemented")
|
||||
}
|
||||
|
||||
func RegisterBroadcastAPIServer(s *grpc.Server, srv BroadcastAPIServer) {
|
||||
s.RegisterService(&_BroadcastAPI_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _BroadcastAPI_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(RequestPing)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(BroadcastAPIServer).Ping(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/tendermint.rpc.grpc.BroadcastAPI/Ping",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(BroadcastAPIServer).Ping(ctx, req.(*RequestPing))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _BroadcastAPI_BroadcastTx_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(RequestBroadcastTx)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(BroadcastAPIServer).BroadcastTx(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/tendermint.rpc.grpc.BroadcastAPI/BroadcastTx",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(BroadcastAPIServer).BroadcastTx(ctx, req.(*RequestBroadcastTx))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _BroadcastAPI_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "tendermint.rpc.grpc.BroadcastAPI",
|
||||
HandlerType: (*BroadcastAPIServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Ping",
|
||||
Handler: _BroadcastAPI_Ping_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "BroadcastTx",
|
||||
Handler: _BroadcastAPI_BroadcastTx_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "tendermint/rpc/grpc/types.proto",
|
||||
}
|
||||
|
||||
func (m *RequestPing) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *RequestPing) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *RequestPing) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *RequestBroadcastTx) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *RequestBroadcastTx) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *RequestBroadcastTx) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.Tx) > 0 {
|
||||
i -= len(m.Tx)
|
||||
copy(dAtA[i:], m.Tx)
|
||||
i = encodeVarintTypes(dAtA, i, uint64(len(m.Tx)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *ResponsePing) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *ResponsePing) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *ResponsePing) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *ResponseBroadcastTx) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *ResponseBroadcastTx) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *ResponseBroadcastTx) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.DeliverTx != nil {
|
||||
{
|
||||
size, err := m.DeliverTx.MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintTypes(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
}
|
||||
if m.CheckTx != nil {
|
||||
{
|
||||
size, err := m.CheckTx.MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintTypes(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func encodeVarintTypes(dAtA []byte, offset int, v uint64) int {
|
||||
offset -= sovTypes(v)
|
||||
base := offset
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
v >>= 7
|
||||
offset++
|
||||
}
|
||||
dAtA[offset] = uint8(v)
|
||||
return base
|
||||
}
|
||||
func (m *RequestPing) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *RequestBroadcastTx) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
l = len(m.Tx)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovTypes(uint64(l))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *ResponsePing) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *ResponseBroadcastTx) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
if m.CheckTx != nil {
|
||||
l = m.CheckTx.Size()
|
||||
n += 1 + l + sovTypes(uint64(l))
|
||||
}
|
||||
if m.DeliverTx != nil {
|
||||
l = m.DeliverTx.Size()
|
||||
n += 1 + l + sovTypes(uint64(l))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func sovTypes(x uint64) (n int) {
|
||||
return (math_bits.Len64(x|1) + 6) / 7
|
||||
}
|
||||
func sozTypes(x uint64) (n int) {
|
||||
return sovTypes(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
}
|
||||
func (m *RequestPing) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: RequestPing: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: RequestPing: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipTypes(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *RequestBroadcastTx) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: RequestBroadcastTx: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: RequestBroadcastTx: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Tx", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if byteLen < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Tx = append(m.Tx[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.Tx == nil {
|
||||
m.Tx = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipTypes(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *ResponsePing) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: ResponsePing: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: ResponsePing: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipTypes(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *ResponseBroadcastTx) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: ResponseBroadcastTx: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: ResponseBroadcastTx: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field CheckTx", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if m.CheckTx == nil {
|
||||
m.CheckTx = &types.ResponseCheckTx{}
|
||||
}
|
||||
if err := m.CheckTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field DeliverTx", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if m.DeliverTx == nil {
|
||||
m.DeliverTx = &types.ResponseDeliverTx{}
|
||||
}
|
||||
if err := m.DeliverTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipTypes(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func skipTypes(dAtA []byte) (n int, err error) {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
depth := 0
|
||||
for iNdEx < l {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
wireType := int(wire & 0x7)
|
||||
switch wireType {
|
||||
case 0:
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx++
|
||||
if dAtA[iNdEx-1] < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
iNdEx += 8
|
||||
case 2:
|
||||
var length int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
length |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthTypes
|
||||
}
|
||||
iNdEx += length
|
||||
case 3:
|
||||
depth++
|
||||
case 4:
|
||||
if depth == 0 {
|
||||
return 0, ErrUnexpectedEndOfGroupTypes
|
||||
}
|
||||
depth--
|
||||
case 5:
|
||||
iNdEx += 4
|
||||
default:
|
||||
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||
}
|
||||
if iNdEx < 0 {
|
||||
return 0, ErrInvalidLengthTypes
|
||||
}
|
||||
if depth == 0 {
|
||||
return iNdEx, nil
|
||||
}
|
||||
}
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidLengthTypes = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowTypes = fmt.Errorf("proto: integer overflow")
|
||||
ErrUnexpectedEndOfGroupTypes = fmt.Errorf("proto: unexpected end of group")
|
||||
)
|
||||
@@ -1069,7 +1069,10 @@ func (m *ABCIResponses) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1174,7 +1177,10 @@ func (m *ValidatorsInfo) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1276,7 +1282,10 @@ func (m *ConsensusParamsInfo) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1391,7 +1400,10 @@ func (m *Version) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1857,7 +1869,10 @@ func (m *State) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -1740,7 +1740,10 @@ func (m *Message) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1790,7 +1793,10 @@ func (m *SnapshotsRequest) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1965,7 +1971,10 @@ func (m *SnapshotsResponse) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2072,7 +2081,10 @@ func (m *ChunkRequest) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2233,7 +2245,10 @@ func (m *ChunkResponse) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2302,7 +2317,10 @@ func (m *LightBlockRequest) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2388,7 +2406,10 @@ func (m *LightBlockResponse) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2457,7 +2478,10 @@ func (m *ParamsRequest) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2559,7 +2583,10 @@ func (m *ParamsResponse) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -59,4 +59,4 @@ message ParamsRequest {
|
||||
message ParamsResponse {
|
||||
uint64 height = 1;
|
||||
tendermint.types.ConsensusParams consensus_params = 2 [(gogoproto.nullable) = false];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,7 +389,10 @@ func (m *Block) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthBlock
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthBlock
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -775,7 +775,10 @@ func (m *CanonicalBlockID) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthCanonical
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthCanonical
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -878,7 +881,10 @@ func (m *CanonicalPartSetHeader) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthCanonical
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthCanonical
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1087,7 +1093,10 @@ func (m *CanonicalProposal) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthCanonical
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthCanonical
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1277,7 +1286,10 @@ func (m *CanonicalVote) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthCanonical
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthCanonical
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -285,7 +285,10 @@ func (m *EventDataRoundState) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthEvents
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthEvents
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -825,7 +825,10 @@ func (m *Evidence) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthEvidence
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthEvidence
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1018,7 +1021,10 @@ func (m *DuplicateVoteEvidence) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthEvidence
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthEvidence
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1209,7 +1215,10 @@ func (m *LightClientAttackEvidence) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthEvidence
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthEvidence
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1293,7 +1302,10 @@ func (m *EvidenceList) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthEvidence
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthEvidence
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -1123,7 +1123,10 @@ func (m *ConsensusParams) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthParams
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthParams
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1211,7 +1214,10 @@ func (m *BlockParams) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthParams
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthParams
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1332,7 +1338,10 @@ func (m *EvidenceParams) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthParams
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthParams
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1414,7 +1423,10 @@ func (m *ValidatorParams) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthParams
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthParams
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1483,7 +1495,10 @@ func (m *VersionParams) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthParams
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthParams
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -1571,7 +1586,10 @@ func (m *HashedParams) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthParams
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthParams
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -2267,7 +2267,10 @@ func (m *PartSetHeader) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2403,7 +2406,10 @@ func (m *Part) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -2520,7 +2526,10 @@ func (m *BlockID) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -3026,7 +3035,10 @@ func (m *Header) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -3108,7 +3120,10 @@ func (m *Data) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -3368,7 +3383,10 @@ func (m *Vote) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -3523,7 +3541,10 @@ func (m *Commit) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -3693,7 +3714,10 @@ func (m *CommitSig) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -3919,7 +3943,10 @@ func (m *Proposal) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -4041,7 +4068,10 @@ func (m *SignedHeader) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -4163,7 +4193,10 @@ func (m *LightBlock) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -4317,7 +4350,10 @@ func (m *BlockMeta) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -4471,7 +4507,10 @@ func (m *TxProof) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -583,7 +583,10 @@ func (m *ValidatorSet) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthValidator
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthValidator
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -738,7 +741,10 @@ func (m *Validator) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthValidator
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthValidator
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
@@ -843,7 +849,10 @@ func (m *SimpleValidator) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthValidator
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthValidator
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -265,7 +265,10 @@ func (m *Consensus) Unmarshal(dAtA []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
buf generate --path proto/tendermint
|
||||
|
||||
mv ./proto/tendermint/abci/types.pb.go ./abci/types
|
||||
|
||||
mv ./proto/tendermint/rpc/grpc/types.pb.go ./rpc/grpc
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
---
|
||||
order: 1
|
||||
title: Overview
|
||||
parent:
|
||||
title: Spec
|
||||
order: 7
|
||||
---
|
||||
|
||||
# Tendermint Spec
|
||||
|
||||
This directory hosts the canonical Markdown specifications of the Tendermint Protocol.
|
||||
|
||||
It shall be used to describe protocol semantics, namely the BFT consensus engine, leader election, block propagation and light client verification. The specification includes encoding descriptions used in interprocess communication to comply with the protocol. It defines the interface between the application and Tendermint. The english specifications are often accompanies with a TLA+ specification.
|
||||
|
||||
## 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
|
||||
- [Write-Ahead Log](./consensus/wal.md): Details about how the consensus
|
||||
engine preserves data and recovers from crash failures
|
||||
|
||||
## Contibuting
|
||||
|
||||
Contributions are welcome.
|
||||
|
||||
Proposals at an early stage can first be drafted as Github issues. To progress, a proposal will often need to be written out and approved as a [Request For Comment (RFC)](../docs/rfc/README.md).
|
||||
|
||||
The standard language for coding blocks is Golang.
|
||||
|
||||
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)!
|
||||
|
||||
## 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,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](https://github.com/tendermint/spec/blob/master/proto/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,775 @@
|
||||
---
|
||||
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](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/)
|
||||
|
||||
## 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](../spec/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](../spec/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](../spec/abci/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](../spec/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](../spec/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 |
|
||||
|
||||
### 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](../spec/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,671 @@
|
||||
---
|
||||
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/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/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](https://github.com/tendermint/tendermint/blob/master/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](https://github.com/tendermint/tendermint/blob/master/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,3 @@
|
||||
# Blockchain
|
||||
|
||||
Deprecated see [core/data_structures.md](../core/data_structures.md)
|
||||
@@ -0,0 +1,3 @@
|
||||
# Encoding
|
||||
|
||||
Deprecated see [core/data_structures.md](../core/encoding.md)
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
order: 1
|
||||
parent:
|
||||
title: Blockchain
|
||||
order: false
|
||||
---
|
||||
|
||||
# Blockchain
|
||||
|
||||
This section describes the core types and functionality of the Tendermint protocol implementation.
|
||||
|
||||
[Core Data Structures](../core/data_structures.md)
|
||||
[Encoding](../core/encoding.md)
|
||||
[State](../core/state.md)
|
||||
@@ -0,0 +1,3 @@
|
||||
# State
|
||||
|
||||
Deprecated see [core/state.md](../core/state.md)
|
||||
@@ -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.
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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}.
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
% ---------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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.md).
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
@@ -0,0 +1,3 @@
|
||||
# Detection
|
||||
|
||||
Deprecated, please see [light-client/detection](../../light-client/detection.md).
|
||||
@@ -0,0 +1,3 @@
|
||||
# Core Verification
|
||||
|
||||
Deprecated, please see [light-client/accountability](../../light-client/verification.md).
|
||||
@@ -0,0 +1,20 @@
|
||||
# Proposer-Based Timestamps
|
||||
|
||||
This section describes a version of the Tendermint consensus protocol,
|
||||
which uses proposer-based timestamps.
|
||||
|
||||
## Contents
|
||||
|
||||
- [Proposer-Based Time][main] (entry point)
|
||||
- [Part I - System Model and Properties][sysmodel]
|
||||
- [Part II - Protocol Specification][algorithm]
|
||||
- [TLA+ Specification][proposertla]
|
||||
|
||||
|
||||
[algorithm]: ./pbts-algorithm_001_draft.md
|
||||
|
||||
[sysmodel]: ./pbts-sysmodel_001_draft.md
|
||||
|
||||
[main]: ./pbts_001_draft.md
|
||||
|
||||
[proposertla]: ./tla/TendermintPBT_001_draft.tla
|
||||
@@ -0,0 +1,163 @@
|
||||
# Proposer-Based Time - Part II
|
||||
|
||||
## Updated Consensus Algorithm
|
||||
|
||||
### Outline
|
||||
|
||||
The algorithm in the [arXiv paper][arXiv] evaluates rules of the received messages without making explicit how these messages are received. In our solution, we will make some message filtering explicit. We will assume that there are message reception steps (where messages are received and possibly stored locally for later evaluation of rules) and processing steps (the latter roughly as described in a way similar to the pseudo code of the arXiv paper).
|
||||
|
||||
In contrast to the original algorithm the field `proposal` in the `PROPOSE` message is a pair `(v, time)`, of the proposed consensus value `v` and the proposed time `time`.
|
||||
|
||||
#### **[PBTS-RECEPTION-STEP.0]**
|
||||
|
||||
In the reception step at process `p` at local time `now_p`, upon receiving a message `m`:
|
||||
|
||||
- if the message `m` is of type `PROPOSE` and satisfies `now_p - PRECISION < m.time < now_p + PRECISION + MSGDELAY`, then mark the message as `timely`
|
||||
|
||||
> if `m` does not satisfy the constraint consider it `untimely`
|
||||
|
||||
|
||||
#### **[PBTS-PROCESSING-STEP.0]**
|
||||
|
||||
In the processing step, based on the messages stored, the rules of the algorithms are
|
||||
executed. Note that the processing step only operates on messages
|
||||
for the current height. The consensus algorithm rules are defined by the following updates to arXiv paper.
|
||||
|
||||
#### New `StartRound`
|
||||
|
||||
There are two additions
|
||||
|
||||
- in case the proposer's local time is smaller than the time of the previous block, the proposer waits until this is not the case anymore (to ensure the block time is monotonically increasing)
|
||||
- the proposer sends its time `now_p` as part of its proposal
|
||||
|
||||
We update the timeout for the `PROPOSE` step according to the following reasoning:
|
||||
|
||||
- If a correct proposer needs to wait to make sure its proposed time is larger than the `blockTime` of the previous block, then it sends by realtime `blockTime + ACCURACY` (By this time, its local clock must exceed `blockTime`)
|
||||
- the receiver will receive a `PROPOSE` message by `blockTime + ACCURACY + MSGDELAY`
|
||||
- the receiver's local clock will be `<= blockTime + 2 * ACCURACY + MSGDELAY`
|
||||
- thus when the receiver `p` enters this round it can set its timeout to a value `waitingTime => blockTime + 2 * ACCURACY + MSGDELAY - now_p`
|
||||
|
||||
So we should set the timeout to `max(timeoutPropose(round_p), waitingTime)`.
|
||||
|
||||
> If, in the future, a block delay parameter `BLOCKDELAY` is introduced, this means
|
||||
that the proposer should wait for `now_p > blockTime + BLOCKDELAY` before sending a `PROPOSE` message.
|
||||
Also, `BLOCKDELAY` needs to be added to `waitingTime`.
|
||||
|
||||
#### **[PBTS-ALG-STARTROUND.0]**
|
||||
|
||||
```go
|
||||
function StartRound(round) {
|
||||
blockTime ← block time of block h_p - 1
|
||||
waitingTime ← blockTime + 2 * ACCURACY + MSGDELAY - now_p
|
||||
round_p ← round
|
||||
step_p ← propose
|
||||
if proposer(h_p, round_p) = p {
|
||||
wait until now_p > blockTime // new wait condition
|
||||
if validValue_p != nil {
|
||||
proposal ← (validValue_p, now_p) // added "now_p"
|
||||
}
|
||||
else {
|
||||
proposal ← (getValue(), now_p) // added "now_p"
|
||||
}
|
||||
broadcast ⟨PROPOSAL, h_p, round_p, proposal, validRound_p⟩
|
||||
}
|
||||
else {
|
||||
schedule OnTimeoutPropose(h_p,round_p) to be executed after max(timeoutPropose(round_p), waitingTime)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### New Rule Replacing Lines 22 - 27
|
||||
|
||||
- a validator prevotes for the consensus value `v` **and** the time `t`
|
||||
- the code changes as the `PROPOSAL` message carries time (while `lockedValue` does not)
|
||||
|
||||
#### **[PBTS-ALG-UPON-PROP.0]**
|
||||
|
||||
```go
|
||||
upon timely(⟨PROPOSAL, h_p, round_p, (v,t), −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,t)⟩
|
||||
}
|
||||
else {
|
||||
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
|
||||
}
|
||||
step_p ← prevote
|
||||
}
|
||||
```
|
||||
|
||||
#### New Rule Replacing Lines 28 - 33
|
||||
|
||||
In case consensus is not reached in round 1, in `StartRound` the proposer of future rounds may propose the same value but with a different time.
|
||||
Thus, the time `tprop` in the `PROPOSAL` message need not match the time `tvote` in the (old) `PREVOTE` messages.
|
||||
A validator may send `PREVOTE` for the current round as long as the value `v` matches.
|
||||
This gives the following rule:
|
||||
|
||||
#### **[PBTS-ALG-OLD-PREVOTE.0]**
|
||||
|
||||
```go
|
||||
upon timely(⟨PROPOSAL, h_p, round_p, (v, tprop), vr⟩) from proposer(h_p, round_p) AND 2f + 1 ⟨PREVOTE, h_p, vr, id((v, tvote)⟩
|
||||
while step_p = propose ∧ (vr ≥ 0 ∧ vr < round_p) do {
|
||||
if valid(v) ∧ (lockedRound_p ≤ vr ∨ lockedValue_p = v) {
|
||||
broadcast ⟨PREVOTE, h_p, roundp, id(v, tprop)⟩
|
||||
}
|
||||
else {
|
||||
broadcast ⟨PREVOTE, hp, roundp, nil⟩
|
||||
}
|
||||
step_p ← prevote
|
||||
}
|
||||
```
|
||||
|
||||
#### New Rule Replacing Lines 36 - 43
|
||||
|
||||
- As above, in the following `(v,t)` is part of the message rather than `v`
|
||||
- the stored values (i.e., `lockedValue`, `validValue`) do not contain the time
|
||||
|
||||
#### **[PBTS-ALG-NEW-PREVOTE.0]**
|
||||
|
||||
```go
|
||||
upon timely(⟨PROPOSAL, h_p, round_p, (v,t), ∗⟩) from proposer(h_p, round_p) AND 2f + 1 ⟨PREVOTE, h_p, round_p, id(v,t)⟩ 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,t))⟩
|
||||
step_p ← precommit
|
||||
}
|
||||
validValue_p ← v
|
||||
validRound_p ← round_p
|
||||
}
|
||||
```
|
||||
|
||||
#### New Rule Replacing Lines 49 - 54
|
||||
|
||||
- we decide on `v` as well as on the time from the proposal message
|
||||
- here we do not care whether the proposal was received timely.
|
||||
|
||||
> In particular we need to take care of the case where the proposer is untimely to one correct validator only. We need to ensure that this validator decides if all decide.
|
||||
|
||||
#### **[PBTS-ALG-DECIDE.0]**
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, r, (v,t), ∗⟩ from proposer(h_p, r) AND 2f + 1 ⟨PRECOMMIT, h_p, r, id(v,t)⟩ while decisionp[h_p] = nil do {
|
||||
if valid(v) {
|
||||
decision_p [h_p] = (v,t) // decide on time too
|
||||
h_p ← h_p + 1
|
||||
reset lockedRound_p , lockedValue_p, validRound_p and validValue_p to initial values and empty message log
|
||||
StartRound(0)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**All other rules remains unchanged.**
|
||||
|
||||
Back to [main document][main].
|
||||
|
||||
[main]: ./pbts_001_draft.md
|
||||
|
||||
[arXiv]: https://arxiv.org/abs/1807.04938
|
||||
|
||||
[tlatender]: https://github.com/tendermint/spec/blob/master/rust-spec/tendermint-accountability/README.md
|
||||
|
||||
[bfttime]: https://github.com/tendermint/spec/blob/439a5bcacb5ef6ef1118566d7b0cd68fff3553d4/spec/consensus/bft-time.md
|
||||
|
||||
[lcspec]: https://github.com/tendermint/spec/blob/439a5bcacb5ef6ef1118566d7b0cd68fff3553d4/rust-spec/lightclient/README.md
|
||||
@@ -0,0 +1,198 @@
|
||||
# Proposer-Based Time - Part I
|
||||
|
||||
## System Model
|
||||
|
||||
### Time and Clocks
|
||||
|
||||
#### **[PBTS-CLOCK-NEWTON.0]**
|
||||
|
||||
There is a reference Newtonian real-time `t` (UTC).
|
||||
|
||||
Every correct validator `V` maintains a synchronized clock `C_V` that ensures:
|
||||
|
||||
#### **[PBTS-CLOCK-PRECISION.0]**
|
||||
|
||||
There exists a system parameter `PRECISION` such that for any two correct validators `V` and `W`, and at any real-time `t`,
|
||||
`|C_V(t) - C_W(t)| < PRECISION`
|
||||
|
||||
|
||||
### Message Delays
|
||||
|
||||
We do not want to interfere with the Tendermint timing assumptions. We will postulate a timing restriction, which, if satisfied, ensures that liveness is preserved.
|
||||
|
||||
In general the local clock may drift from the global time. (It may progress faster, e.g., one second of clock time might take 1.005 seconds of real-time). As a result the local clock and the global clock may be measured in different time units. Usually, the message delay is measured in global clock time units. To estimate the correct local timeout precisely, we would need to estimate the clock time duration of a message delay taking into account the clock drift. For simplicity we ignore this, and directly postulate the message delay assumption in terms of local time.
|
||||
|
||||
|
||||
#### **[PBTS-MSG-D.0]**
|
||||
|
||||
There exists a system parameter `MSGDELAY` for message end-to-end delays **counted in clock-time**.
|
||||
|
||||
> Observe that [PBTS-MSG-D.0] imposes constraints on message delays as well as on the clock.
|
||||
|
||||
#### **[PBTS-MSG-FAIR.0]**
|
||||
|
||||
The message end-to-end delay between a correct proposer and a correct validator (for `PROPOSE` messages) is less than `MSGDELAY`.
|
||||
|
||||
|
||||
## 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 pair `(v,t)` of consensus value `v` and time `t`.
|
||||
|
||||
> We then restrict the allowed decisions along the following lines:
|
||||
|
||||
#### **[PBTS-INV-AGREEMENT.0]**
|
||||
|
||||
[Agreement] No two correct validators decide on different values `v`.
|
||||
|
||||
#### **[PBTS-INV-TIME-VAL.0]**
|
||||
|
||||
[Time-Validity] If a correct validator decides on `t` then `t` is "OK" (we will formalize this below), even if up to `2f` validators are faulty.
|
||||
|
||||
However, the properties of Tendermint consensus are of more interest with respect to the blocks, that is, what is written into a block and when. We therefore, in the following, will give the safety and liveness properties from this block-centric viewpoint.
|
||||
For this, observe that the time `t` decided at consensus height `k` will be written in the block of height `k+1`, and will be supported by `2f + 1` `PRECOMMIT` messages of the same consensus round `r`. The time written in the block, we will denote by `b.time` (to distinguish it from the term `bfttime` used for median-based time). For this, it is important to have the following consensus algorithm property:
|
||||
|
||||
#### **[PBTS-INV-TIME-AGR.0]**
|
||||
|
||||
[Time-Agreement] If two correct validators decide in the same round, then they decide on the same `t`.
|
||||
|
||||
#### **[PBTS-DECISION-ROUND.0]**
|
||||
|
||||
Note that the relation between consensus decisions, on the one hand, and blocks, on the other hand, is not immediate; in particular if we consider time: In the proposed solution,
|
||||
as validators may decide in different rounds, they may decide on different times.
|
||||
The proposer of the next block, may pick a commit (at least `2f + 1` `PRECOMMIT` messages from one round), and thus it picks a decision round that is going to become "canonic".
|
||||
As a result, the proposer implicitly has a choice of one of the times that belong to rounds in which validators decided. Observe that this choice was implicitly the case already in the median-based `bfttime`.
|
||||
However, as most consensus instances terminate within one round on the Cosmos hub, this is hardly ever observed in practice.
|
||||
|
||||
|
||||
|
||||
Finally, observe that the agreement ([Agreement] and [Time-Agreement]) properties are based on the Tendermint security model [TMBC-FM-2THIRDS.0] of more than 2/3 correct validators, while [Time-Validity] is based on more than 1/3 correct validators.
|
||||
|
||||
### SAFETY
|
||||
|
||||
Here we will provide specifications that relate local time to block time. However, since we do not assume (by now) that local time is linked to real-time, these specifications also do not provide a relation between block time and real-time. Such properties are given [later](#REAL-TIME-SAFETY).
|
||||
|
||||
For a correct validator `V`, let `beginConsensus(V,k)` be the local time when it sets its height to `k`, and let `endConsensus(V,k)` be the time when it sets its height to `k + 1`.
|
||||
|
||||
Let
|
||||
|
||||
- `beginConsensus(k)` be the minimum over `beginConsensus(V,k)`, and
|
||||
- `last-beginConsensus(k)` be the maximum over `beginConsensus(V,k)`, and
|
||||
- `endConsensus(k)` the maximum over `endConsensus(V,k)`
|
||||
|
||||
for all correct validators `V`.
|
||||
|
||||
> Observe that `beginConsensus(k) <= last-beginConsensus(k)` and if local clocks are monotonic, then `last-beginConsensus(k) <= endConsensus(k)`.
|
||||
|
||||
#### **[PBTS-CLOCK-GROW.0]**
|
||||
|
||||
We assume that during one consensus instance, local clocks are not set back, in particular for each correct validator `V` and each height `k`, we have `beginConsensus(V,k) < endConsensus(V,k)`.
|
||||
|
||||
|
||||
#### **[PBTS-CONSENSUS-TIME-VALID.0]**
|
||||
|
||||
If
|
||||
|
||||
- there is a valid commit `c` for height `k`, and
|
||||
- `c` contains a `PRECOMMIT` message by at least one correct validator,
|
||||
|
||||
then the time `b.time` in the block `b` that is signed by `c` satisfies
|
||||
|
||||
- `beginConsensus(k) - PRECISION <= b.time < endConsensus(k) + PRECISION + MSGDELAY`.
|
||||
|
||||
|
||||
> [PBTS-CONSENSUS-TIME-VALID.0] is based on an analysis where the proposer is faulty (and does does not count towards `beginConsensus(k)` and `endConsensus(k)`), and we estimate the times at which correct validators receive and `accept` the `propose` message. If the proposer is correct we obtain
|
||||
|
||||
#### **[PBTS-CONSENSUS-LIVE-VALID-CORR-PROP.0]**
|
||||
|
||||
If the proposer of round 1 is correct, and
|
||||
|
||||
- [TMBC-FM-2THIRDS.0] holds for a block of height `k - 1`, and
|
||||
- [PBTS-MSG-FAIR.0], and
|
||||
- [PBTS-CLOCK-PRECISION.0], and
|
||||
- [PBTS-CLOCK-GROW.0] (**TODO:** is that enough?)
|
||||
|
||||
then eventually (within bounded time) every correct validator decides in round 1.
|
||||
|
||||
#### **[PBTS-CONSENSUS-SAFE-VALID-CORR-PROP.0]**
|
||||
|
||||
If the proposer of round 1 is correct, and
|
||||
|
||||
- [TMBC-FM-2THIRDS.0] holds for a block of height `k - 1`, and
|
||||
- [PBTS-MSG-FAIR.0], and
|
||||
- [PBTS-CLOCK-PRECISION.0], and
|
||||
- [PBTS-CLOCK-GROW.0] (**TODO:** is that enough?)
|
||||
|
||||
then `beginConsensus_k <= b.time <= last-beginConsensus_k`.
|
||||
|
||||
|
||||
> For the above two properties we will assume that a correct proposer `v` sends its `PROPOSAL` at its local time `beginConsensus(v,k)`.
|
||||
|
||||
### LIVENESS
|
||||
|
||||
If
|
||||
|
||||
- [TMBC-FM-2THIRDS.0] holds for a block of height `k - 1`, and
|
||||
- [PBTS-MSG-FAIR.0],
|
||||
- [PBTS-CLOCK.0], and
|
||||
- [PBTS-CLOCK-GROW.0] (**TODO:** is that enough?)
|
||||
|
||||
then eventually there is a valid commit `c` for height `k`.
|
||||
|
||||
|
||||
### REAL-TIME SAFETY
|
||||
|
||||
> We want to give a property that can be exploited from the outside, that is, given a block with some time stored in it, what is the estimate at which real-time the block was generated. To do so, we need to link clock-time to real-time; which is not the case with [PBTS-CLOCK.0]. For this, we introduce the following assumption on the clocks:
|
||||
|
||||
#### **[PBTS-CLOCKSYNC-EXTERNAL.0]**
|
||||
|
||||
There is a system parameter `ACCURACY`, such that for all real-times `t` and all correct validators `V`,
|
||||
|
||||
- `| C_V(t) - t | < ACCURACY`.
|
||||
|
||||
> `ACCURACY` is not necessarily visible at the code level. The properties below just show that the smaller
|
||||
its value, the closer the block time will be to real-time
|
||||
|
||||
#### **[PBTS-CONSENSUS-PTIME.0]**
|
||||
|
||||
LET `m` be a propose message. We consider the following two real-times `proposalTime(m)` and `propRecvTime(m)`:
|
||||
|
||||
- if the proposer is correct and sends `m` at time `t`, we write `proposalTime(m)` for real-time `t`.
|
||||
- if first correct validator receives `m` at time `t`, we write `propRecvTime(m)` for real-time `t`.
|
||||
|
||||
|
||||
#### **[PBTS-CONSENSUS-REALTIME-VALID.0]**
|
||||
|
||||
Let `b` be a block with a valid commit that contains at least one `precommit` message by a correct validator (and `proposalTime` is the time for the height/round `propose` message `m` that triggered the `precommit`). Then:
|
||||
|
||||
`propRecvTime(m) - ACCURACY - PRECISION < b.time < propRecvTime(m) + ACCURACY + PRECISION + MSGDELAY`
|
||||
|
||||
|
||||
#### **[PBTS-CONSENSUS-REALTIME-VALID-CORR.0]**
|
||||
|
||||
Let `b` be a block with a valid commit that contains at least one `precommit` message by a correct validator (and `proposalTime` is the time for the height/round `propose` message `m` that triggered the `precommit`). Then, if the proposer is correct:
|
||||
|
||||
`proposalTime(m) - ACCURACY < b.time < proposalTime(m) + ACCURACY`
|
||||
|
||||
> by the algorithm at time `proposalTime(m)` the proposer fixes `m.time <- now_p(proposalTime(m))`
|
||||
|
||||
> "triggered the `PRECOMMIT`" implies that the data in `m` and `b` are "matching", that is, `m` proposed the values that are actually stored in `b`.
|
||||
|
||||
Back to [main document][main].
|
||||
|
||||
[main]: ./pbts_001_draft.md
|
||||
|
||||
[arXiv]: https://arxiv.org/abs/1807.04938
|
||||
|
||||
[tlatender]: https://github.com/tendermint/spec/blob/master/rust-spec/tendermint-accountability/README.md
|
||||
|
||||
[bfttime]: https://github.com/tendermint/spec/blob/439a5bcacb5ef6ef1118566d7b0cd68fff3553d4/spec/consensus/bft-time.md
|
||||
|
||||
[lcspec]: https://github.com/tendermint/spec/blob/439a5bcacb5ef6ef1118566d7b0cd68fff3553d4/rust-spec/lightclient/README.md
|
||||
|
||||
[algorithm]: ./pbts-algorithm_001_draft.md
|
||||
|
||||
[sysmodel]: ./pbts-sysmodel_001_draft.md
|
||||
@@ -0,0 +1,270 @@
|
||||
# Proposer-Based Time
|
||||
|
||||
## Current BFTTime
|
||||
|
||||
### Description
|
||||
|
||||
In Tendermint consensus, the first version of how time is computed and stored in a block works as follows:
|
||||
|
||||
- validators send their current local time as part of `precommit` messages
|
||||
- upon collecting the `precommit` messages that the proposer uses to build a commit to be put in the next block, the proposer computes the `time` of the next block as the median (weighted over voting power) of the times in the `precommit` messages.
|
||||
|
||||
### Analysis
|
||||
|
||||
1. **Fault tolerance.** The computed median time is called [`bfttime`][bfttime] as it is indeed fault-tolerant: if **less than a third** of the validators is faulty (counted in voting power), it is guaranteed that the computed time lies between the minimum and the maximum times sent by correct validators.
|
||||
1. **Effect of faulty validators.** If more than `1/2` of the voting power (which is in fact more than one third and less than two thirds of the voting power) is held by faulty validators, then the time is under total control of the faulty validators. (This is particularly challenging in the context of [lightclient][lcspec] security.)
|
||||
1. **Proposer influence on block time.** The proposer of the next block has a degree of freedom in choosing the `bfttime`, since it computes the median time based on the timestamps from `precommit` messages sent by
|
||||
`2f + 1` correct validators.
|
||||
1. If there are `n` different timestamps in the `precommit` messages, the proposer can use any subset of timestamps that add up to `2f + 1`
|
||||
of the voting power in order to compute the median.
|
||||
1. If the validators decide in different rounds, the proposer can decide on which round the median computation is based.
|
||||
1. **Liveness.** The liveness of the protocol:
|
||||
1. does not depend on clock synchronization,
|
||||
1. depends on bounded message delays.
|
||||
1. **Relation to real time.** There is no clock synchronizaton, which implies that there is **no relation** between the computed block `time` and real time.
|
||||
1. **Aggregate signatures.** As the `precommit` messages contain the local times, all these `precommit` messages typically differ in the time field, which **prevents** the use of aggregate signatures.
|
||||
|
||||
## Suggested Proposer-Based Time
|
||||
|
||||
### Outline
|
||||
|
||||
An alternative approach to time has been discussed: Rather than having the validators send the time in the `precommit` messages, the proposer in the consensus algorithm sends its time in the `propose` message, and the validators locally check whether the time is OK (by comparing to their local clock).
|
||||
|
||||
This proposed solution adds the requirement of having synchronized clocks, and other implicit assumptions.
|
||||
|
||||
### Comparison of the Suggested Method to the Old One
|
||||
|
||||
1. **Fault tolerance.** Maintained in the suggested protocol.
|
||||
1. **Effect of faulty validators.** Eliminated in the suggested protocol,
|
||||
that is, the block `time` can be corrupted only in the extreme case when
|
||||
`>2/3` of the validators are faulty.
|
||||
1. **Proposer influence on block time.** The proposer of the next block
|
||||
has less freedom when choosing the block time.
|
||||
1. This scenario is eliminated in the suggested protocol, provided that there are `<1/3` faulty validators.
|
||||
1. This scenario is still there.
|
||||
1. **Liveness.** The liveness of the suggested protocol:
|
||||
1. depends on the introduced assumptions on synchronized clocks (see below),
|
||||
1. still depends on the message delays (unavoidable).
|
||||
1. **Relation to real time.** We formalize clock synchronization, and obtain a **well-defined relation** between the block `time` and real time.
|
||||
1. **Aggregate signatures.** The `precommit` messages free of time, which **allows** for aggregate signatures.
|
||||
|
||||
### Protocol Overview
|
||||
|
||||
#### Proposed Time
|
||||
|
||||
We assume that the field `proposal` in the `PROPOSE` message is a pair `(v, time)`, of the proposed consensus value `v` and the proposed time `time`.
|
||||
|
||||
#### Reception Step
|
||||
|
||||
In the reception step at node `p` at local time `now_p`, upon receiving a message `m`:
|
||||
|
||||
- **if** the message `m` is of type `PROPOSE` and satisfies `now_p - PRECISION < m.time < now_p + PRECISION + MSGDELAY`, then mark the message as `timely`.
|
||||
(`PRECISION` and `MSGDELAY` being system parameters, see [below](#safety-and-liveness))
|
||||
|
||||
> after the presentation in the dev session, we realized that different semantics for the reception step is closer aligned to the implementation. Instead of dropping propose messages, we keep all of them, and mark timely ones.
|
||||
|
||||
#### Processing Step
|
||||
|
||||
- Start round
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>arXiv paper</th>
|
||||
<th>Proposer-based time</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```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)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
```go
|
||||
function StartRound(round) {
|
||||
round_p ← round
|
||||
step_p ← propose
|
||||
if proposer(h_p, round_p) = p {
|
||||
// new wait condition
|
||||
wait until now_p > block time of block h_p - 1
|
||||
if validValue_p != nil {
|
||||
// add "now_p"
|
||||
proposal ← (validValue_p, now_p)
|
||||
} else {
|
||||
// add "now_p"
|
||||
proposal ← (getValue(), now_p)
|
||||
}
|
||||
broadcast ⟨PROPOSAL, h_p, round_p, proposal, validRound_p⟩
|
||||
} else {
|
||||
schedule OnTimeoutPropose(h_p,round_p) to
|
||||
be executed after timeoutPropose(round_p)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
- Rule on lines 28-35
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>arXiv paper</th>
|
||||
<th>Proposer-based time</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```go
|
||||
upon timely(⟨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, hp, round_p, nil⟩
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
```go
|
||||
upon timely(⟨PROPOSAL, h_p, round_p, (v, tprop), vr⟩)
|
||||
from proposer(h_p, round_p)
|
||||
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v, tvote)⟩
|
||||
while step_p = propose ∧ (vr ≥ 0 ∧ vr < round_p) do {
|
||||
if valid(v) ∧ (lockedRound_p ≤ vr ∨ lockedValue_p = v) {
|
||||
// send hash of v and tprop in PREVOTE message
|
||||
broadcast ⟨PREVOTE, h_p, round_p, id(v, tprop)⟩
|
||||
} else {
|
||||
broadcast ⟨PREVOTE, hp, round_p, nil⟩
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
- Rule on lines 49-54
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>arXiv paper</th>
|
||||
<th>Proposer-based time</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, r, v, ∗⟩ from proposer(h_p, r)
|
||||
AND 2f + 1 ⟨PRECOMMIT, h_p, r, id(v)⟩
|
||||
while decisionp[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 and empty message log
|
||||
StartRound(0)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, r, (v,t), ∗⟩ from proposer(h_p, r)
|
||||
AND 2f + 1 ⟨PRECOMMIT, h_p, r, id(v,t)⟩
|
||||
while decisionp[h_p] = nil do {
|
||||
if valid(v) {
|
||||
// decide on time too
|
||||
decision_p [h_p] = (v,t)
|
||||
h_p ← h_p + 1
|
||||
reset lockedRound_p , lockedValue_p, validRound_p and
|
||||
validValue_p to initial values and empty message log
|
||||
StartRound(0)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
- Other rules are extended in a similar way, or remain unchanged
|
||||
|
||||
### Property Overview
|
||||
|
||||
#### Safety and Liveness
|
||||
|
||||
For safety (Point 1, Point 2, Point 3i) and liveness (Point 4) we need
|
||||
the following assumptions:
|
||||
|
||||
- There exists a system parameter `PRECISION` such that for any two correct validators `V` and `W`, and at any real-time `t`, their local times `C_V(t)` and `C_W(t)` differ by less than `PRECISION` time units,
|
||||
i.e., `|C_V(t) - C_W(t)| < PRECISION`
|
||||
- The message end-to-end delay between a correct proposer and a correct validator (for `PROPOSE` messages) is less than `MSGDELAY`.
|
||||
|
||||
#### Relation to Real-Time
|
||||
|
||||
For analyzing real-time safety (Point 5), we use a system parameter `ACCURACY`, such that for all real-times `t` and all correct validators `V`, we have `| C_V(t) - t | < ACCURACY`.
|
||||
|
||||
> `ACCURACY` is not necessarily visible at the code level. We might even view `ACCURACY` as variable over time. The smaller it is during a consensus instance, the closer the block time will be to real-time.
|
||||
>
|
||||
> Note that `PRECISION` and `MSGDELAY` show up in the code.
|
||||
|
||||
### Detailed Specification
|
||||
|
||||
This specification describes the changes needed to be done to the Tendermint consensus algorithm as described in the [arXiv paper][arXiv] and the simplified specification in [TLA+][tlatender], and makes precise the underlying assumptions and the required properties.
|
||||
|
||||
- [Part I - System Model and Properties][sysmodel]
|
||||
- [Part II - Protocol specification][algorithm]
|
||||
- [TLA+ Specification][proposertla]
|
||||
|
||||
[arXiv]: https://arxiv.org/abs/1807.04938
|
||||
|
||||
[tlatender]: https://github.com/tendermint/spec/blob/master/rust-spec/tendermint-accountability/README.md
|
||||
|
||||
[bfttime]: https://github.com/tendermint/spec/blob/439a5bcacb5ef6ef1118566d7b0cd68fff3553d4/spec/consensus/bft-time.md
|
||||
|
||||
[lcspec]: https://github.com/tendermint/spec/blob/439a5bcacb5ef6ef1118566d7b0cd68fff3553d4/rust-spec/lightclient/README.md
|
||||
|
||||
[algorithm]: ./pbts-algorithm_001_draft.md
|
||||
|
||||
[sysmodel]: ./pbts-sysmodel_001_draft.md
|
||||
|
||||
[main]: ./pbts_001_draft.md
|
||||
|
||||
[proposertla]: ./tla/TendermintPBT_001_draft.tla
|
||||
@@ -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
|
||||
|
||||
=============================================================================
|
||||
@@ -0,0 +1,323 @@
|
||||
---
|
||||
order: 3
|
||||
---
|
||||
|
||||
# Proposer Selection Procedure
|
||||
|
||||
This document specifies the Proposer Selection Procedure that is used in Tendermint to choose a round proposer.
|
||||
As Tendermint is “leader-based protocol”, the proposer selection is critical for its correct functioning.
|
||||
|
||||
At a given block height, the proposer selection algorithm runs with the same validator set at each round .
|
||||
Between heights, an updated validator set may be specified by the application as part of the ABCIResponses' EndBlock.
|
||||
|
||||
## Requirements for Proposer Selection
|
||||
|
||||
This sections covers the requirements with Rx being mandatory and Ox optional requirements.
|
||||
The following requirements must be met by the Proposer Selection procedure:
|
||||
|
||||
### R1: Determinism
|
||||
|
||||
Given a validator set `V`, and two honest validators `p` and `q`, for each height `h` and each round `r` the following must hold:
|
||||
|
||||
`proposer_p(h,r) = proposer_q(h,r)`
|
||||
|
||||
where `proposer_p(h,r)` is the proposer returned by the Proposer Selection Procedure at process `p`, at height `h` and round `r`.
|
||||
|
||||
### R2: Fairness
|
||||
|
||||
Given a validator set with total voting power P and a sequence S of elections. In any sub-sequence of S with length C*P, a validator v must be elected as proposer P/VP(v) times, i.e. with frequency:
|
||||
|
||||
f(v) ~ VP(v) / P
|
||||
|
||||
where C is a tolerance factor for validator set changes with following values:
|
||||
|
||||
- C == 1 if there are no validator set changes
|
||||
- C ~ k when there are validator changes
|
||||
|
||||
*[this needs more work]*
|
||||
|
||||
## Basic Algorithm
|
||||
|
||||
At its core, the proposer selection procedure uses a weighted round-robin algorithm.
|
||||
|
||||
A model that gives a good intuition on how/ why the selection algorithm works and it is fair is that of a priority queue. The validators move ahead in this queue according to their voting power (the higher the voting power the faster a validator moves towards the head of the queue). When the algorithm runs the following happens:
|
||||
|
||||
- all validators move "ahead" according to their powers: for each validator, increase the priority by the voting power
|
||||
- first in the queue becomes the proposer: select the validator with highest priority
|
||||
- move the proposer back in the queue: decrease the proposer's priority by the total voting power
|
||||
|
||||
Notation:
|
||||
|
||||
- vset - the validator set
|
||||
- n - the number of validators
|
||||
- VP(i) - voting power of validator i
|
||||
- A(i) - accumulated priority for validator i
|
||||
- P - total voting power of set
|
||||
- avg - average of all validator priorities
|
||||
- prop - proposer
|
||||
|
||||
Simple view at the Selection Algorithm:
|
||||
|
||||
```md
|
||||
def ProposerSelection (vset):
|
||||
|
||||
// compute priorities and elect proposer
|
||||
for each validator i in vset:
|
||||
A(i) += VP(i)
|
||||
prop = max(A)
|
||||
A(prop) -= P
|
||||
```
|
||||
|
||||
## Stable Set
|
||||
|
||||
Consider the validator set:
|
||||
|
||||
Validator | p1 | p2
|
||||
----------|----|---
|
||||
VP | 1 | 3
|
||||
|
||||
Assuming no validator changes, the following table shows the proposer priority computation over a few runs. Four runs of the selection procedure are shown, starting with the 5th the same values are computed.
|
||||
Each row shows the priority queue and the process place in it. The proposer is the closest to the head, the rightmost validator. As priorities are updated, the validators move right in the queue. The proposer moves left as its priority is reduced after election.
|
||||
|
||||
| Priority Run | -2 | -1 | 0 | 1 | 2 | 3 | 4 | 5 | Alg step |
|
||||
|----------------|----|----|-------|----|-------|----|----|----|------------------|
|
||||
| | | | p1,p2 | | | | | | Initialized to 0 |
|
||||
| run 1 | | | | p1 | | p2 | | | A(i)+=VP(i) |
|
||||
| | | p2 | | p1 | | | | | A(p2)-= P |
|
||||
| run 2 | | | | | p1,p2 | | | | A(i)+=VP(i) |
|
||||
| | p1 | | | | p2 | | | | A(p1)-= P |
|
||||
| run 3 | | p1 | | | | | | p2 | A(i)+=VP(i) |
|
||||
| | | p1 | | p2 | | | | | A(p2)-= P |
|
||||
| run 4 | | | p1 | | | | p2 | | A(i)+=VP(i) |
|
||||
| | | | p1,p2 | | | | | | A(p2)-= P |
|
||||
|
||||
It can be shown that:
|
||||
|
||||
- At the end of each run k+1 the sum of the priorities is the same as at end of run k. If a new set's priorities are initialized to 0 then the sum of priorities will be 0 at each run while there are no changes.
|
||||
- The max distance between priorites is (n-1) *P.*[formal proof not finished]*
|
||||
|
||||
## Validator Set Changes
|
||||
|
||||
Between proposer selection runs the validator set may change. Some changes have implications on the proposer election.
|
||||
|
||||
### Voting Power Change
|
||||
|
||||
Consider again the earlier example and assume that the voting power of p1 is changed to 4:
|
||||
|
||||
Validator | p1 | p2
|
||||
----------|----|---
|
||||
VP | 4 | 3
|
||||
|
||||
Let's also assume that before this change the proposer priorites were as shown in first row (last run). As it can be seen, the selection could run again, without changes, as before.
|
||||
|
||||
| Priority Run | -2 | -1 | 0 | 1 | 2 | Comment |
|
||||
|----------------|----|----|---|----|----|-------------------|
|
||||
| last run | | p2 | | p1 | | __update VP(p1)__ |
|
||||
| next run | | | | | p2 | A(i)+=VP(i) |
|
||||
| | p1 | | | | p2 | A(p1)-= P |
|
||||
|
||||
However, when a validator changes power from a high to a low value, some other validator remain far back in the queue for a long time. This scenario is considered again in the Proposer Priority Range section.
|
||||
|
||||
As before:
|
||||
|
||||
- At the end of each run k+1 the sum of the priorities is the same as at run k.
|
||||
- The max distance between priorites is (n-1) * P.
|
||||
|
||||
### Validator Removal
|
||||
|
||||
Consider a new example with set:
|
||||
|
||||
Validator | p1 | p2 | p3
|
||||
----------|----|----|---
|
||||
VP | 1 | 2 | 3
|
||||
|
||||
Let's assume that after the last run the proposer priorities were as shown in first row with their sum being 0. After p2 is removed, at the end of next proposer selection run (penultimate row) the sum of priorities is -2 (minus the priority of the removed process).
|
||||
|
||||
The procedure could continue without modifications. However, after a sufficiently large number of modifications in validator set, the priority values would migrate towards maximum or minimum allowed values causing truncations due to overflow detection.
|
||||
For this reason, the selection procedure adds another __new step__ that centers the current priority values such that the priority sum remains close to 0.
|
||||
|
||||
| Priority Run | -3 | -2 | -1 | 0 | 1 | 2 | 4 | Comment |
|
||||
|----------------|----|----|----|---|----|----|---|-----------------------|
|
||||
| last run | p3 | | | | p1 | p2 | | __remove p2__ |
|
||||
| nextrun | | | | | | | | |
|
||||
| __new step__ | | p3 | | | | p1 | | A(i) -= avg, avg = -1 |
|
||||
| | | | | | p3 | p1 | | A(i)+=VP(i) |
|
||||
| | | | p1 | | p3 | | | A(p1)-= P |
|
||||
|
||||
The modified selection algorithm is:
|
||||
|
||||
```md
|
||||
def ProposerSelection (vset):
|
||||
|
||||
// center priorities around zero
|
||||
avg = sum(A(i) for i in vset)/len(vset)
|
||||
for each validator i in vset:
|
||||
A(i) -= avg
|
||||
|
||||
// compute priorities and elect proposer
|
||||
for each validator i in vset:
|
||||
A(i) += VP(i)
|
||||
prop = max(A)
|
||||
A(prop) -= P
|
||||
```
|
||||
|
||||
Observations:
|
||||
|
||||
- The sum of priorities is now close to 0. Due to integer division the sum is an integer in (-n, n), where n is the number of validators.
|
||||
|
||||
### New Validator
|
||||
|
||||
When a new validator is added, same problem as the one described for removal appears, the sum of priorities in the new set is not zero. This is fixed with the centering step introduced above.
|
||||
|
||||
One other issue that needs to be addressed is the following. A validator V that has just been elected is moved to the end of the queue. If the validator set is large and/ or other validators have significantly higher power, V will have to wait many runs to be elected. If V removes and re-adds itself to the set, it would make a significant (albeit unfair) "jump" ahead in the queue.
|
||||
|
||||
In order to prevent this, when a new validator is added, its initial priority is set to:
|
||||
|
||||
```md
|
||||
A(V) = -1.125 * P
|
||||
```
|
||||
|
||||
where P is the total voting power of the set including V.
|
||||
|
||||
Curent implementation uses the penalty factor of 1.125 because it provides a small punishment that is efficient to calculate. See [here](https://github.com/tendermint/tendermint/pull/2785#discussion_r235038971) for more details.
|
||||
|
||||
If we consider the validator set where p3 has just been added:
|
||||
|
||||
Validator | p1 | p2 | p3
|
||||
----------|----|----|---
|
||||
VP | 1 | 3 | 8
|
||||
|
||||
then p3 will start with proposer priority:
|
||||
|
||||
```md
|
||||
A(p3) = -1.125 * (1 + 3 + 8) ~ -13
|
||||
```
|
||||
|
||||
Note that since current computation uses integer division there is penalty loss when sum of the voting power is less than 8.
|
||||
|
||||
In the next run, p3 will still be ahead in the queue, elected as proposer and moved back in the queue.
|
||||
|
||||
| Priority Run | -13 | -9 | -5 | -2 | -1 | 0 | 1 | 2 | 5 | 6 | 7 | Alg step |
|
||||
|----------------|-----|----|----|----|----|---|---|----|----|----|----|-----------------------|
|
||||
| last run | | | | p2 | | | | p1 | | | | __add p3__ |
|
||||
| | p3 | | | p2 | | | | p1 | | | | A(p3) = -4 |
|
||||
| next run | | p3 | | | | | | p2 | | p1 | | A(i) -= avg, avg = -4 |
|
||||
| | | | | | p3 | | | | p2 | | p1 | A(i)+=VP(i) |
|
||||
| | | | p1 | | p3 | | | | p2 | | | A(p1)-=P |
|
||||
|
||||
## Proposer Priority Range
|
||||
|
||||
With the introduction of centering, some interesting cases occur. Low power validators that bind early in a set that includes high power validator(s) benefit from subsequent additions to the set. This is because these early validators run through more right shift operations during centering, operations that increase their priority.
|
||||
|
||||
As an example, consider the set where p2 is added after p1, with priority -1.125 * 80k = -90k. After the selection procedure runs once:
|
||||
|
||||
Validator | p1 | p2 | Comment
|
||||
----------|------|------|------------------
|
||||
VP | 80k | 10 |
|
||||
A | 0 | -90k | __added p2__
|
||||
A | -45k | 45k | __run selection__
|
||||
|
||||
Then execute the following steps:
|
||||
|
||||
1. Add a new validator p3:
|
||||
|
||||
Validator | p1 | p2 | p3
|
||||
----------|-----|----|---
|
||||
VP | 80k | 10 | 10
|
||||
|
||||
2. Run selection once. The notation '..p'/'p..' means very small deviations compared to column priority.
|
||||
|
||||
| Priority Run | -90k.. | -60k | -45k | -15k | 0 | 45k | 75k | 155k | Comment |
|
||||
|---------------|--------|------|------|------|---|-----|-----|------|--------------|
|
||||
| last run | p3 | | p2 | | | p1 | | | __added p3__ |
|
||||
| next run
|
||||
| *right_shift*| | p3 | | p2 | | | p1 | | A(i) -= avg,avg=-30k
|
||||
| | | ..p3| | ..p2| | | | p1 | A(i)+=VP(i)
|
||||
| | | ..p3| | ..p2| | | p1.. | | A(p1)-=P, P=80k+20
|
||||
|
||||
3. Remove p1 and run selection once:
|
||||
|
||||
Validator | p3 | p2 | Comment
|
||||
----------|--------|-------|------------------
|
||||
VP | 10 | 10 |
|
||||
A | -60k | -15k |
|
||||
A | -22.5k | 22.5k | __run selection__
|
||||
|
||||
At this point, while the total voting power is 20, the distance between priorities is 45k. It will take 4500 runs for p3 to catch up with p2.
|
||||
|
||||
In order to prevent these types of scenarios, the selection algorithm performs scaling of priorities such that the difference between min and max values is smaller than two times the total voting power.
|
||||
|
||||
The modified selection algorithm is:
|
||||
|
||||
```md
|
||||
def ProposerSelection (vset):
|
||||
|
||||
// scale the priority values
|
||||
diff = max(A)-min(A)
|
||||
threshold = 2 * P
|
||||
if diff > threshold:
|
||||
scale = diff/threshold
|
||||
for each validator i in vset:
|
||||
A(i) = A(i)/scale
|
||||
|
||||
// center priorities around zero
|
||||
avg = sum(A(i) for i in vset)/len(vset)
|
||||
for each validator i in vset:
|
||||
A(i) -= avg
|
||||
|
||||
// compute priorities and elect proposer
|
||||
for each validator i in vset:
|
||||
A(i) += VP(i)
|
||||
prop = max(A)
|
||||
A(prop) -= P
|
||||
```
|
||||
|
||||
Observations:
|
||||
|
||||
- With this modification, the maximum distance between priorites becomes 2 * P.
|
||||
|
||||
Note also that even during steady state the priority range may increase beyond 2 * P. The scaling introduced here helps to keep the range bounded.
|
||||
|
||||
## Wrinkles
|
||||
|
||||
### Validator Power Overflow Conditions
|
||||
|
||||
The validator voting power is a positive number stored as an int64. When a validator is added the `1.125 * P` computation must not overflow. As a consequence the code handling validator updates (add and update) checks for overflow conditions making sure the total voting power is never larger than the largest int64 `MAX`, with the property that `1.125 * MAX` is still in the bounds of int64. Fatal error is return when overflow condition is detected.
|
||||
|
||||
### Proposer Priority Overflow/ Underflow Handling
|
||||
|
||||
The proposer priority is stored as an int64. The selection algorithm performs additions and subtractions to these values and in the case of overflows and underflows it limits the values to:
|
||||
|
||||
```go
|
||||
MaxInt64 = 1 << 63 - 1
|
||||
MinInt64 = -1 << 63
|
||||
```
|
||||
|
||||
## Requirement Fulfillment Claims
|
||||
|
||||
__[R1]__
|
||||
|
||||
The proposer algorithm is deterministic giving consistent results across executions with same transactions and validator set modifications.
|
||||
[WIP - needs more detail]
|
||||
|
||||
__[R2]__
|
||||
|
||||
Given a set of processes with the total voting power P, during a sequence of elections of length P, the number of times any process is selected as proposer is equal to its voting power. The sequence of the P proposers then repeats. If we consider the validator set:
|
||||
|
||||
Validator | p1 | p2
|
||||
----------|----|---
|
||||
VP | 1 | 3
|
||||
|
||||
With no other changes to the validator set, the current implementation of proposer selection generates the sequence:
|
||||
`p2, p1, p2, p2, p2, p1, p2, p2,...` or [`p2, p1, p2, p2`]*
|
||||
A sequence that starts with any circular permutation of the [`p2, p1, p2, p2`] sub-sequence would also provide the same degree of fairness. In fact these circular permutations show in the sliding window (over the generated sequence) of size equal to the length of the sub-sequence.
|
||||
|
||||
Assigning priorities to each validator based on the voting power and updating them at each run ensures the fairness of the proposer selection. In addition, every time a validator is elected as proposer its priority is decreased with the total voting power.
|
||||
|
||||
Intuitively, a process v jumps ahead in the queue at most (max(A) - min(A))/VP(v) times until it reaches the head and is elected. The frequency is then:
|
||||
|
||||
```md
|
||||
f(v) ~ VP(v)/(max(A)-min(A)) = 1/k * VP(v)/P
|
||||
```
|
||||
|
||||
For current implementation, this means v should be proposer at least VP(v) times out of k * P runs, with scaling factor k=2.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
order: 1
|
||||
parent:
|
||||
title: Consensus
|
||||
order: 4
|
||||
---
|
||||
|
||||
# Consensus
|
||||
|
||||
Specification of the Tendermint consensus protocol.
|
||||
|
||||
## Contents
|
||||
|
||||
- [Consensus Paper](./consensus-paper) - Latex paper on
|
||||
[arxiv](https://arxiv.org/abs/1807.04938) describing the
|
||||
core Tendermint consensus state machine with proofs of safety and termination.
|
||||
- [BFT Time](./bft-time.md) - How the timestamp in a Tendermint
|
||||
block header is computed in a Byzantine Fault Tolerant manner
|
||||
- [Creating Proposal](./creating-proposal.md) - How a proposer
|
||||
creates a block proposal for consensus
|
||||
- [Light Client Protocol](./light-client) - A protocol for light weight consensus
|
||||
verification and syncing to the latest state
|
||||
- [Signing](./signing.md) - Rules for cryptographic signatures
|
||||
produced by validators.
|
||||
- [Write Ahead Log](./wal.md) - Write ahead log used by the
|
||||
consensus state machine to recover from crashes.
|
||||
|
||||
The protocol used to gossip consensus messages between peers, which is critical
|
||||
for liveness, is described in the [reactors section](../reactors/consensus/consensus.md).
|
||||
|
||||
There is also a [stale markdown description](consensus.md) of the consensus state machine
|
||||
(TODO update this).
|
||||
@@ -0,0 +1,229 @@
|
||||
# Validator Signing
|
||||
|
||||
Here we specify the rules for validating a proposal and vote before signing.
|
||||
First we include some general notes on validating data structures common to both types.
|
||||
We then provide specific validation rules for each. Finally, we include validation rules to prevent double-sigining.
|
||||
|
||||
## SignedMsgType
|
||||
|
||||
The `SignedMsgType` is a single byte that refers to the type of the message
|
||||
being signed. It is defined in Go as follows:
|
||||
|
||||
```go
|
||||
// SignedMsgType is a type of signed message in the consensus.
|
||||
type SignedMsgType byte
|
||||
|
||||
const (
|
||||
// Votes
|
||||
PrevoteType SignedMsgType = 0x01
|
||||
PrecommitType SignedMsgType = 0x02
|
||||
|
||||
// Proposals
|
||||
ProposalType SignedMsgType = 0x20
|
||||
)
|
||||
```
|
||||
|
||||
All signed messages must correspond to one of these types.
|
||||
|
||||
## Timestamp
|
||||
|
||||
Timestamp validation is subtle and there are currently no bounds placed on the
|
||||
timestamp included in a proposal or vote. It is expected that validators will honestly
|
||||
report their local clock time. The median of all timestamps
|
||||
included in a commit is used as the timestamp for the next block height.
|
||||
|
||||
Timestamps are expected to be strictly monotonic for a given validator, though
|
||||
this is not currently enforced.
|
||||
|
||||
## ChainID
|
||||
|
||||
ChainID is an unstructured string with a max length of 50-bytes.
|
||||
In the future, the ChainID may become structured, and may take on longer lengths.
|
||||
For now, it is recommended that signers be configured for a particular ChainID,
|
||||
and to only sign votes and proposals corresponding to that ChainID.
|
||||
|
||||
## BlockID
|
||||
|
||||
BlockID is the structure used to represent the block:
|
||||
|
||||
```go
|
||||
type BlockID struct {
|
||||
Hash []byte
|
||||
PartsHeader PartSetHeader
|
||||
}
|
||||
|
||||
type PartSetHeader struct {
|
||||
Hash []byte
|
||||
Total int
|
||||
}
|
||||
```
|
||||
|
||||
To be included in a valid vote or proposal, BlockID must either represent a `nil` block, or a complete one.
|
||||
We introduce two methods, `BlockID.IsZero()` and `BlockID.IsComplete()` for these cases, respectively.
|
||||
|
||||
`BlockID.IsZero()` returns true for BlockID `b` if each of the following
|
||||
are true:
|
||||
|
||||
```go
|
||||
b.Hash == nil
|
||||
b.PartsHeader.Total == 0
|
||||
b.PartsHeader.Hash == nil
|
||||
```
|
||||
|
||||
`BlockID.IsComplete()` returns true for BlockID `b` if each of the following
|
||||
are true:
|
||||
|
||||
```go
|
||||
len(b.Hash) == 32
|
||||
b.PartsHeader.Total > 0
|
||||
len(b.PartsHeader.Hash) == 32
|
||||
```
|
||||
|
||||
## Proposals
|
||||
|
||||
The structure of a proposal for signing looks like:
|
||||
|
||||
```go
|
||||
type CanonicalProposal struct {
|
||||
Type SignedMsgType // type alias for byte
|
||||
Height int64 `binary:"fixed64"`
|
||||
Round int64 `binary:"fixed64"`
|
||||
POLRound int64 `binary:"fixed64"`
|
||||
BlockID BlockID
|
||||
Timestamp time.Time
|
||||
ChainID string
|
||||
}
|
||||
```
|
||||
|
||||
A proposal is valid if each of the following lines evaluates to true for proposal `p`:
|
||||
|
||||
```go
|
||||
p.Type == 0x20
|
||||
p.Height > 0
|
||||
p.Round >= 0
|
||||
p.POLRound >= -1
|
||||
p.BlockID.IsComplete()
|
||||
```
|
||||
|
||||
In other words, a proposal is valid for signing if it contains the type of a Proposal
|
||||
(0x20), has a positive, non-zero height, a
|
||||
non-negative round, a POLRound not less than -1, and a complete BlockID.
|
||||
|
||||
## Votes
|
||||
|
||||
The structure of a vote for signing looks like:
|
||||
|
||||
```go
|
||||
type CanonicalVote struct {
|
||||
Type SignedMsgType // type alias for byte
|
||||
Height int64 `binary:"fixed64"`
|
||||
Round int64 `binary:"fixed64"`
|
||||
BlockID BlockID
|
||||
Timestamp time.Time
|
||||
ChainID string
|
||||
}
|
||||
```
|
||||
|
||||
A vote is valid if each of the following lines evaluates to true for vote `v`:
|
||||
|
||||
```go
|
||||
v.Type == 0x1 || v.Type == 0x2
|
||||
v.Height > 0
|
||||
v.Round >= 0
|
||||
v.BlockID.IsZero() || v.BlockID.IsComplete()
|
||||
```
|
||||
|
||||
In other words, a vote is valid for signing if it contains the type of a Prevote
|
||||
or Precommit (0x1 or 0x2, respectively), has a positive, non-zero height, a
|
||||
non-negative round, and an empty or valid BlockID.
|
||||
|
||||
## Invalid Votes and Proposals
|
||||
|
||||
Votes and proposals which do not satisfy the above rules are considered invalid.
|
||||
Peers gossipping invalid votes and proposals may be disconnected from other peers on the network.
|
||||
Note, however, that there is not currently any explicit mechanism to punish validators signing votes or proposals that fail
|
||||
these basic validation rules.
|
||||
|
||||
## Double Signing
|
||||
|
||||
Signers must be careful not to sign conflicting messages, also known as "double signing" or "equivocating".
|
||||
Tendermint has mechanisms to publish evidence of validators that signed conflicting votes, so they can be punished
|
||||
by the application. Note Tendermint does not currently handle evidence of conflciting proposals, though it may in the future.
|
||||
|
||||
### State
|
||||
|
||||
To prevent such double signing, signers must track the height, round, and type of the last message signed.
|
||||
Assume the signer keeps the following state, `s`:
|
||||
|
||||
```go
|
||||
type LastSigned struct {
|
||||
Height int64
|
||||
Round int64
|
||||
Type SignedMsgType // byte
|
||||
}
|
||||
```
|
||||
|
||||
After signing a vote or proposal `m`, the signer sets:
|
||||
|
||||
```go
|
||||
s.Height = m.Height
|
||||
s.Round = m.Round
|
||||
s.Type = m.Type
|
||||
```
|
||||
|
||||
### Proposals
|
||||
|
||||
A signer should only sign a proposal `p` if any of the following lines are true:
|
||||
|
||||
```go
|
||||
p.Height > s.Height
|
||||
p.Height == s.Height && p.Round > s.Round
|
||||
```
|
||||
|
||||
In other words, a proposal should only be signed if it's at a higher height, or a higher round for the same height.
|
||||
Once a proposal or vote has been signed for a given height and round, a proposal should never be signed for the same height and round.
|
||||
|
||||
### Votes
|
||||
|
||||
A signer should only sign a vote `v` if any of the following lines are true:
|
||||
|
||||
```go
|
||||
v.Height > s.Height
|
||||
v.Height == s.Height && v.Round > s.Round
|
||||
v.Height == s.Height && v.Round == s.Round && v.Step == 0x1 && s.Step == 0x20
|
||||
v.Height == s.Height && v.Round == s.Round && v.Step == 0x2 && s.Step != 0x2
|
||||
```
|
||||
|
||||
In other words, a vote should only be signed if it's:
|
||||
|
||||
- at a higher height
|
||||
- at a higher round for the same height
|
||||
- a prevote for the same height and round where we haven't signed a prevote or precommit (but have signed a proposal)
|
||||
- a precommit for the same height and round where we haven't signed a precommit (but have signed a proposal and/or a prevote)
|
||||
|
||||
This means that once a validator signs a prevote for a given height and round, the only other message it can sign for that height and round is a precommit.
|
||||
And once a validator signs a precommit for a given height and round, it must not sign any other message for that same height and round.
|
||||
|
||||
Note this includes votes for `nil`, ie. where `BlockID.IsZero()` is true. If a
|
||||
signer has already signed a vote where `BlockID.IsZero()` is true, it cannot
|
||||
sign another vote with the same type for the same height and round where
|
||||
`BlockID.IsComplete()` is true. Thus only a single vote of a particular type
|
||||
(ie. 0x01 or 0x02) can be signed for the same height and round.
|
||||
|
||||
### Other Rules
|
||||
|
||||
According to the rules of Tendermint consensus, once a validator precommits for
|
||||
a block, they become "locked" on that block, which means they can't prevote for
|
||||
another block unless they see sufficient justification (ie. a polka from a
|
||||
higher round). For more details, see the [consensus
|
||||
spec](https://arxiv.org/abs/1807.04938).
|
||||
|
||||
Violating this rule is known as "amnesia". In contrast to equivocation,
|
||||
which is easy to detect, amnesia is difficult to detect without access to votes
|
||||
from all the validators, as this is what constitutes the justification for
|
||||
"unlocking". Hence, amnesia is not punished within the protocol, and cannot
|
||||
easily be prevented by a signer. If enough validators simultaneously commit an
|
||||
amnesia attack, they may cause a fork of the blockchain, at which point an
|
||||
off-chain protocol must be engaged to collect votes from all the validators and
|
||||
determine who misbehaved. For more details, see [fork
|
||||
detection](https://github.com/tendermint/tendermint/pull/3978).
|
||||
@@ -0,0 +1,32 @@
|
||||
# WAL
|
||||
|
||||
Consensus module writes every message to the WAL (write-ahead log).
|
||||
|
||||
It also issues fsync syscall through
|
||||
[File#Sync](https://golang.org/pkg/os/#File.Sync) for messages signed by this
|
||||
node (to prevent double signing).
|
||||
|
||||
Under the hood, it uses
|
||||
[autofile.Group](https://godoc.org/github.com/tendermint/tmlibs/autofile#Group),
|
||||
which rotates files when those get too big (> 10MB).
|
||||
|
||||
The total maximum size is 1GB. We only need the latest block and the block before it,
|
||||
but if the former is dragging on across many rounds, we want all those rounds.
|
||||
|
||||
## Replay
|
||||
|
||||
Consensus module will replay all the messages of the last height written to WAL
|
||||
before a crash (if such occurs).
|
||||
|
||||
The private validator may try to sign messages during replay because it runs
|
||||
somewhat autonomously and does not know about replay process.
|
||||
|
||||
For example, if we got all the way to precommit in the WAL and then crash,
|
||||
after we replay the proposal message, the private validator will try to sign a
|
||||
prevote. But it will fail. That's ok because we’ll see the prevote later in the
|
||||
WAL. Then it will go to precommit, and that time it will work because the
|
||||
private validator contains the `LastSignBytes` and then we’ll replay the
|
||||
precommit from the WAL.
|
||||
|
||||
Make sure to read about [WAL corruption](https://github.com/tendermint/tendermint/blob/master/docs/tendermint-core/running-in-production.md#wal-corruption)
|
||||
and recovery strategies.
|
||||
@@ -0,0 +1,456 @@
|
||||
# Data Structures
|
||||
|
||||
Here we describe the data structures in the Tendermint blockchain and the rules for validating them.
|
||||
|
||||
The Tendermint blockchains consists of a short list of data types:
|
||||
|
||||
- [Data Structures](#data-structures)
|
||||
- [Block](#block)
|
||||
- [Execution](#execution)
|
||||
- [Header](#header)
|
||||
- [Version](#version)
|
||||
- [BlockID](#blockid)
|
||||
- [PartSetHeader](#partsetheader)
|
||||
- [Part](#part)
|
||||
- [Time](#time)
|
||||
- [Data](#data)
|
||||
- [Commit](#commit)
|
||||
- [CommitSig](#commitsig)
|
||||
- [BlockIDFlag](#blockidflag)
|
||||
- [Vote](#vote)
|
||||
- [CanonicalVote](#canonicalvote)
|
||||
- [Proposal](#proposal)
|
||||
- [SignedMsgType](#signedmsgtype)
|
||||
- [Signature](#signature)
|
||||
- [EvidenceList](#evidencelist)
|
||||
- [Evidence](#evidence)
|
||||
- [DuplicateVoteEvidence](#duplicatevoteevidence)
|
||||
- [LightClientAttackEvidence](#lightclientattackevidence)
|
||||
- [LightBlock](#lightblock)
|
||||
- [SignedHeader](#signedheader)
|
||||
- [ValidatorSet](#validatorset)
|
||||
- [Validator](#validator)
|
||||
- [Address](#address)
|
||||
- [ConsensusParams](#consensusparams)
|
||||
- [BlockParams](#blockparams)
|
||||
- [EvidenceParams](#evidenceparams)
|
||||
- [ValidatorParams](#validatorparams)
|
||||
- [VersionParams](#versionparams)
|
||||
- [Proof](#proof)
|
||||
|
||||
|
||||
## Block
|
||||
|
||||
A block consists of a header, transactions, votes (the commit),
|
||||
and a list of evidence of malfeasance (ie. signing conflicting votes).
|
||||
|
||||
| Name | Type | Description | Validation |
|
||||
|--------|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
|
||||
| Header | [Header](#header) | Header corresponding to the block. This field contains information used throughout consensus and other areas of the protocol. To find out what it contains, visit [header] (#header) | Must adhere to the validation rules of [header](#header) |
|
||||
| Data | [Data](#data) | Data contains a list of transactions. The contents of the transaction is unknown to Tendermint. | This field can be empty or populated, but no validation is performed. Applications can perform validation on individual transactions prior to block creation using [checkTx](../abci/abci.md#checktx).
|
||||
| Evidence | [EvidenceList](#evidence_list) | Evidence contains a list of infractions committed by validators. | Can be empty, but when populated the validations rules from [evidenceList](#evidence_list) apply |
|
||||
| LastCommit | [Commit](#commit) | `LastCommit` includes one vote for every validator. All votes must either be for the previous block, nil or absent. If a vote is for the previous block it must have a valid signature from the corresponding validator. The sum of the voting power of the validators that voted must be greater than 2/3 of the total voting power of the complete validator set. The number of votes in a commit is limited to 10000 (see `types.MaxVotesCount`). | Must be empty for the initial height and must adhere to the validation rules of [commit](#commit). |
|
||||
|
||||
## Execution
|
||||
|
||||
Once a block is validated, it can be executed against the state.
|
||||
|
||||
The state follows this recursive equation:
|
||||
|
||||
```go
|
||||
state(initialHeight) = InitialState
|
||||
state(h+1) <- Execute(state(h), ABCIApp, block(h))
|
||||
```
|
||||
|
||||
where `InitialState` includes the initial consensus parameters and validator set,
|
||||
and `ABCIApp` is an ABCI application that can return results and changes to the validator
|
||||
set (TODO). Execute is defined as:
|
||||
|
||||
```go
|
||||
func Execute(s State, app ABCIApp, block Block) State {
|
||||
// Fuction ApplyBlock executes block of transactions against the app and returns the new root hash of the app state,
|
||||
// modifications to the validator set and the changes of the consensus parameters.
|
||||
AppHash, ValidatorChanges, ConsensusParamChanges := app.ApplyBlock(block)
|
||||
|
||||
nextConsensusParams := UpdateConsensusParams(state.ConsensusParams, ConsensusParamChanges)
|
||||
return State{
|
||||
ChainID: state.ChainID,
|
||||
InitialHeight: state.InitialHeight,
|
||||
LastResults: abciResponses.DeliverTxResults,
|
||||
AppHash: AppHash,
|
||||
InitialHeight: state.InitialHeight,
|
||||
LastValidators: state.Validators,
|
||||
Validators: state.NextValidators,
|
||||
NextValidators: UpdateValidators(state.NextValidators, ValidatorChanges),
|
||||
ConsensusParams: nextConsensusParams,
|
||||
Version: {
|
||||
Consensus: {
|
||||
AppVersion: nextConsensusParams.Version.AppVersion,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Validating a new block is first done prior to the `prevote`, `precommit` & `finalizeCommit` stages.
|
||||
|
||||
The steps to validate a new block are:
|
||||
|
||||
- Check the validity rules of the block and its fields.
|
||||
- Check the versions (Block & App) are the same as in local state.
|
||||
- Check the chainID's match.
|
||||
- Check the height is correct.
|
||||
- Check the `LastBlockID` corresponds to BlockID currently in state.
|
||||
- Check the hashes in the header match those in state.
|
||||
- Verify the LastCommit against state, this step is skipped for the initial height.
|
||||
- This is where checking the signatures correspond to the correct block will be made.
|
||||
- Make sure the proposer is part of the validator set.
|
||||
- Validate bock time.
|
||||
- Make sure the new blocks time is after the previous blocks time.
|
||||
- Calculate the medianTime and check it against the blocks time.
|
||||
- If the blocks height is the initial height then check if it matches the genesis time.
|
||||
- Validate the evidence in the block. Note: Evidence can be empty
|
||||
|
||||
## Header
|
||||
|
||||
A block header contains metadata about the block and about the consensus, as well as commitments to
|
||||
the data in the current block, the previous block, and the results returned by the application:
|
||||
|
||||
| Name | Type | Description | Validation |
|
||||
|-------------------|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Version | [Version](#version) | Version defines the application and protocol version being used. | Must adhere to the validation rules of [Version](#version) |
|
||||
| ChainID | String | ChainID is the ID of the chain. This must be unique to your chain. | ChainID must be less than 50 bytes. |
|
||||
| Height | uint64 | Height is the height for this header. | Must be > 0, >= initialHeight, and == previous Height+1 |
|
||||
| Time | [Time](#time) | The timestamp is equal to the weighted median of validators present in the last commit. Read more on time in the [BFT-time section](../consensus/bft-time.md). Note: the timestamp of a vote must be greater by at least one millisecond than that of the block being voted on. | Time must be >= previous header timestamp + consensus parameters TimeIotaMs. The timestamp of the first block must be equal to the genesis time (since there's no votes to compute the median). |
|
||||
| LastBlockID | [BlockID](#blockid) | BlockID of the previous block. | Must adhere to the validation rules of [blockID](#blockid). The first block has `block.Header.LastBlockID == BlockID{}`. |
|
||||
| LastCommitHash | slice of bytes (`[]byte`) | MerkleRoot of the lastCommit's signatures. The signatures represent the validators that committed to the last block. The first block has an empty slices of bytes for the hash. | Must be of length 32 |
|
||||
| DataHash | slice of bytes (`[]byte`) | MerkleRoot of the hash of transactions. **Note**: The transactions are hashed before being included in the merkle tree, the leaves of the Merkle tree are the hashes, not the transactions themselves. | Must be of length 32 |
|
||||
| ValidatorHash | slice of bytes (`[]byte`) | MerkleRoot of the current validator set. The validators are first sorted by voting power (descending), then by address (ascending) prior to computing the MerkleRoot. | Must be of length 32 |
|
||||
| NextValidatorHash | slice of bytes (`[]byte`) | MerkleRoot of the next validator set. The validators are first sorted by voting power (descending), then by address (ascending) prior to computing the MerkleRoot. | Must be of length 32 |
|
||||
| ConsensusHash | slice of bytes (`[]byte`) | Hash of the protobuf encoded consensus parameters. | Must be of length 32 |
|
||||
| AppHash | slice of bytes (`[]byte`) | Arbitrary byte array returned by the application after executing and commiting the previous block. It serves as the basis for validating any merkle proofs that comes from the ABCI application and represents the state of the actual application rather than the state of the blockchain itself. The first block's `block.Header.AppHash` is given by `ResponseInitChain.app_hash`. | This hash is determined by the application, Tendermint can not perform validation on it. |
|
||||
| LastResultHash | slice of bytes (`[]byte`) | `LastResultsHash` is the root hash of a Merkle tree built from `ResponseDeliverTx` responses (`Log`,`Info`, `Codespace` and `Events` fields are ignored). | Must be of length 32. The first block has `block.Header.ResultsHash == MerkleRoot(nil)`, i.e. the hash of an empty input, for RFC-6962 conformance. |
|
||||
| EvidenceHash | slice of bytes (`[]byte`) | MerkleRoot of the evidence of Byzantine behaviour included in this block. | Must be of length 32 |
|
||||
| ProposerAddress | slice of bytes (`[]byte`) | Address of the original proposer of the block. Validator must be in the current validatorSet. | Must be of length 20 |
|
||||
|
||||
## Version
|
||||
|
||||
NOTE: that this is more specifically the consensus version and doesn't include information like the
|
||||
P2P Version. (TODO: we should write a comprehensive document about
|
||||
versioning that this can refer to)
|
||||
|
||||
| Name | type | Description | Validation |
|
||||
|-------|--------|-----------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------|
|
||||
| Block | uint64 | This number represents the version of the block protocol and must be the same throughout an operational network | Must be equal to protocol version being used in a network (`block.Version.Block == state.Version.Consensus.Block`) |
|
||||
| App | uint64 | App version is decided on by the application. Read [here](../abci/abci.md#info) | `block.Version.App == state.Version.Consensus.App` |
|
||||
|
||||
## BlockID
|
||||
|
||||
The `BlockID` contains two distinct Merkle roots of the block. The `BlockID` includes these two hashes, as well as the number of parts (ie. `len(MakeParts(block))`)
|
||||
|
||||
| Name | Type | Description | Validation |
|
||||
|---------------|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------|
|
||||
| Hash | slice of bytes (`[]byte`) | MerkleRoot of all the fields in the header (ie. `MerkleRoot(header)`. | hash must be of length 32 |
|
||||
| PartSetHeader | [PartSetHeader](#PartSetHeader) | Used for secure gossiping of the block during consensus, is the MerkleRoot of the complete serialized block cut into parts (ie. `MerkleRoot(MakeParts(block))`). | Must adhere to the validation rules of [PartSetHeader](#PartSetHeader) |
|
||||
|
||||
See [MerkleRoot](./encoding.md#MerkleRoot) for details.
|
||||
|
||||
## PartSetHeader
|
||||
|
||||
| Name | Type | Description | Validation |
|
||||
|-------|---------------------------|-----------------------------------|----------------------|
|
||||
| Total | int32 | Total amount of parts for a block | Must be > 0 |
|
||||
| Hash | slice of bytes (`[]byte`) | MerkleRoot of a serialized block | Must be of length 32 |
|
||||
|
||||
## Part
|
||||
|
||||
Part defines a part of a block. In Tendermint blocks are broken into `parts` for gossip.
|
||||
|
||||
| Name | Type | Description | Validation |
|
||||
|-------|-----------------|-----------------------------------|----------------------|
|
||||
| index | int32 | Total amount of parts for a block | Must be > 0 |
|
||||
| bytes | bytes | MerkleRoot of a serialized block | Must be of length 32 |
|
||||
| proof | [Proof](#proof) | MerkleRoot of a serialized block | Must be of length 32 |
|
||||
|
||||
## Time
|
||||
|
||||
Tendermint uses the [Google.Protobuf.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp)
|
||||
format, which uses two integers, one 64 bit integer for Seconds and a 32 bit integer for Nanoseconds.
|
||||
|
||||
## Data
|
||||
|
||||
Data is just a wrapper for a list of transactions, where transactions are arbitrary byte arrays:
|
||||
|
||||
| Name | Type | Description | Validation |
|
||||
|------|----------------------------|------------------------|-----------------------------------------------------------------------------|
|
||||
| Txs | Matrix of bytes ([][]byte) | Slice of transactions. | Validation does not occur on this field, this data is unknown to Tendermint |
|
||||
|
||||
## Commit
|
||||
|
||||
Commit is a simple wrapper for a list of signatures, with one for each validator. It also contains the relevant BlockID, height and round:
|
||||
|
||||
| Name | Type | Description | Validation |
|
||||
|------------|----------------------------------|----------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|
|
||||
| Height | uint64 | Height at which this commit was created. | Must be > 0 |
|
||||
| Round | int32 | Round that the commit corresponds to. | Must be > 0 |
|
||||
| BlockID | [BlockID](#blockid) | The blockID of the corresponding block. | Must adhere to the validation rules of [BlockID](#blockid). |
|
||||
| Signatures | Array of [CommitSig](#commitsig) | Array of commit signatures that correspond to current validator set. | Length of signatures must be > 0 and adhere to the validation of each individual [Commitsig](#commitsig) |
|
||||
|
||||
## CommitSig
|
||||
|
||||
`CommitSig` represents a signature of a validator, who has voted either for nil,
|
||||
a particular `BlockID` or was absent. It's a part of the `Commit` and can be used
|
||||
to reconstruct the vote set given the validator set.
|
||||
|
||||
| Name | Type | Description | Validation |
|
||||
|------------------|-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------|
|
||||
| BlockIDFlag | [BlockIDFlag](#blockidflag) | Represents the validators participation in consensus: Either voted for the block that received the majority, voted for another block, voted nil or did not vote | Must be one of the fields in the [BlockIDFlag](#blockidflag) enum |
|
||||
| ValidatorAddress | [Address](#address) | Address of the validator | Must be of length 20 |
|
||||
| Timestamp | [Time](#time) | This field will vary from `CommitSig` to `CommitSig`. It represents the timestamp of the validator. | [Time](#time) |
|
||||
| Signature | [Signature](#signature) | Signature corresponding to the validators participation in consensus. | The length of the signature must be > 0 and < than 64 |
|
||||
|
||||
NOTE: `ValidatorAddress` and `Timestamp` fields may be removed in the future
|
||||
(see [ADR-25](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-025-commit.md)).
|
||||
|
||||
## BlockIDFlag
|
||||
|
||||
BlockIDFlag represents which BlockID the [signature](#commitsig) is for.
|
||||
|
||||
```go
|
||||
enum BlockIDFlag {
|
||||
BLOCK_ID_FLAG_UNKNOWN = 0;
|
||||
BLOCK_ID_FLAG_ABSENT = 1; // signatures for other blocks are also considered absent
|
||||
BLOCK_ID_FLAG_COMMIT = 2;
|
||||
BLOCK_ID_FLAG_NIL = 3;
|
||||
}
|
||||
```
|
||||
|
||||
## Vote
|
||||
|
||||
A vote is a signed message from a validator for a particular block.
|
||||
The vote includes information about the validator signing it. When stored in the blockchain or propagated over the network, votes are encoded in Protobuf.
|
||||
|
||||
| Name | Type | Description | Validation |
|
||||
|------------------|---------------------------------|---------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|
|
||||
| Type | [SignedMsgType](#signedmsgtype) | Either prevote or precommit. [SignedMsgType](#signedmsgtype) | A Vote is valid if its corresponding fields are included in the enum [signedMsgType](#signedmsgtype) |
|
||||
| Height | uint64 | Height for which this vote was created for | Must be > 0 |
|
||||
| Round | int32 | Round that the commit corresponds to. | Must be > 0 |
|
||||
| BlockID | [BlockID](#blockid) | The blockID of the corresponding block. | [BlockID](#blockid) |
|
||||
| Timestamp | [Time](#Time) | Timestamp represents the time at which a validator signed. | [Time](#time) |
|
||||
| ValidatorAddress | slice of bytes (`[]byte`) | Address of the validator | Length must be equal to 20 |
|
||||
| ValidatorIndex | int32 | Index at a specific block height that corresponds to the Index of the validator in the set. | must be > 0 |
|
||||
| Signature | slice of bytes (`[]byte`) | Signature by the validator if they participated in consensus for the associated bock. | Length of signature must be > 0 and < 64 |
|
||||
|
||||
## CanonicalVote
|
||||
|
||||
CanonicalVote is for validator signing. This type will not be present in a block. Votes are represented via `CanonicalVote` and also encoded using protobuf via `type.SignBytes` which includes the `ChainID`, and uses a different ordering of
|
||||
the fields.
|
||||
|
||||
```proto
|
||||
message CanonicalVote {
|
||||
SignedMsgType type = 1;
|
||||
fixed64 height = 2;
|
||||
sfixed64 round = 3;
|
||||
CanonicalBlockID block_id = 4;
|
||||
google.protobuf.Timestamp timestamp = 5;
|
||||
string chain_id = 6;
|
||||
}
|
||||
```
|
||||
|
||||
For signing, votes are represented via [`CanonicalVote`](#canonicalvote) and also encoded using protobuf via
|
||||
`type.SignBytes` which includes the `ChainID`, and uses a different ordering of
|
||||
the fields.
|
||||
|
||||
We define a method `Verify` that returns `true` if the signature verifies against the pubkey for the `SignBytes`
|
||||
using the given ChainID:
|
||||
|
||||
```go
|
||||
func (vote *Vote) Verify(chainID string, pubKey crypto.PubKey) error {
|
||||
if !bytes.Equal(pubKey.Address(), vote.ValidatorAddress) {
|
||||
return ErrVoteInvalidValidatorAddress
|
||||
}
|
||||
|
||||
if !pubKey.VerifyBytes(types.VoteSignBytes(chainID), vote.Signature) {
|
||||
return ErrVoteInvalidSignature
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Proposal
|
||||
|
||||
Proposal contains height and round for which this proposal is made, BlockID as a unique identifier
|
||||
of proposed block, timestamp, and POLRound (a so-called Proof-of-Lock (POL) round) that is needed for
|
||||
termination of the consensus. If POLRound >= 0, then BlockID corresponds to the block that
|
||||
is locked in POLRound. The message is signed by the validator private key.
|
||||
|
||||
| Name | Type | Description | Validation |
|
||||
|-----------|---------------------------------|---------------------------------------------------------------------------------------|---------------------------------------------------------|
|
||||
| Type | [SignedMsgType](#signedmsgtype) | Represents a Proposal [SignedMsgType](#signedmsgtype) | Must be `ProposalType` [signedMsgType](#signedmsgtype) |
|
||||
| Height | uint64 | Height for which this vote was created for | Must be > 0 |
|
||||
| Round | int32 | Round that the commit corresponds to. | Must be > 0 |
|
||||
| POLRound | int64 | Proof of lock | Must be > 0 |
|
||||
| BlockID | [BlockID](#blockid) | The blockID of the corresponding block. | [BlockID](#blockid) |
|
||||
| Timestamp | [Time](#Time) | Timestamp represents the time at which a validator signed. | [Time](#time) |
|
||||
| Signature | slice of bytes (`[]byte`) | Signature by the validator if they participated in consensus for the associated bock. | Length of signature must be > 0 and < 64 |
|
||||
|
||||
## SignedMsgType
|
||||
|
||||
Signed message type represents a signed messages in consensus.
|
||||
|
||||
```proto
|
||||
enum SignedMsgType {
|
||||
|
||||
SIGNED_MSG_TYPE_UNKNOWN = 0;
|
||||
// Votes
|
||||
SIGNED_MSG_TYPE_PREVOTE = 1;
|
||||
SIGNED_MSG_TYPE_PRECOMMIT = 2;
|
||||
|
||||
// Proposal
|
||||
SIGNED_MSG_TYPE_PROPOSAL = 32;
|
||||
}
|
||||
```
|
||||
|
||||
## Signature
|
||||
|
||||
Signatures in Tendermint are raw bytes representing the underlying signature.
|
||||
|
||||
See the [signature spec](./encoding.md#key-types) for more.
|
||||
|
||||
## EvidenceList
|
||||
|
||||
EvidenceList is a simple wrapper for a list of evidence:
|
||||
|
||||
| Name | Type | Description | Validation |
|
||||
|----------|--------------------------------|----------------------------------------|-----------------------------------------------------------------|
|
||||
| Evidence | Array of [Evidence](#evidence) | List of verified [evidence](#evidence) | Validation adheres to individual types of [Evidence](#evidence) |
|
||||
|
||||
## Evidence
|
||||
|
||||
Evidence in Tendermint is used to indicate breaches in the consensus by a validator.
|
||||
|
||||
More information on how evidence works in Tendermint can be found [here](../consensus/evidence.md)
|
||||
|
||||
### DuplicateVoteEvidence
|
||||
|
||||
`DuplicateVoteEvidence` represents a validator that has voted for two different blocks
|
||||
in the same round of the same height. Votes are lexicographically sorted on `BlockID`.
|
||||
|
||||
| Name | Type | Description | Validation |
|
||||
|------------------|---------------|--------------------------------------------------------------------|-----------------------------------------------------|
|
||||
| VoteA | [Vote](#vote) | One of the votes submitted by a validator when they equivocated | VoteA must adhere to [Vote](#vote) validation rules |
|
||||
| VoteB | [Vote](#vote) | The second vote submitted by a validator when they equivocated | VoteB must adhere to [Vote](#vote) validation rules |
|
||||
| TotalVotingPower | int64 | The total power of the validator set at the height of equivocation | Must be equal to nodes own copy of the data |
|
||||
| ValidatorPower | int64 | Power of the equivocating validator at the height | Must be equal to the nodes own copy of the data |
|
||||
| Timestamp | [Time](#Time) | Time of the block where the equivocation occurred | Must be equal to the nodes own copy of the data |
|
||||
|
||||
### LightClientAttackEvidence
|
||||
|
||||
`LightClientAttackEvidence` is a generalized evidence that captures all forms of known attacks on
|
||||
a light client such that a full node can verify, propose and commit the evidence on-chain for
|
||||
punishment of the malicious validators. There are three forms of attacks: Lunatic, Equivocation
|
||||
and Amnesia. These attacks are exhaustive. You can find a more detailed overview of this [here](../light-client/accountability#the_misbehavior_of_faulty_validators)
|
||||
|
||||
| Name | Type | Description | Validation |
|
||||
|----------------------|------------------------------------|----------------------------------------------------------------------|------------------------------------------------------------------|
|
||||
| ConflictingBlock | [LightBlock](#LightBlock) | Read Below | Must adhere to the validation rules of [lightBlock](#lightblock) |
|
||||
| CommonHeight | int64 | Read Below | must be > 0 |
|
||||
| Byzantine Validators | Array of [Validators](#Validators) | validators that acted maliciously | Read Below |
|
||||
| TotalVotingPower | int64 | The total power of the validator set at the height of the infraction | Must be equal to the nodes own copy of the data |
|
||||
| Timestamp | [Time](#Time) | Time of the block where the infraction occurred | Must be equal to the nodes own copy of the data |
|
||||
|
||||
## LightBlock
|
||||
|
||||
LightBlock is the core data structure of the [light client](../light-client/README.md). It combines two data structures needed for verification ([signedHeader](#signedheader) & [validatorSet](#validatorset)).
|
||||
|
||||
| Name | Type | Description | Validation |
|
||||
|--------------|-------------------------------|----------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|
|
||||
| SignedHeader | [SignedHeader](#signedheader) | The header and commit, these are used for verification purposes. To find out more visit [light client docs](../light-client/README.md) | Must not be nil and adhere to the validation rules of [signedHeader](#signedheader) |
|
||||
| ValidatorSet | [ValidatorSet](#validatorset) | The validatorSet is used to help with verify that the validators in that committed the infraction were truly in the validator set. | Must not be nil and adhere to the validation rules of [validatorSet](#validatorset) |
|
||||
|
||||
The `SignedHeader` and `ValidatorSet` are linked by the hash of the validator set(`SignedHeader.ValidatorsHash == ValidatorSet.Hash()`.
|
||||
|
||||
## SignedHeader
|
||||
|
||||
The SignedhHeader is the [header](#header) accompanied by the commit to prove it.
|
||||
|
||||
| Name | Type | Description | Validation |
|
||||
|--------|-------------------|-------------------|-----------------------------------------------------------------------------------|
|
||||
| Header | [Header](#Header) | [Header](#header) | Header cannot be nil and must adhere to the [Header](#Header) validation criteria |
|
||||
| Commit | [Commit](#commit) | [Commit](#commit) | Commit cannot be nil and must adhere to the [Commit](#commit) criteria |
|
||||
|
||||
## ValidatorSet
|
||||
|
||||
| Name | Type | Description | Validation |
|
||||
|------------|----------------------------------|----------------------------------------------------|-------------------------------------------------------------------------------------------------------------------|
|
||||
| Validators | Array of [validator](#validator) | List of the active validators at a specific height | The list of validators can not be empty or nil and must adhere to the validation rules of [validator](#validator) |
|
||||
| Proposer | [validator](#validator) | The block proposer for the corresponding block | The proposer cannot be nil and must adhere to the validation rules of [validator](#validator) |
|
||||
|
||||
## Validator
|
||||
|
||||
| Name | Type | Description | Validation |
|
||||
|------------------|---------------------------|---------------------------------------------------------------------------------------------------|---------------------------------------------------|
|
||||
| Address | [Address](#address) | Validators Address | Length must be of size 20 |
|
||||
| Pubkey | slice of bytes (`[]byte`) | Validators Public Key | must be a length greater than 0 |
|
||||
| VotingPower | int64 | Validators voting power | cannot be < 0 |
|
||||
| ProposerPriority | int64 | Validators proposer priority. This is used to gauge when a validator is up next to propose blocks | No validation, value can be negative and positive |
|
||||
|
||||
## Address
|
||||
|
||||
Address is a type alias of a slice of bytes. The address is calculated by hashing the public key using sha256 and truncating it to only use the first 20 bytes of the slice.
|
||||
|
||||
```go
|
||||
const (
|
||||
TruncatedSize = 20
|
||||
)
|
||||
|
||||
func SumTruncated(bz []byte) []byte {
|
||||
hash := sha256.Sum256(bz)
|
||||
return hash[:TruncatedSize]
|
||||
}
|
||||
```
|
||||
|
||||
## ConsensusParams
|
||||
|
||||
| Name | Type | Description | Field Number |
|
||||
|-----------|-------------------------------------|------------------------------------------------------------------------------|--------------|
|
||||
| block | [BlockParams](#blockparams) | Parameters limiting the size of a block and time between consecutive blocks. | 1 |
|
||||
| evidence | [EvidenceParams](#evidenceparams) | Parameters limiting the validity of evidence of byzantine behaviour. | 2 |
|
||||
| validator | [ValidatorParams](#validatorparams) | Parameters limiting the types of public keys validators can use. | 3 |
|
||||
| version | [BlockParams](#blockparams) | The ABCI application version. | 4 |
|
||||
|
||||
### BlockParams
|
||||
|
||||
| Name | Type | Description | Field Number |
|
||||
|--------------|-------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
|
||||
| max_bytes | int64 | Max size of a block, in bytes. | 1 |
|
||||
| max_gas | int64 | Max sum of `GasWanted` in a proposed block. NOTE: blocks that violate this may be committed if there are Byzantine proposers. It's the application's responsibility to handle this when processing a block! | 2 |
|
||||
|
||||
### EvidenceParams
|
||||
|
||||
| Name | Type | Description | Field Number |
|
||||
|--------------------|------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
|
||||
| max_age_num_blocks | int64 | Max age of evidence, in blocks. | 1 |
|
||||
| max_age_duration | [google.protobuf.Duration](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Duration) | 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). | 2 |
|
||||
| max_bytes | int64 | maximum size in bytes of total evidence allowed to be entered into a block | 3 |
|
||||
|
||||
### ValidatorParams
|
||||
|
||||
| Name | Type | Description | Field Number |
|
||||
|---------------|-----------------|-----------------------------------------------------------------------|--------------|
|
||||
| pub_key_types | repeated string | List of accepted public key types. Uses same naming as `PubKey.Type`. | 1 |
|
||||
|
||||
### VersionParams
|
||||
|
||||
| Name | Type | Description | Field Number |
|
||||
|-------------|--------|-------------------------------|--------------|
|
||||
| app_version | uint64 | The ABCI application version. | 1 |
|
||||
|
||||
## Proof
|
||||
|
||||
| Name | Type | Description | Field Number |
|
||||
|-----------|----------------|-----------------------------------------------|--------------|
|
||||
| total | int64 | Total number of items. | 1 |
|
||||
| index | int64 | Index item to prove. | 2 |
|
||||
| leaf_hash | bytes | Hash of item value. | 3 |
|
||||
| aunts | repeated bytes | Hashes from leaf's sibling to a root's child. | 4 |
|
||||
@@ -0,0 +1,300 @@
|
||||
# Encoding
|
||||
|
||||
## Protocol Buffers
|
||||
|
||||
Tendermint uses [Protocol Buffers](https://developers.google.com/protocol-buffers), specifically proto3, for all data structures.
|
||||
|
||||
Please see the [Proto3 language guide](https://developers.google.com/protocol-buffers/docs/proto3) for more details.
|
||||
|
||||
## Byte Arrays
|
||||
|
||||
The encoding of a byte array is simply the raw-bytes prefixed with the length of
|
||||
the array as a `UVarint` (what proto calls a `Varint`).
|
||||
|
||||
For details on varints, see the [protobuf
|
||||
spec](https://developers.google.com/protocol-buffers/docs/encoding#varints).
|
||||
|
||||
For example, the byte-array `[0xA, 0xB]` would be encoded as `0x020A0B`,
|
||||
while a byte-array containing 300 entires beginning with `[0xA, 0xB, ...]` would
|
||||
be encoded as `0xAC020A0B...` where `0xAC02` is the UVarint encoding of 300.
|
||||
|
||||
## Hashing
|
||||
|
||||
Tendermint uses `SHA256` as its hash function.
|
||||
Objects are always serialized before being hashed.
|
||||
So `SHA256(obj)` is short for `SHA256(ProtoEncoding(obj))`.
|
||||
|
||||
## Public Key Cryptography
|
||||
|
||||
Tendermint uses Protobuf [Oneof](https://developers.google.com/protocol-buffers/docs/proto3#oneof)
|
||||
to distinguish between different types public keys, and signatures.
|
||||
Additionally, for each public key, Tendermint
|
||||
defines an Address function that can be used as a more compact identifier in
|
||||
place of the public key. Here we list the concrete types, their names,
|
||||
and prefix bytes for public keys and signatures, as well as the address schemes
|
||||
for each PubKey. Note for brevity we don't
|
||||
include details of the private keys beyond their type and name.
|
||||
|
||||
### Key Types
|
||||
|
||||
Each type specifies it's own pubkey, address, and signature format.
|
||||
|
||||
#### Ed25519
|
||||
|
||||
The address is the first 20-bytes of the SHA256 hash of the raw 32-byte public key:
|
||||
|
||||
```go
|
||||
address = SHA256(pubkey)[:20]
|
||||
```
|
||||
|
||||
The signature is the raw 64-byte ED25519 signature.
|
||||
|
||||
Tendermint adopted [zip215](https://zips.z.cash/zip-0215) for verification of ed25519 signatures.
|
||||
|
||||
> Note: This change will be released in the next major release of Tendermint-Go (0.35).
|
||||
|
||||
#### Secp256k1
|
||||
|
||||
The address is the first 20-bytes of the SHA256 hash of the raw 32-byte public key:
|
||||
|
||||
```go
|
||||
address = SHA256(pubkey)[:20]
|
||||
```
|
||||
|
||||
## Other Common Types
|
||||
|
||||
### BitArray
|
||||
|
||||
The BitArray is used in some consensus messages to represent votes received from
|
||||
validators, or parts received in a block. It is represented
|
||||
with a struct containing the number of bits (`Bits`) and the bit-array itself
|
||||
encoded in base64 (`Elems`).
|
||||
|
||||
| Name | Type |
|
||||
|-------|----------------------------|
|
||||
| bits | int64 |
|
||||
| elems | slice of int64 (`[]int64`) |
|
||||
|
||||
Note BitArray receives a special JSON encoding in the form of `x` and `_`
|
||||
representing `1` and `0`. Ie. the BitArray `10110` would be JSON encoded as
|
||||
`"x_xx_"`
|
||||
|
||||
### Part
|
||||
|
||||
Part is used to break up blocks into pieces that can be gossiped in parallel
|
||||
and securely verified using a Merkle tree of the parts.
|
||||
|
||||
Part contains the index of the part (`Index`), the actual
|
||||
underlying data of the part (`Bytes`), and a Merkle proof that the part is contained in
|
||||
the set (`Proof`).
|
||||
|
||||
| Name | Type |
|
||||
|-------|---------------------------|
|
||||
| index | uint32 |
|
||||
| bytes | slice of bytes (`[]byte`) |
|
||||
| proof | [proof](#merkle-proof) |
|
||||
|
||||
See details of SimpleProof, below.
|
||||
|
||||
### MakeParts
|
||||
|
||||
Encode an object using Protobuf and slice it into parts.
|
||||
Tendermint uses a part size of 65536 bytes, and allows a maximum of 1601 parts
|
||||
(see `types.MaxBlockPartsCount`). This corresponds to the hard-coded block size
|
||||
limit of 100MB.
|
||||
|
||||
```go
|
||||
func MakeParts(block Block) []Part
|
||||
```
|
||||
|
||||
## Merkle Trees
|
||||
|
||||
For an overview of Merkle trees, see
|
||||
[wikipedia](https://en.wikipedia.org/wiki/Merkle_tree)
|
||||
|
||||
We use the RFC 6962 specification of a merkle tree, with sha256 as the hash function.
|
||||
Merkle trees are used throughout Tendermint to compute a cryptographic digest of a data structure.
|
||||
The differences between RFC 6962 and the simplest form a merkle tree are that:
|
||||
|
||||
1. leaf nodes and inner nodes have different hashes.
|
||||
This is for "second pre-image resistance", to prevent the proof to an inner node being valid as the proof of a leaf.
|
||||
The leaf nodes are `SHA256(0x00 || leaf_data)`, and inner nodes are `SHA256(0x01 || left_hash || right_hash)`.
|
||||
|
||||
2. When the number of items isn't a power of two, the left half of the tree is as big as it could be.
|
||||
(The largest power of two less than the number of items) This allows new leaves to be added with less
|
||||
recomputation. For example:
|
||||
|
||||
```md
|
||||
Simple Tree with 6 items Simple Tree with 7 items
|
||||
|
||||
* *
|
||||
/ \ / \
|
||||
/ \ / \
|
||||
/ \ / \
|
||||
/ \ / \
|
||||
* * * *
|
||||
/ \ / \ / \ / \
|
||||
/ \ / \ / \ / \
|
||||
/ \ / \ / \ / \
|
||||
* * h4 h5 * * * h6
|
||||
/ \ / \ / \ / \ / \
|
||||
h0 h1 h2 h3 h0 h1 h2 h3 h4 h5
|
||||
```
|
||||
|
||||
### MerkleRoot
|
||||
|
||||
The function `MerkleRoot` is a simple recursive function defined as follows:
|
||||
|
||||
```go
|
||||
// SHA256([]byte{})
|
||||
func emptyHash() []byte {
|
||||
return tmhash.Sum([]byte{})
|
||||
}
|
||||
|
||||
// SHA256(0x00 || leaf)
|
||||
func leafHash(leaf []byte) []byte {
|
||||
return tmhash.Sum(append(0x00, leaf...))
|
||||
}
|
||||
|
||||
// SHA256(0x01 || left || right)
|
||||
func innerHash(left []byte, right []byte) []byte {
|
||||
return tmhash.Sum(append(0x01, append(left, right...)...))
|
||||
}
|
||||
|
||||
// largest power of 2 less than k
|
||||
func getSplitPoint(k int) { ... }
|
||||
|
||||
func MerkleRoot(items [][]byte) []byte{
|
||||
switch len(items) {
|
||||
case 0:
|
||||
return empthHash()
|
||||
case 1:
|
||||
return leafHash(items[0])
|
||||
default:
|
||||
k := getSplitPoint(len(items))
|
||||
left := MerkleRoot(items[:k])
|
||||
right := MerkleRoot(items[k:])
|
||||
return innerHash(left, right)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: `MerkleRoot` operates on items which are arbitrary byte arrays, not
|
||||
necessarily hashes. For items which need to be hashed first, we introduce the
|
||||
`Hashes` function:
|
||||
|
||||
```go
|
||||
func Hashes(items [][]byte) [][]byte {
|
||||
return SHA256 of each item
|
||||
}
|
||||
```
|
||||
|
||||
Note: we will abuse notion and invoke `MerkleRoot` with arguments of type `struct` or type `[]struct`.
|
||||
For `struct` arguments, we compute a `[][]byte` containing the protobuf encoding of each
|
||||
field in the struct, in the same order the fields appear in the struct.
|
||||
For `[]struct` arguments, we compute a `[][]byte` by protobuf encoding the individual `struct` elements.
|
||||
|
||||
### Merkle Proof
|
||||
|
||||
Proof that a leaf is in a Merkle tree is composed as follows:
|
||||
|
||||
| Name | Type |
|
||||
|----------|----------------------------|
|
||||
| total | int64 |
|
||||
| index | int64 |
|
||||
| leafHash | slice of bytes (`[]byte`) |
|
||||
| aunts | Matrix of bytes ([][]byte) |
|
||||
|
||||
Which is verified as follows:
|
||||
|
||||
```golang
|
||||
func (proof Proof) Verify(rootHash []byte, leaf []byte) bool {
|
||||
assert(proof.LeafHash, leafHash(leaf)
|
||||
|
||||
computedHash := computeHashFromAunts(proof.Index, proof.Total, proof.LeafHash, proof.Aunts)
|
||||
return computedHash == rootHash
|
||||
}
|
||||
|
||||
func computeHashFromAunts(index, total int, leafHash []byte, innerHashes [][]byte) []byte{
|
||||
assert(index < total && index >= 0 && total > 0)
|
||||
|
||||
if total == 1{
|
||||
assert(len(proof.Aunts) == 0)
|
||||
return leafHash
|
||||
}
|
||||
|
||||
assert(len(innerHashes) > 0)
|
||||
|
||||
numLeft := getSplitPoint(total) // largest power of 2 less than total
|
||||
if index < numLeft {
|
||||
leftHash := computeHashFromAunts(index, numLeft, leafHash, innerHashes[:len(innerHashes)-1])
|
||||
assert(leftHash != nil)
|
||||
return innerHash(leftHash, innerHashes[len(innerHashes)-1])
|
||||
}
|
||||
rightHash := computeHashFromAunts(index-numLeft, total-numLeft, leafHash, innerHashes[:len(innerHashes)-1])
|
||||
assert(rightHash != nil)
|
||||
return innerHash(innerHashes[len(innerHashes)-1], rightHash)
|
||||
}
|
||||
```
|
||||
|
||||
The number of aunts is limited to 100 (`MaxAunts`) to protect the node against DOS attacks.
|
||||
This limits the tree size to 2^100 leaves, which should be sufficient for any
|
||||
conceivable purpose.
|
||||
|
||||
### IAVL+ Tree
|
||||
|
||||
Because Tendermint only uses a Simple Merkle Tree, application developers are expect to use their own Merkle tree in their applications. For example, the IAVL+ Tree - an immutable self-balancing binary tree for persisting application state is used by the [Cosmos SDK](https://github.com/cosmos/cosmos-sdk/blob/ae77f0080a724b159233bd9b289b2e91c0de21b5/docs/interfaces/lite/specification.md)
|
||||
|
||||
## JSON
|
||||
|
||||
Tendermint has its own JSON encoding in order to keep backwards compatibility with the previous RPC layer.
|
||||
|
||||
Registered types are encoded as:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "<type name>",
|
||||
"value": <JSON>
|
||||
}
|
||||
```
|
||||
|
||||
For instance, an ED25519 PubKey would look like:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "tendermint/PubKeyEd25519",
|
||||
"value": "uZ4h63OFWuQ36ZZ4Bd6NF+/w9fWUwrOncrQsackrsTk="
|
||||
}
|
||||
```
|
||||
|
||||
Where the `"value"` is the base64 encoding of the raw pubkey bytes, and the
|
||||
`"type"` is the type name for Ed25519 pubkeys.
|
||||
|
||||
### Signed Messages
|
||||
|
||||
Signed messages (eg. votes, proposals) in the consensus are encoded using protobuf.
|
||||
|
||||
When signing, the elements of a message are re-ordered so the fixed-length fields
|
||||
are first, making it easy to quickly check the type, height, and round.
|
||||
The `ChainID` is also appended to the end.
|
||||
We call this encoding the SignBytes. For instance, SignBytes for a vote is the protobuf encoding of the following struct:
|
||||
|
||||
```protobuf
|
||||
message CanonicalVote {
|
||||
SignedMsgType type = 1;
|
||||
sfixed64 height = 2; // canonicalization requires fixed size encoding here
|
||||
sfixed64 round = 3; // canonicalization requires fixed size encoding here
|
||||
CanonicalBlockID block_id = 4;
|
||||
google.protobuf.Timestamp timestamp = 5;
|
||||
string chain_id = 6;
|
||||
}
|
||||
```
|
||||
|
||||
The field ordering and the fixed sized encoding for the first three fields is optimized to ease parsing of SignBytes
|
||||
in HSMs. It creates fixed offsets for relevant fields that need to be read in this context.
|
||||
|
||||
> Note: All canonical messages are length prefixed.
|
||||
|
||||
For more details, see the [signing spec](../consensus/signing.md).
|
||||
Also, see the motivating discussion in
|
||||
[#1622](https://github.com/tendermint/tendermint/issues/1622).
|
||||
@@ -0,0 +1,34 @@
|
||||
# Genesis
|
||||
|
||||
The genesis file is the starting point of a chain. An application will populate the `app_state` field in the genesis with their required fields. Tendermint is not able to validate this section because it is unaware what application state consists of.
|
||||
|
||||
## Genesis Fields
|
||||
|
||||
- `genesis_time`: The genesis time is the time the blockchain started or will start. If nodes are started before this time they will sit idle until the time specified.
|
||||
- `chain_id`: The chainid is the chain identifier. Every chain should have a unique identifier. When conducting a fork based upgrade, we recommend changing the chainid to avoid network or consensus errors.
|
||||
- `initial_height`: This field is the starting height of the blockchain. When conducting a chain restart to avoid restarting at height 1, the network is able to start at a specified height.
|
||||
- `consensus_params`
|
||||
- `block`
|
||||
- `max_bytes`: The max amount of bytes a block can be.
|
||||
- `max_gas`: The maximum amount of gas that a block can have.
|
||||
- `time_iota_ms`: This parameter has no value anymore in Tendermint-core.
|
||||
|
||||
- `evidence`
|
||||
- `max_age_num_blocks`: After this preset amount of blocks has passed a single piece of evidence is considered invalid
|
||||
- `max_age_duration`: After this preset amount of time has passed a single piece of evidence is considered invalid.
|
||||
- `max_bytes`: The max amount of bytes of all evidence included in a block.
|
||||
|
||||
> Note: For evidence to be considered invalid, evidence must be older than both `max_age_num_blocks` and `max_age_duration`
|
||||
|
||||
- `validator`
|
||||
- `pub_key_types`: Defines which curves are to be accepted as a valid validator consensus key. Tendermint supports ed25519, sr25519 and secp256k1.
|
||||
|
||||
- `version`
|
||||
- `app_version`: The version of the application. This is set by the application and is used to identify which version of the app a user should be using in order to operate a node.
|
||||
|
||||
- `validators`
|
||||
- This is an array of validators. This validator set is used as the starting validator set of the chain. This field can be empty, if the application sets the validator set in `InitChain`.
|
||||
|
||||
- `app_hash`: The applications state root hash. This field does not need to be populated at the start of the chain, the application may provide the needed information via `Initchain`.
|
||||
|
||||
- `app_state`: This section is filled in by the application and is unknown to Tendermint.
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
order: 1
|
||||
parent:
|
||||
title: Core
|
||||
order: 3
|
||||
---
|
||||
|
||||
This section describes the core types and functionality of the Tendermint protocol implementation.
|
||||
|
||||
- [Core Data Structures](./data_structures.md)
|
||||
- [Encoding](./encoding.md)
|
||||
- [Genesis](./genesis.md)
|
||||
- [State](./state.md)
|
||||
@@ -0,0 +1,121 @@
|
||||
# State
|
||||
|
||||
The state contains information whose cryptographic digest is included in block headers, and thus is
|
||||
necessary for validating new blocks. For instance, the validators set and the results of
|
||||
transactions are never included in blocks, but their Merkle roots are:
|
||||
the state keeps track of them.
|
||||
|
||||
The `State` object itself is an implementation detail, since it is never
|
||||
included in a block or gossiped over the network, and we never compute
|
||||
its hash. The persistence or query interface of the `State` object
|
||||
is an implementation detail and not included in the specification.
|
||||
However, the types in the `State` object are part of the specification, since
|
||||
the Merkle roots of the `State` objects are included in blocks and values are used during
|
||||
validation.
|
||||
|
||||
```go
|
||||
type State struct {
|
||||
ChainID string
|
||||
InitialHeight int64
|
||||
|
||||
LastBlockHeight int64
|
||||
LastBlockID types.BlockID
|
||||
LastBlockTime time.Time
|
||||
|
||||
Version Version
|
||||
LastResults []Result
|
||||
AppHash []byte
|
||||
|
||||
LastValidators ValidatorSet
|
||||
Validators ValidatorSet
|
||||
NextValidators ValidatorSet
|
||||
|
||||
ConsensusParams ConsensusParams
|
||||
}
|
||||
```
|
||||
|
||||
The chain ID and initial height are taken from the genesis file, and not changed again. The
|
||||
initial height will be `1` in the typical case, `0` is an invalid value.
|
||||
|
||||
Note there is a hard-coded limit of 10000 validators. This is inherited from the
|
||||
limit on the number of votes in a commit.
|
||||
|
||||
Further information on [`Validator`'s](./data_structures.md#validator),
|
||||
[`ValidatorSet`'s](./data_structures.md#validatorset) and
|
||||
[`ConsensusParams`'s](./data_structures.md#consensusparams) can
|
||||
be found in [data structures](./data_structures.md)
|
||||
|
||||
## Execution
|
||||
|
||||
State gets updated at the end of executing a block. Of specific interest is `ResponseEndBlock` and
|
||||
`ResponseCommit`
|
||||
|
||||
```go
|
||||
type ResponseEndBlock struct {
|
||||
ValidatorUpdates []ValidatorUpdate `protobuf:"bytes,1,rep,name=validator_updates,json=validatorUpdates,proto3" json:"validator_updates"`
|
||||
ConsensusParamUpdates *types1.ConsensusParams `protobuf:"bytes,2,opt,name=consensus_param_updates,json=consensusParamUpdates,proto3" json:"consensus_param_updates,omitempty"`
|
||||
Events []Event `protobuf:"bytes,3,rep,name=events,proto3" json:"events,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
where
|
||||
|
||||
```go
|
||||
type ValidatorUpdate struct {
|
||||
PubKey crypto.PublicKey `protobuf:"bytes,1,opt,name=pub_key,json=pubKey,proto3" json:"pub_key"`
|
||||
Power int64 `protobuf:"varint,2,opt,name=power,proto3" json:"power,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```go
|
||||
type ResponseCommit struct {
|
||||
// reserve 1
|
||||
Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
|
||||
RetainHeight int64 `protobuf:"varint,3,opt,name=retain_height,json=retainHeight,proto3" json:"retain_height,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
`ValidatorUpdates` are used to add and remove validators to the current set as well as update
|
||||
validator power. Setting validator power to 0 in `ValidatorUpdate` will cause the validator to be
|
||||
removed. `ConsensusParams` are safely copied across (i.e. if a field is nil it gets ignored) and the
|
||||
`Data` from the `ResponseCommit` is used as the `AppHash`
|
||||
|
||||
## Version
|
||||
|
||||
```go
|
||||
type Version struct {
|
||||
consensus Consensus
|
||||
software string
|
||||
}
|
||||
```
|
||||
|
||||
[`Consensus`](./data_structures.md#version) contains the protocol version for the blockchain and the
|
||||
application.
|
||||
|
||||
## Block
|
||||
|
||||
The total size of a block is limited in bytes by the `ConsensusParams.Block.MaxBytes`.
|
||||
Proposed blocks must be less than this size, and will be considered invalid
|
||||
otherwise.
|
||||
|
||||
Blocks should additionally be limited by the amount of "gas" consumed by the
|
||||
transactions in the block, though this is not yet implemented.
|
||||
|
||||
## Evidence
|
||||
|
||||
For evidence in a block to be valid, it must satisfy:
|
||||
|
||||
```go
|
||||
block.Header.Time-evidence.Time < ConsensusParams.Evidence.MaxAgeDuration &&
|
||||
block.Header.Height-evidence.Height < ConsensusParams.Evidence.MaxAgeNumBlocks
|
||||
```
|
||||
|
||||
A block must not contain more than `ConsensusParams.Evidence.MaxBytes` of evidence. This is
|
||||
implemented to mitigate spam attacks.
|
||||
|
||||
## Validator
|
||||
|
||||
Validators from genesis file and `ResponseEndBlock` must have pubkeys of type ∈
|
||||
`ConsensusParams.Validator.PubKeyTypes`.
|
||||
@@ -0,0 +1,37 @@
|
||||
# we need python2 support, which was dropped after buster:
|
||||
FROM debian:buster
|
||||
|
||||
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y apt-utils
|
||||
|
||||
# Install and configure locale `en_US.UTF-8`
|
||||
RUN apt-get install -y locales && \
|
||||
sed -i -e "s/# $en_US.*/en_US.UTF-8 UTF-8/" /etc/locale.gen && \
|
||||
dpkg-reconfigure --frontend=noninteractive locales && \
|
||||
update-locale LANG=en_US.UTF-8
|
||||
ENV LANG=en_US.UTF-8
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y git python2 python-pip g++ cmake python-ply python-tk tix pkg-config libssl-dev python-setuptools
|
||||
|
||||
# create a user:
|
||||
RUN useradd -ms /bin/bash user
|
||||
USER user
|
||||
WORKDIR /home/user
|
||||
|
||||
RUN git clone --recurse-submodules https://github.com/kenmcmil/ivy.git
|
||||
WORKDIR /home/user/ivy/
|
||||
RUN git checkout 271ee38980699115508eb90a0dd01deeb750a94b
|
||||
|
||||
RUN python2.7 build_submodules.py
|
||||
RUN mkdir -p "/home/user/python/lib/python2.7/site-packages"
|
||||
ENV PYTHONPATH="/home/user/python/lib/python2.7/site-packages"
|
||||
# need to install pyparsing manually because otherwise wrong version found
|
||||
RUN pip install pyparsing
|
||||
RUN python2.7 setup.py install --prefix="/home/user/python/"
|
||||
ENV PATH=$PATH:"/home/user/python/bin/"
|
||||
WORKDIR /home/user/tendermint-proof/
|
||||
|
||||
ENTRYPOINT ["/home/user/tendermint-proof/check_proofs.sh"]
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# Ivy Proofs
|
||||
|
||||
```copyright
|
||||
Copyright (c) 2020 Galois, Inc.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
```
|
||||
|
||||
## Contents
|
||||
|
||||
This folder contains:
|
||||
|
||||
* `tendermint.ivy`, a specification of Tendermint algorithm as described in *The latest gossip on BFT consensus* by E. Buchman, J. Kwon, Z. Milosevic.
|
||||
* `abstract_tendermint.ivy`, a more abstract specification of Tendermint that is more verification-friendly.
|
||||
* `classic_safety.ivy`, a proof that Tendermint satisfies the classic safety property of BFT consensus: if every two quorums have a well-behaved node in common, then no two well-behaved nodes ever disagree.
|
||||
* `accountable_safety_1.ivy`, a proof that, assuming every quorum contains at least one well-behaved node, if two well-behaved nodes disagree, then there is evidence demonstrating at least f+1 nodes misbehaved.
|
||||
* `accountable_safety_2.ivy`, a proof that, regardless of any assumption about quorums, well-behaved nodes cannot be framed by malicious nodes. In other words, malicious nodes can never construct evidence that incriminates a well-behaved node.
|
||||
* `network_shim.ivy`, the network model and a convenience `shim` object to interface with the Tendermint specification.
|
||||
* `domain_model.ivy`, a specification of the domain model underlying the Tendermint specification, i.e. rounds, value, quorums, etc.
|
||||
|
||||
All specifications and proofs are written in [Ivy](https://github.com/kenmcmil/ivy).
|
||||
|
||||
The license above applies to all files in this folder.
|
||||
|
||||
|
||||
## Building and running
|
||||
|
||||
The easiest way to check the proofs is to use [Docker](https://www.docker.com/).
|
||||
|
||||
1. Install [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/).
|
||||
2. Build a Docker image: `docker-compose build`
|
||||
3. Run the proofs inside the Docker container: `docker-compose run
|
||||
tendermint-proof`. This will check all the proofs with the `ivy_check`
|
||||
command and write the output of `ivy_check` to a subdirectory of `./output/'
|
||||
@@ -0,0 +1,178 @@
|
||||
#lang ivy1.7
|
||||
# ---
|
||||
# layout: page
|
||||
# title: Abstract specification of Tendermint in Ivy
|
||||
# ---
|
||||
|
||||
# Here we define an abstract version of the Tendermint specification. We use
|
||||
# two main forms of abstraction: a) We abstract over how information is
|
||||
# transmitted (there is no network). b) We abstract functions using relations.
|
||||
# For example, we abstract over a node's current round, instead only tracking
|
||||
# with a relation which rounds the node has left. We do something similar for
|
||||
# the `lockedRound` variable. This is in order to avoid using a function from
|
||||
# node to round, and it allows us to emit verification conditions that are
|
||||
# efficiently solvable by Z3.
|
||||
|
||||
# This specification also defines the observations that are used to adjudicate
|
||||
# misbehavior. Well-behaved nodes faithfully observe every message that they
|
||||
# use to take a step, while Byzantine nodes can fake observations about
|
||||
# themselves (including withholding observations). Misbehavior is defined using
|
||||
# the collection of all observations made (in reality, those observations must
|
||||
# be collected first, but we do not model this process).
|
||||
|
||||
include domain_model
|
||||
|
||||
module abstract_tendermint = {
|
||||
|
||||
# Protocol state
|
||||
# ##############
|
||||
|
||||
relation left_round(N:node, R:round)
|
||||
relation prevoted(N:node, R:round, V:value)
|
||||
relation precommitted(N:node, R:round, V:value)
|
||||
relation decided(N:node, R:round, V:value)
|
||||
relation locked(N:node, R:round, V:value)
|
||||
|
||||
# Accountability relations
|
||||
# ########################
|
||||
|
||||
relation observed_prevoted(N:node, R:round, V:value)
|
||||
relation observed_precommitted(N:node, R:round, V:value)
|
||||
|
||||
# relations that are defined in terms of the previous two:
|
||||
relation observed_equivocation(N:node)
|
||||
relation observed_unlawful_prevote(N:node)
|
||||
relation agreement
|
||||
relation accountability_violation
|
||||
|
||||
object defs = { # we hide those definitions and use them only when needed
|
||||
private {
|
||||
definition [observed_equivocation_def] observed_equivocation(N) = exists V1,V2,R .
|
||||
V1 ~= V2 & (observed_precommitted(N,R,V1) & observed_precommitted(N,R,V2) | observed_prevoted(N,R,V1) & observed_prevoted(N,R,V2))
|
||||
|
||||
definition [observed_unlawful_prevote_def] observed_unlawful_prevote(N) = exists V1,V2,R1,R2 .
|
||||
V1 ~= value.nil & V2 ~= value.nil & V1 ~= V2 & R1 < R2 & observed_precommitted(N,R1,V1) & observed_prevoted(N,R2,V2)
|
||||
& forall Q,R . R1 <= R & R < R2 & nset.is_quorum(Q) -> exists N2 . nset.member(N2,Q) & ~observed_prevoted(N2,R,V2)
|
||||
|
||||
definition [agreement_def] agreement = forall N1,N2,R1,R2,V1,V2 . well_behaved(N1) & well_behaved(N2) & decided(N1,R1,V1) & decided(N2,R2,V2) -> V1 = V2
|
||||
|
||||
definition [accountability_violation_def] accountability_violation = exists Q1,Q2 . nset.is_quorum(Q1) & nset.is_quorum(Q2) & (forall N . nset.member(N,Q1) & nset.member(N,Q2) -> observed_equivocation(N) | observed_unlawful_prevote(N))
|
||||
}
|
||||
}
|
||||
|
||||
# Protocol transitions
|
||||
# ####################
|
||||
|
||||
after init {
|
||||
left_round(N,R) := R < 0;
|
||||
prevoted(N,R,V) := false;
|
||||
precommitted(N,R,V) := false;
|
||||
decided(N,R,V) := false;
|
||||
locked(N,R,V) := false;
|
||||
|
||||
observed_prevoted(N,R,V) := false;
|
||||
observed_precommitted(N,R,V) := false;
|
||||
}
|
||||
|
||||
# Actions are named after the corresponding line numbers in the Tendermint
|
||||
# arXiv paper.
|
||||
|
||||
action l_11(n:node, r:round) = { # start round r
|
||||
require ~left_round(n,r);
|
||||
left_round(n,R) := R < r;
|
||||
}
|
||||
|
||||
action l_22(n:node, rp:round, v:value) = {
|
||||
require ~left_round(n,rp);
|
||||
require ~prevoted(n,rp,V) & ~precommitted(n,rp,V);
|
||||
require (forall R,V . locked(n,R,V) -> V = v) | v = value.nil;
|
||||
prevoted(n, rp, v) := true;
|
||||
left_round(n, R) := R < rp; # leave all lower rounds.
|
||||
|
||||
observed_prevoted(n, rp, v) := observed_prevoted(n, rp, v) | well_behaved(n); # the node observes itself
|
||||
}
|
||||
|
||||
action l_28(n:node, rp:round, v:value, vr:round, q:nset) = {
|
||||
require ~left_round(n,rp) & ~prevoted(n,rp,V);
|
||||
require ~prevoted(n,rp,V) & ~precommitted(n,rp,V);
|
||||
require vr < rp;
|
||||
require nset.is_quorum(q) & (forall N . nset.member(N,q) -> (prevoted(N,vr,v) | ~well_behaved(N)));
|
||||
var proposal:value;
|
||||
if value.valid(v) & ((forall R0,V0 . locked(n,R0,V0) -> R0 <= vr) | (forall R,V . locked(n,R,V) -> V = v)) {
|
||||
proposal := v;
|
||||
}
|
||||
else {
|
||||
proposal := value.nil;
|
||||
};
|
||||
prevoted(n, rp, proposal) := true;
|
||||
left_round(n, R) := R < rp; # leave all lower rounds
|
||||
|
||||
observed_prevoted(N, vr, v) := observed_prevoted(N, vr, v) | (well_behaved(n) & nset.member(N,q)); # the node observes the prevotes of quorum q
|
||||
observed_prevoted(n, rp, proposal) := observed_prevoted(n, rp, proposal) | well_behaved(n); # the node observes itself
|
||||
}
|
||||
|
||||
action l_36(n:node, rp:round, v:value, q:nset) = {
|
||||
require v ~= value.nil;
|
||||
require ~left_round(n,rp);
|
||||
require exists V . prevoted(n,rp,V);
|
||||
require ~precommitted(n,rp,V);
|
||||
require nset.is_quorum(q) & (forall N . nset.member(N,q) -> (prevoted(N,rp,v) | ~well_behaved(N)));
|
||||
precommitted(n, rp, v) := true;
|
||||
left_round(n, R) := R < rp; # leave all lower rounds
|
||||
locked(n,R,V) := R <= rp & V = v;
|
||||
|
||||
observed_prevoted(N, rp, v) := observed_prevoted(N, rp, v) | (well_behaved(n) & nset.member(N,q)); # the node observes the prevotes of quorum q
|
||||
observed_precommitted(n, rp, v) := observed_precommitted(n, rp, v) | well_behaved(n); # the node observes itself
|
||||
}
|
||||
|
||||
action l_44(n:node, rp:round, q:nset) = {
|
||||
require ~left_round(n,rp);
|
||||
require ~precommitted(n,rp,V);
|
||||
require nset.is_quorum(q) & (forall N .nset.member(N,q) -> (prevoted(N,rp,value.nil) | ~well_behaved(N)));
|
||||
precommitted(n, rp, value.nil) := true;
|
||||
left_round(n, R) := R < rp; # leave all lower rounds
|
||||
|
||||
observed_prevoted(N, rp, value.nil) := observed_prevoted(N, rp, value.nil) | (well_behaved(n) & nset.member(N,q)); # the node observes the prevotes of quorum q
|
||||
observed_precommitted(n, rp, value.nil) := observed_precommitted(n, rp, value.nil) | well_behaved(n); # the node observes itself
|
||||
}
|
||||
|
||||
action l_57(n:node, rp:round) = {
|
||||
require ~left_round(n,rp);
|
||||
require ~prevoted(n,rp,V);
|
||||
prevoted(n, rp, value.nil) := true;
|
||||
left_round(n, R) := R < rp; # leave all lower rounds
|
||||
|
||||
observed_prevoted(n, rp, value.nil) := observed_prevoted(n, rp, value.nil) | well_behaved(n); # the node observes itself
|
||||
}
|
||||
|
||||
action l_61(n:node, rp:round) = {
|
||||
require ~left_round(n,rp);
|
||||
require ~precommitted(n,rp,V);
|
||||
precommitted(n, rp, value.nil) := true;
|
||||
left_round(n, R) := R < rp; # leave all lower rounds
|
||||
|
||||
observed_precommitted(n, rp, value.nil) := observed_precommitted(n, rp, value.nil) | well_behaved(n); # the node observes itself
|
||||
}
|
||||
|
||||
action decide(n:node, r:round, v:value, q:nset) = {
|
||||
require v ~= value.nil;
|
||||
require nset.is_quorum(q) & (forall N . nset.member(N, q) -> (precommitted(N, r, v) | ~well_behaved(N)));
|
||||
decided(n, r, v) := true;
|
||||
|
||||
observed_precommitted(N, r, v) := observed_precommitted(N, r, v) | (well_behaved(n) & nset.member(N,q)); # the node observes the precommits of quorum q
|
||||
|
||||
}
|
||||
|
||||
action misbehave = {
|
||||
# Byzantine nodes can claim they observed whatever they want about themselves,
|
||||
# but they cannot remove observations. Note that we use assume because we don't
|
||||
# want those to be checked; we just want them to be true (that's the model of
|
||||
# Byzantine behavior).
|
||||
observed_prevoted(N,R,V) := *;
|
||||
assume (old observed_prevoted(N,R,V)) -> observed_prevoted(N,R,V);
|
||||
assume well_behaved(N) -> old observed_prevoted(N,R,V) = observed_prevoted(N,R,V);
|
||||
observed_precommitted(N,R,V) := *;
|
||||
assume (old observed_precommitted(N,R,V)) -> observed_precommitted(N,R,V);
|
||||
assume well_behaved(N) -> old observed_precommitted(N,R,V) = observed_precommitted(N,R,V);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
#lang ivy1.7
|
||||
# ---
|
||||
# layout: page
|
||||
# title: Proof of Classic Safety
|
||||
# ---
|
||||
|
||||
include tendermint
|
||||
include abstract_tendermint
|
||||
|
||||
# Here we prove the first accountability property: if two well-behaved nodes
|
||||
# disagree, then there are two quorums Q1 and Q2 such that all members of the
|
||||
# intersection of Q1 and Q2 have violated the accountability properties.
|
||||
|
||||
# The proof is done in two steps: first we prove the abstract specification
|
||||
# satisfies the property, and then we show by refinement that this property
|
||||
# also holds in the concrete specification.
|
||||
|
||||
# To see what is checked in the refinement proof, use `ivy_show isolate=accountable_safety_1 accountable_safety_1.ivy`
|
||||
# To see what is checked in the abstract correctness proof, use `ivy_show isolate=abstract_accountable_safety_1 accountable_safety_1.ivy`
|
||||
# To check the whole proof, use `ivy_check accountable_safety_1.ivy`.
|
||||
|
||||
|
||||
# Proof of the accountability property in the abstract specification
|
||||
# ==================================================================
|
||||
|
||||
# We prove with tactics (see `lemma_1` and `lemma_2`) that, if some basic
|
||||
# invariants hold (see `invs` below), then the accountability property holds.
|
||||
|
||||
isolate abstract_accountable_safety = {
|
||||
|
||||
instantiate abstract_tendermint
|
||||
|
||||
# The main property
|
||||
# -----------------
|
||||
|
||||
# If there is disagreement, then there is evidence that a third of the nodes
|
||||
# have violated the protocol:
|
||||
invariant [accountability] agreement | accountability_violation
|
||||
proof {
|
||||
apply lemma_1.thm # this reduces to goal to three subgoals: p1, p2, and p3 (see their definition below)
|
||||
proof [p1] {
|
||||
assume invs.inv1
|
||||
}
|
||||
proof [p2] {
|
||||
assume invs.inv2
|
||||
}
|
||||
proof [p3] {
|
||||
assume invs.inv3
|
||||
}
|
||||
}
|
||||
|
||||
# The invariants
|
||||
# --------------
|
||||
|
||||
isolate invs = {
|
||||
|
||||
# well-behaved nodes observe their own actions faithfully:
|
||||
invariant [inv1] well_behaved(N) -> (observed_precommitted(N,R,V) = precommitted(N,R,V))
|
||||
# if a value is precommitted by a well-behaved node, then a quorum is observed to prevote it:
|
||||
invariant [inv2] (exists N . well_behaved(N) & precommitted(N,R,V)) & V ~= value.nil -> exists Q . nset.is_quorum(Q) & forall N2 . nset.member(N2,Q) -> observed_prevoted(N2,R,V)
|
||||
# if a value is decided by a well-behaved node, then a quorum is observed to precommit it:
|
||||
invariant [inv3] (exists N . well_behaved(N) & decided(N,R,V)) -> 0 <= R & V ~= value.nil & exists Q . nset.is_quorum(Q) & forall N2 . nset.member(N2,Q) -> observed_precommitted(N2,R,V)
|
||||
private {
|
||||
invariant (precommitted(N,R,V) | prevoted(N,R,V)) -> 0 <= R
|
||||
invariant R < 0 -> left_round(N,R)
|
||||
}
|
||||
|
||||
} with this, nset, round, accountable_bft.max_2f_byzantine
|
||||
|
||||
# The theorems proved with tactics
|
||||
# --------------------------------
|
||||
|
||||
# Using complete induction on rounds, we prove that, assuming that the
|
||||
# invariants inv1, inv2, and inv3 hold, the accountability property holds.
|
||||
|
||||
# For technical reasons, we separate the proof in two steps
|
||||
isolate lemma_1 = {
|
||||
|
||||
specification {
|
||||
theorem [thm] {
|
||||
property [p1] forall N,R,V . well_behaved(N) -> (observed_precommitted(N,R,V) = precommitted(N,R,V))
|
||||
property [p2] forall R,V . (exists N . well_behaved(N) & precommitted(N,R,V)) & V ~= value.nil -> exists Q . nset.is_quorum(Q) & forall N2 . nset.member(N2,Q) -> observed_prevoted(N2,R,V)
|
||||
property [p3] forall R,V. (exists N . well_behaved(N) & decided(N,R,V)) -> 0 <= R & V ~= value.nil & exists Q . nset.is_quorum(Q) & forall N2 . nset.member(N2,Q) -> observed_precommitted(N2,R,V)
|
||||
#-------------------------------------------------------------------------------------------------------------------------------------------
|
||||
property agreement | accountability_violation
|
||||
}
|
||||
proof {
|
||||
assume inductive_property # the theorem follows from what we prove by induction below
|
||||
}
|
||||
}
|
||||
|
||||
implementation {
|
||||
# complete induction is not built-in, so we introduce it with an axiom. Note that this only holds for a type where 0 is the smallest element
|
||||
axiom [complete_induction] {
|
||||
relation p(X:round)
|
||||
{ # base case
|
||||
property p(0)
|
||||
}
|
||||
{ # inductive step: show that if the property is true for all X lower or equal to x and y=x+1, then the property is true of y
|
||||
individual a:round
|
||||
individual b:round
|
||||
property (forall X. 0 <= X & X <= a -> p(X)) & round.succ(a,b) -> p(b)
|
||||
}
|
||||
#--------------------------
|
||||
property forall X . 0 <= X -> p(X)
|
||||
}
|
||||
|
||||
# The main lemma: if inv1 and inv2 below hold and a quorum is observed to
|
||||
# precommit V1 at R1 and another quorum is observed to precommit V2~=V1 at
|
||||
# R2>=R1, then the intersection of two quorums (i.e. f+1 nodes) is observed to
|
||||
# violate the protocol. We prove this by complete induction on R2.
|
||||
theorem [inductive_property] {
|
||||
property [p1] forall N,R,V . well_behaved(N) -> (observed_precommitted(N,R,V) = precommitted(N,R,V))
|
||||
property [p2] forall R,V . (exists N . well_behaved(N) & precommitted(N,R,V)) -> V = value.nil | exists Q . nset.is_quorum(Q) & forall N2 . nset.member(N2,Q) -> observed_prevoted(N2,R,V)
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
property forall R2. 0 <= R2 -> ((exists V2,Q1,R1,V1,Q1 . V1 ~= value.nil & V2 ~= value.nil & V1 ~= V2 & 0 <= R1 & R1 <= R2 & nset.is_quorum(Q1) & (forall N . nset.member(N,Q1) -> observed_precommitted(N,R1,V1)) & (exists Q2 . nset.is_quorum(Q2) & forall N . nset.member(N,Q2) -> observed_prevoted(N,R2,V2))) -> accountability_violation)
|
||||
}
|
||||
proof {
|
||||
apply complete_induction # the two subgoals (base case and inductive case) are then discharged automatically
|
||||
# NOTE: this can take a long time depending on the SMT random seed (to try a different seed, use `ivy_check seed=$RANDOM`
|
||||
}
|
||||
}
|
||||
} with this, round, nset, accountable_bft.max_2f_byzantine, defs.observed_equivocation_def, defs.observed_unlawful_prevote_def, defs.accountability_violation_def, defs.agreement_def
|
||||
|
||||
} with round
|
||||
|
||||
# The final proof
|
||||
# ===============
|
||||
|
||||
isolate accountable_safety_1 = {
|
||||
|
||||
# First we instantiate the concrete protocol:
|
||||
instantiate tendermint(abstract_accountable_safety)
|
||||
|
||||
# We then define what we mean by agreement
|
||||
relation agreement
|
||||
definition [agreement_def] agreement = forall N1,N2. well_behaved(N1) & well_behaved(N2) & server.decision(N1) ~= value.nil & server.decision(N2) ~= value.nil -> server.decision(N1) = server.decision(N2)
|
||||
|
||||
invariant abstract_accountable_safety.agreement -> agreement
|
||||
|
||||
invariant [accountability] agreement | abstract_accountable_safety.accountability_violation
|
||||
|
||||
} with value, round, proposers, shim, abstract_accountable_safety, abstract_accountable_safety.defs.agreement_def, accountable_safety_1.agreement_def
|
||||
@@ -0,0 +1,52 @@
|
||||
#lang ivy1.7
|
||||
|
||||
include tendermint
|
||||
include abstract_tendermint
|
||||
|
||||
# Here we prove the second accountability property: no well-behaved node is
|
||||
# ever observed to violate the accountability properties.
|
||||
|
||||
# The proof is done in two steps: first we prove the the abstract specification
|
||||
# satisfies the property, and then we show by refinement that this property
|
||||
# also holds in the concrete specification.
|
||||
|
||||
# To see what is checked in the refinement proof, use `ivy_show isolate=accountable_safety_2 accountable_safety_2.ivy`
|
||||
# To see what is checked in the abstract correctness proof, use `ivy_show isolate=abstract_accountable_safety_2 accountable_safety_2.ivy`
|
||||
# To check the whole proof, use `ivy_check complete=fo accountable_safety_2.ivy`.
|
||||
|
||||
# Proof that the property holds in the abstract specification
|
||||
# ============================================================
|
||||
|
||||
isolate abstract_accountable_safety_2 = {
|
||||
|
||||
instantiate abstract_tendermint
|
||||
|
||||
# the main property:
|
||||
invariant [wb_never_punished] well_behaved(N) -> ~(observed_equivocation(N) | observed_unlawful_prevote(N))
|
||||
|
||||
# the main invariant for proving wb_not_punished:
|
||||
invariant well_behaved(N) & precommitted(N,R,V) & ~locked(N,R,V) & V ~= value.nil -> exists R2,V2 . V2 ~= value.nil & R < R2 & precommitted(N,R2,V2) & locked(N,R2,V2)
|
||||
|
||||
invariant (exists N . well_behaved(N) & precommitted(N,R,V) & V ~= value.nil) -> exists Q . nset.is_quorum(Q) & forall N . nset.member(N,Q) -> observed_prevoted(N,R,V)
|
||||
|
||||
invariant well_behaved(N) -> (observed_prevoted(N,R,V) <-> prevoted(N,R,V))
|
||||
invariant well_behaved(N) -> (observed_precommitted(N,R,V) <-> precommitted(N,R,V))
|
||||
|
||||
# nodes stop prevoting or precommitting in lower rounds when doing so in a higher round:
|
||||
invariant well_behaved(N) & prevoted(N,R2,V2) & R1 < R2 -> left_round(N,R1)
|
||||
invariant well_behaved(N) & locked(N,R2,V2) & R1 < R2 -> left_round(N,R1)
|
||||
|
||||
invariant [precommit_unique_per_round] well_behaved(N) & precommitted(N,R,V1) & precommitted(N,R,V2) -> V1 = V2
|
||||
|
||||
} with nset, round, abstract_accountable_safety_2.defs.observed_equivocation_def, abstract_accountable_safety_2.defs.observed_unlawful_prevote_def
|
||||
|
||||
# Proof that the property holds in the concrete specification
|
||||
# ===========================================================
|
||||
|
||||
isolate accountable_safety_2 = {
|
||||
|
||||
instantiate tendermint(abstract_accountable_safety_2)
|
||||
|
||||
invariant well_behaved(N) -> ~(abstract_accountable_safety_2.observed_equivocation(N) | abstract_accountable_safety_2.observed_unlawful_prevote(N))
|
||||
|
||||
} with round, value, shim, abstract_accountable_safety_2, abstract_accountable_safety_2.defs.observed_equivocation_def, abstract_accountable_safety_2.defs.observed_unlawful_prevote_def
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
|
||||
# returns non-zero error code if any proof fails
|
||||
|
||||
success=0
|
||||
log_dir=$(cat /dev/urandom | tr -cd 'a-f0-9' | head -c 6)
|
||||
cmd="ivy_check seed=$RANDOM"
|
||||
mkdir -p output/$log_dir
|
||||
|
||||
echo "Checking classic safety:"
|
||||
res=$($cmd classic_safety.ivy | tee "output/$log_dir/classic_safety.txt" | tail -n 1)
|
||||
if [ "$res" = "OK" ]; then
|
||||
echo "OK"
|
||||
else
|
||||
echo "FAILED"
|
||||
success=1
|
||||
fi
|
||||
|
||||
echo "Checking accountable safety 1:"
|
||||
res=$($cmd accountable_safety_1.ivy | tee "output/$log_dir/accountable_safety_1.txt" | tail -n 1)
|
||||
if [ "$res" = "OK" ]; then
|
||||
echo "OK"
|
||||
else
|
||||
echo "FAILED"
|
||||
success=1
|
||||
fi
|
||||
|
||||
echo "Checking accountable safety 2:"
|
||||
res=$($cmd complete=fo accountable_safety_2.ivy | tee "output/$log_dir/accountable_safety_2.txt" | tail -n 1)
|
||||
if [ "$res" = "OK" ]; then
|
||||
echo "OK"
|
||||
else
|
||||
echo "FAILED"
|
||||
success=1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "See ivy_check output in the output/ folder"
|
||||
exit $success
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user