mirror of
https://github.com/tendermint/tendermint.git
synced 2026-08-28 11:56:15 +00:00
Merge branch 'master' into alessio/go1.14
This commit is contained in:
+1
-22
@@ -14,9 +14,6 @@ executors:
|
||||
- image: tendermintdev/docker-website-deployment
|
||||
environment:
|
||||
AWS_REGION: us-east-1
|
||||
protoc:
|
||||
docker:
|
||||
- image: tendermintdev/docker-protoc
|
||||
|
||||
commands:
|
||||
run_test:
|
||||
@@ -75,19 +72,6 @@ jobs:
|
||||
root: "/tmp/bin"
|
||||
paths:
|
||||
- "."
|
||||
proto-lint:
|
||||
executor: protoc
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
command: make proto-lint
|
||||
|
||||
proto-breakage:
|
||||
executor: protoc
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
command: make proto-check-breaking
|
||||
|
||||
test_abci_apps:
|
||||
executor: golang
|
||||
@@ -378,13 +362,10 @@ jobs:
|
||||
# command: |
|
||||
# set -x
|
||||
# export PATH=~/.local/bin:$PATH
|
||||
|
||||
# # install node and dredd
|
||||
# ./scripts/get_nodejs.sh
|
||||
|
||||
# # build the binaries with a proper version of Go
|
||||
# docker run --rm -v "$PWD":/go/src/github.com/tendermint/tendermint -w /go/src/github.com/tendermint/tendermint golang make build-linux build-contract-tests-hooks
|
||||
|
||||
# # This docker image works with go 1.7, we can install here the hook handler that contract-tests is going to use
|
||||
# go get github.com/snikch/goodman/cmd/goodman
|
||||
# make contract-tests
|
||||
@@ -407,13 +388,11 @@ workflows:
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- docs-theme-latest
|
||||
- docs-staging
|
||||
- setup_dependencies
|
||||
- test_abci_apps:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
- proto-breakage
|
||||
- proto-lint
|
||||
- test_abci_cli:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
name: Check Markdown links
|
||||
on:
|
||||
schedule:
|
||||
- cron: '* */24 * * *'
|
||||
jobs:
|
||||
markdown-link-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: gaurav-nelson/github-action-markdown-link-check@0.6.0
|
||||
with:
|
||||
folder-path: "docs"
|
||||
@@ -0,0 +1,12 @@
|
||||
name: Proto check
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
proto-checks:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: docker-practice/actions-setup-docker@master
|
||||
- name: lint
|
||||
run: make proto-lint
|
||||
- name: check-breakage
|
||||
run: make proto-check-breaking-ci
|
||||
+53
-1
@@ -1,5 +1,57 @@
|
||||
# Changelog
|
||||
|
||||
## v0.33.2
|
||||
|
||||
*March 11, 2020*
|
||||
|
||||
Special thanks to external contributors on this release:
|
||||
@antho1404, @michaelfig, @gterzian, @tau3, @Shivani912
|
||||
|
||||
Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint).
|
||||
|
||||
### BREAKING CHANGES:
|
||||
|
||||
- CLI/RPC/Config
|
||||
- [cli] [\#4505](https://github.com/tendermint/tendermint/pull/4505) `tendermint lite` sub-command new syntax (@melekes):
|
||||
`lite cosmoshub-3 -p 52.57.29.196:26657 -w public-seed-node.cosmoshub.certus.one:26657
|
||||
--height 962118 --hash 28B97BE9F6DE51AC69F70E0B7BFD7E5C9CD1A595B7DC31AFF27C50D4948`
|
||||
|
||||
- Go API
|
||||
- [lite2] [\#4535](https://github.com/tendermint/tendermint/pull/4535) Remove `Start/Stop` (@melekes)
|
||||
- [lite2] [\#4469](https://github.com/tendermint/tendermint/issues/4469) Remove `RemoveNoLongerTrustedHeaders` and `RemoveNoLongerTrustedHeadersPeriod` option (@cmwaters)
|
||||
- [lite2] [\#4473](https://github.com/tendermint/tendermint/issues/4473) Return height as a 2nd param in `TrustedValidatorSet` (@melekes)
|
||||
- [lite2] [\#4536](https://github.com/tendermint/tendermint/pull/4536) `Update` returns a signed header (1st param) (@melekes)
|
||||
|
||||
|
||||
### IMPROVEMENTS:
|
||||
|
||||
- [blockchain/v2] [\#4361](https://github.com/tendermint/tendermint/pull/4361) Add reactor (@brapse)
|
||||
- [cmd] [\#4515](https://github.com/tendermint/tendermint/issues/4515) Change `tendermint debug dump` sub-command archives filename's format (@melekes)
|
||||
- [consensus] [\#3583](https://github.com/tendermint/tendermint/issues/3583) Reduce `non-deterministic signature` log noise (@tau3)
|
||||
- [examples/kvstore] [\#4507](https://github.com/tendermint/tendermint/issues/4507) ABCI query now returns the proper height (@erikgrinaker)
|
||||
- [lite2] [\#4462](https://github.com/tendermint/tendermint/issues/4462) Add `NewHTTPClient` and `NewHTTPClientFromTrustedStore` (@cmwaters)
|
||||
- [lite2] [\#4329](https://github.com/tendermint/tendermint/issues/4329) modified bisection to loop (@cmwaters)
|
||||
- [lite2] [\#4385](https://github.com/tendermint/tendermint/issues/4385) Disconnect from bad nodes (@melekes)
|
||||
- [lite2] [\#4398](https://github.com/tendermint/tendermint/issues/4398) Add `VerifyAdjacent` and `VerifyNonAdjacent` funcs (@cmwaters)
|
||||
- [lite2] [\#4426](https://github.com/tendermint/tendermint/issues/4426) Don't save intermediate headers (@cmwaters)
|
||||
- [lite2] [\#4464](https://github.com/tendermint/tendermint/issues/4464) Cross-check first header (@cmwaters)
|
||||
- [lite2] [\#4470](https://github.com/tendermint/tendermint/issues/4470) Fix inconsistent header-validatorset pairing (@melekes)
|
||||
- [lite2] [\#4488](https://github.com/tendermint/tendermint/issues/4488) Allow local clock drift -10 sec. (@melekes)
|
||||
- [p2p] [\#4449](https://github.com/tendermint/tendermint/pull/4449) Use `curve25519.X25519()` instead of `ScalarMult` (@erikgrinaker)
|
||||
- [types] [\#4417](https://github.com/tendermint/tendermint/issues/4417) **VerifyCommitX() functions should return as soon as +2/3 threshold is reached** (@alessio).
|
||||
- [libs/kv] [\#4542](https://github.com/tendermint/tendermint/pull/4542) remove unused type KI64Pair (@tessr)
|
||||
|
||||
### BUG FIXES:
|
||||
|
||||
- [cmd] [\#4303](https://github.com/tendermint/tendermint/issues/4303) Show useful error when Tendermint is not initialized (@melekes)
|
||||
- [cmd] [\#4515](https://github.com/tendermint/tendermint/issues/4515) **Fix `tendermint debug kill` sub-command** (@melekes)
|
||||
- [rpc] [\#3935](https://github.com/tendermint/tendermint/issues/3935) **Create buffered subscriptions on `/subscribe`** (@melekes)
|
||||
- [rpc] [\#4375](https://github.com/tendermint/tendermint/issues/4375) Stop searching for txs in `/tx_search` upon client timeout (@gterzian)
|
||||
- [rpc] [\#4406](https://github.com/tendermint/tendermint/pull/4406) Fix issue with multiple subscriptions on the websocket (@antho1404)
|
||||
- [rpc] [\#4432](https://github.com/tendermint/tendermint/issues/4432) Fix `/tx_search` pagination with ordered results (@erikgrinaker)
|
||||
- [rpc] [\#4492](https://github.com/tendermint/tendermint/issues/4492) Keep the original subscription "id" field when new RPCs come in (@michaelfig)
|
||||
|
||||
|
||||
## v0.33.1
|
||||
|
||||
*Feburary 13, 2020*
|
||||
@@ -95,7 +147,7 @@ subjectivity interface. Refer to the [spec](https://github.com/tendermint/spec/b
|
||||
|
||||
- Apps
|
||||
|
||||
- [tm-bench] Removed tm-bench in favor of [tm-load-test](https://github.com/interchainio/tm-load-test)
|
||||
- [tm-bench] Removed tm-bench in favor of [tm-load-test](https://github.com/informalsystems/tm-load-test)
|
||||
|
||||
- Go API
|
||||
|
||||
|
||||
+25
-9
@@ -1,11 +1,12 @@
|
||||
## v0.33.2
|
||||
## v0.33.3
|
||||
|
||||
- Nodes are no longer guaranteed to contain all blocks up to the latest height. The ABCI app can now control which blocks to retain through the ABCI field `ResponseCommit.retain_height`, all blocks and associated data below this height will be removed.
|
||||
|
||||
\*\*
|
||||
|
||||
Special thanks to external contributors on this release:
|
||||
|
||||
Friendly reminder, we have a [bug bounty
|
||||
program](https://hackerone.com/tendermint).
|
||||
Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint).
|
||||
|
||||
### BREAKING CHANGES:
|
||||
|
||||
@@ -13,18 +14,33 @@ program](https://hackerone.com/tendermint).
|
||||
|
||||
- Apps
|
||||
|
||||
- P2P Protocol
|
||||
|
||||
- Go API
|
||||
|
||||
- [rpc/client] [\#4628](https://github.com/tendermint/tendermint/pull/4628) Split out HTTP and local clients into `http` and `local` packages (@erikgrinaker).
|
||||
- [lite2] [\#4616](https://github.com/tendermint/tendermint/pull/4616) Make `maxClockDrift` an option (@melekes).
|
||||
`Verify/VerifyAdjacent/VerifyNonAdjacent` now accept `maxClockDrift time.Duration`.
|
||||
|
||||
### FEATURES:
|
||||
|
||||
- [abci] Add `ResponseCommit.retain_height` field, which will automatically remove blocks below this height.
|
||||
- [rpc] Add `/status` response fields for the earliest block available on the node
|
||||
- [rpc] [\#4611](https://github.com/tendermint/tendermint/pull/4611) Add `codespace` to `ResultBroadcastTx` (@whylee259)
|
||||
|
||||
### IMPROVEMENTS:
|
||||
|
||||
- [types] [\#4417](https://github.com/tendermint/tendermint/issues/4417) VerifyCommitX() functions should return as soon as +2/3 threashold is reached.
|
||||
- [blockchain] Add `Base` to blockchain reactor P2P messages `StatusRequest` and `StatusResponse`
|
||||
- [example/kvstore] Add `RetainBlocks` option to control block retention
|
||||
- [p2p] [\#4548](https://github.com/tendermint/tendermint/pull/4548) Add ban list to address book (@cmwaters)
|
||||
- [privval] \#4534 Add `error` as a return value on`GetPubKey()`
|
||||
- [Docker] \#4569 Default configuration added to docker image (you can still mount your own config the same way) (@greg-szabo)
|
||||
- [lite2] [\#4562](https://github.com/tendermint/tendermint/pull/4562) Cache headers when using bisection (@cmwaters)
|
||||
- [all] [\#4608](https://github.com/tendermint/tendermint/pull/4608) Give reactors descriptive names when they're initialized
|
||||
- [lite2] [\#4575](https://github.com/tendermint/tendermint/pull/4575) Use bisection for within-range verification (@cmwaters)
|
||||
- [tools] \#4615 Allow developers to use Docker to generate proto stubs, via `make proto-gen-docker`.
|
||||
|
||||
### BUG FIXES:
|
||||
|
||||
- [rpc] [\#4493](https://github.com/tendermint/tendermint/pull/4493) Keep the original subscription "id" field when new RPCs come in (@michaelfig)
|
||||
|
||||
- [rpc] [\#4437](https://github.com/tendermint/tendermint/pull/4437) Fix tx_search pagination with ordered results (@erikgrinaker)
|
||||
|
||||
- [rpc] [\#4406](https://github.com/tendermint/tendermint/pull/4406) Fix issue with multiple subscriptions on the websocket (@antho1404)
|
||||
- [rpc] \#4568 Fix panic when `Subscribe` is called, but HTTP client is not running (@melekes)
|
||||
`Subscribe`, `Unsubscribe(All)` methods return an error now.
|
||||
|
||||
+41
-5
@@ -12,7 +12,7 @@ landing changes in master.
|
||||
All work on the code base should be motivated by a [Github
|
||||
Issue](https://github.com/tendermint/tendermint/issues).
|
||||
[Search](https://github.com/tendermint/tendermint/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22)
|
||||
is a good place start when looking for places to contribute. If you
|
||||
is a good place start when looking for places to contribute. If you
|
||||
would like to work on an issue which already exists, please indicate so
|
||||
by leaving a comment.
|
||||
|
||||
@@ -49,8 +49,9 @@ maintainers to take a look.
|
||||

|
||||
|
||||
Each stage of the process is aimed at creating feedback cycles which align contributors and maintainers to make sure:
|
||||
* Contributors don’t waste their time implementing/proposing features which won’t land in master.
|
||||
* Maintainers have the necessary context in order to support and review contributions.
|
||||
|
||||
- Contributors don’t waste their time implementing/proposing features which won’t land in master.
|
||||
- Maintainers have the necessary context in order to support and review contributions.
|
||||
|
||||
## Forking
|
||||
|
||||
@@ -100,7 +101,28 @@ specify exactly the dependency you want to update, eg.
|
||||
|
||||
## Protobuf
|
||||
|
||||
When working with [protobuf](https://developers.google.com/protocol-buffers) there are a few things you should know. We use [buf](https://buf.build/) for our linting and breaking changes checking. If you would like to run linting and check if the changes you have made are breaking then you will have to install the needed dependencies with `make buf`. Then the linting cmd will be `make proto-lint` and the breaking changes check will be `make proto-check-breaking`. To generate new stubs based off of your changes you can run `make proto-gen` (you can do this outside of GOPATH).
|
||||
We use [Protocol Buffers](https://developers.google.com/protocol-buffers) along with [gogoproto](https://github.com/gogo/protobuf) to generate code for use across Tendermint Core.
|
||||
|
||||
For linting and checking breaking changes, we use [buf](https://buf.build/). If you would like to run linting and check if the changes you have made are breaking then you will need to have docker running locally. Then the linting cmd will be `make proto-lint` and the breaking changes check will be `make proto-check-breaking`.
|
||||
|
||||
There are two ways to generate your proto stubs.
|
||||
|
||||
1. Use Docker, pull an image that will generate your proto stubs with no need to install anything. `make proto-gen-docker`
|
||||
2. Run `make proto-gen` after installing `protoc` and gogoproto.
|
||||
|
||||
### Installation Instructions
|
||||
|
||||
To install `protoc`, download an appropriate release (https://github.com/protocolbuffers/protobuf) and then move the provided binaries into your PATH (follow instructions in README included with the download).
|
||||
|
||||
To install `gogoproto`, do the following:
|
||||
|
||||
```sh
|
||||
$ go get github.com/gogo/protobuf/gogoproto
|
||||
$ cd $GOPATH/pkg/mod/github.com/gogo/protobuf@v1.3.1 # or wherever go get installs things
|
||||
$ make install
|
||||
```
|
||||
|
||||
You should now be able to run `make proto-gen` from inside the root Tendermint directory to generate new files from proto files.
|
||||
|
||||
## Vagrant
|
||||
|
||||
@@ -172,9 +194,23 @@ easy to reference the pull request where a change was introduced.
|
||||
|
||||
- ensure pull branch is based on a recent `master`
|
||||
- run `make test` to ensure that all tests pass
|
||||
- squash merge pull request
|
||||
- [squash](https://stackoverflow.com/questions/5189560/squash-my-last-x-commits-together-using-git) merge pull request
|
||||
- the `unstable` branch may be used to aggregate pull merges before fixing tests
|
||||
|
||||
### Git Commit Style
|
||||
|
||||
We follow the [Go style guide on commit messages](https://tip.golang.org/doc/contribute.html#commit_messages). Write concise commits that start with the package name and have a description that finishes the sentence "This change modifies Tendermint to...". For example,
|
||||
|
||||
\```
|
||||
cmd/debug: execute p.Signal only when p is not nil
|
||||
|
||||
[potentially longer description in the body]
|
||||
|
||||
Fixes #nnnn
|
||||
\```
|
||||
|
||||
Each PR should have one commit once it lands on `master`; this can be accomplished by using the "squash and merge" button on Github. Be sure to edit your commit message, though!
|
||||
|
||||
### Release Procedure
|
||||
|
||||
#### Major Release
|
||||
|
||||
+22
-6
@@ -22,17 +22,33 @@ RUN apk update && \
|
||||
# Run the container with tmuser by default. (UID=100, GID=1000)
|
||||
USER tmuser
|
||||
|
||||
# Expose the data directory as a volume since there's mutable state in there
|
||||
VOLUME [ $TMHOME ]
|
||||
|
||||
WORKDIR $TMHOME
|
||||
|
||||
# p2p and rpc port
|
||||
EXPOSE 26656 26657
|
||||
# p2p, rpc and prometheus port
|
||||
EXPOSE 26656 26657 26660
|
||||
|
||||
ENTRYPOINT ["/usr/bin/tendermint"]
|
||||
CMD ["node", "--moniker=`hostname`"]
|
||||
CMD ["node"]
|
||||
STOPSIGNAL SIGTERM
|
||||
|
||||
ARG BINARY=tendermint
|
||||
COPY $BINARY /usr/bin/tendermint
|
||||
|
||||
# Create default configuration for docker run.
|
||||
RUN /usr/bin/tendermint init && \
|
||||
sed -i \
|
||||
-e 's/^proxy_app\s*=.*/proxy_app = "kvstore"/' \
|
||||
-e 's/^moniker\s*=.*/moniker = "dockernode"/' \
|
||||
-e 's/^addr_book_strict\s*=.*/addr_book_strict = false/' \
|
||||
-e 's/^timeout_commit\s*=.*/timeout_commit = "500ms"/' \
|
||||
-e 's/^index_all_tags\s*=.*/index_all_tags = true/' \
|
||||
-e 's,^laddr = "tcp://127.0.0.1:26657",laddr = "tcp://0.0.0.0:26657",' \
|
||||
-e 's/^prometheus\s*=.*/prometheus = true/' \
|
||||
$TMHOME/config/config.toml && \
|
||||
sed -i \
|
||||
-e 's/^\s*"chain_id":.*/ "chain_id": "dockerchain",/' \
|
||||
$TMHOME/config/genesis.json
|
||||
|
||||
# Expose the data directory as a volume since there's mutable state in there
|
||||
VOLUME [ $TMHOME ]
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ OUTPUT?=build/tendermint
|
||||
BUILD_TAGS?='tendermint'
|
||||
LD_FLAGS = -X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse --short=8 HEAD` -s -w
|
||||
BUILD_FLAGS = -mod=readonly -ldflags "$(LD_FLAGS)"
|
||||
HTTPS_GIT := https://github.com/tendermint/tendermint.git
|
||||
DOCKER_BUF := docker run -v $(shell pwd):/workspace --workdir /workspace bufbuild/buf
|
||||
|
||||
all: check build test install
|
||||
.PHONY: all
|
||||
@@ -52,14 +54,23 @@ proto-gen:
|
||||
@sh scripts/protocgen.sh
|
||||
.PHONY: proto-gen
|
||||
|
||||
proto-gen-docker:
|
||||
@echo "Generating Protobuf files"
|
||||
@docker run -v $(shell pwd):/workspace --workdir /workspace tendermintdev/docker-build-proto sh ./scripts/protocgen.sh
|
||||
.PHONY: proto-gen-docker
|
||||
|
||||
proto-lint:
|
||||
@buf check lint --error-format=json
|
||||
@$(DOCKER_BUF) check lint --error-format=json
|
||||
.PHONY: proto-lint
|
||||
|
||||
proto-check-breaking:
|
||||
@buf check breaking --against-input '.git#branch=master'
|
||||
@$(DOCKER_BUF) check breaking --against-input .git#branch=master
|
||||
.PHONY: proto-check-breaking
|
||||
|
||||
proto-check-breaking-ci:
|
||||
@$(DOCKER_BUF) check breaking --against-input $(HTTPS_GIT)#branch=master
|
||||
.PHONY: proto-check-breaking-ci
|
||||
|
||||
###############################################################################
|
||||
### Build ABCI ###
|
||||
###############################################################################
|
||||
@@ -129,9 +140,10 @@ clean_certs:
|
||||
### Formatting, linting, and vetting ###
|
||||
###############################################################################
|
||||
|
||||
fmt:
|
||||
@go fmt ./...
|
||||
.PHONY: fmt
|
||||
format:
|
||||
find . -name '*.go' -type f -not -path "*.git*" -not -name '*.pb.go' -not -name '*pb_test.go' | xargs gofmt -w -s
|
||||
find . -name '*.go' -type f -not -path "*.git*" -not -name '*.pb.go' -not -name '*pb_test.go' | xargs goimports -w -local github.com/tendermint/tendermint
|
||||
.PHONY: format
|
||||
|
||||
lint:
|
||||
@echo "--> Running linter"
|
||||
|
||||
@@ -9,7 +9,7 @@ Or [Blockchain](<https://en.wikipedia.org/wiki/Blockchain_(database)>), for shor
|
||||
[](https://github.com/tendermint/tendermint/releases/latest)
|
||||
[](https://godoc.org/github.com/tendermint/tendermint)
|
||||
[](https://github.com/moovweb/gvm)
|
||||
[](https://riot.im/app/#/room/#tendermint:matrix.org)
|
||||
[](https://discord.gg/AzefAFd)
|
||||
[](https://github.com/tendermint/tendermint/blob/master/LICENSE)
|
||||
[](https://github.com/tendermint/tendermint)
|
||||
|
||||
@@ -68,17 +68,16 @@ See the [install instructions](/docs/introduction/install.md)
|
||||
|
||||
## Contributing
|
||||
|
||||
Please abide by the [Code of Conduct](CODE_OF_CONDUCT.md) in all interactions,
|
||||
and the [contributing guidelines](CONTRIBUTING.md) when submitting code.
|
||||
Please abide by the [Code of Conduct](CODE_OF_CONDUCT.md) in all interactions.
|
||||
|
||||
Join the larger community on the [forum](https://forum.cosmos.network/) and the [chat](https://riot.im/app/#/room/#tendermint:matrix.org).
|
||||
Before contributing to the project, please take a look at the [contributing guidelines](CONTRIBUTING.md)
|
||||
and the [style guide](STYLE_GUIDE.md).
|
||||
|
||||
To learn more about the structure of the software, watch the [Developer
|
||||
Sessions](/docs/DEV_SESSIONS.md) and read some [Architectural Decision
|
||||
Records](https://github.com/tendermint/tendermint/tree/master/docs/architecture).
|
||||
To get more active, Join the wider community at [Discord](https://discord.gg/AzefAFd) or jump onto the [Forum](https://forum.cosmos.network/).
|
||||
|
||||
Learn more by reading the code and comparing it to the
|
||||
[specification](https://github.com/tendermint/spec).
|
||||
Learn more by reading the code and the
|
||||
[specifications](https://github.com/tendermint/spec) or watch the [Developer Sessions](/docs/DEV_SESSIONS.md) and read up on the
|
||||
[Architectural Decision Records](https://github.com/tendermint/tendermint/tree/master/docs/architecture).
|
||||
|
||||
## Versioning
|
||||
|
||||
@@ -137,7 +136,7 @@ hosted at: https://docs.tendermint.com/master/
|
||||
### Tools
|
||||
|
||||
Benchmarking is provided by `tm-load-test`.
|
||||
The code for `tm-load-test` can be found [here](https://github.com/interchainio/tm-load-test) this binary needs to be built separately.
|
||||
The code for `tm-load-test` can be found [here](https://github.com/informalsystems/tm-load-test) this binary needs to be built separately.
|
||||
Additional documentation is found [here](/docs/tools).
|
||||
|
||||
### Sub-projects
|
||||
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
# Go Coding Style Guide
|
||||
|
||||
In order to keep our code looking good with lots of programmers working on it, it helps to have a "style guide", so all
|
||||
the code generally looks quite similar. This doesn't mean there is only one "right way" to write code, or even that this
|
||||
standard is better than your style. But if we agree to a number of stylistic practices, it makes it much easier to read
|
||||
and modify new code. Please feel free to make suggestions if there's something you would like to add or modify.
|
||||
|
||||
We expect all contributors to be familiar with [Effective Go](https://golang.org/doc/effective_go.html)
|
||||
(and it's recommended reading for all Go programmers anyways). Additionally, we generally agree with the suggestions
|
||||
in [Uber's style guide](https://github.com/uber-go/guide/blob/master/style.md) and use that as a starting point.
|
||||
|
||||
|
||||
## Code Structure
|
||||
|
||||
Perhaps more key for code readability than good commenting is having the right structure. As a rule of thumb, try to write
|
||||
in a logical order of importance, taking a little time to think how to order and divide the code such that someone could
|
||||
scroll down and understand the functionality of it just as well as you do. A loose example of such order would be:
|
||||
* Constants, global and package-level variables
|
||||
* Main Struct
|
||||
* Options (only if they are seen as critical to the struct else they should be placed in another file)
|
||||
* Initialization / Start and stop of the service
|
||||
* Msgs/Events
|
||||
* Public Functions (In order of most important)
|
||||
* Private/helper functions
|
||||
* Auxiliary structs and function (can also be above private functions or in a separate file)
|
||||
|
||||
## General
|
||||
|
||||
* Use `gofmt` (or `goimport`) to format all code upon saving it. (If you use VIM, check out vim-go).
|
||||
* Use a linter (see below) and generally try to keep the linter happy (where it makes sense).
|
||||
* Think about documentation, and try to leave godoc comments, when it will help new developers.
|
||||
* Every package should have a high level doc.go file to describe the purpose of that package, its main functions, and any other relevant information.
|
||||
* `TODO` should not be used. If important enough should be recorded as an issue.
|
||||
* `BUG` / `FIXME` should be used sparingly to guide future developers on some of the vulnerabilities of the code.
|
||||
* `XXX` can be used in work-in-progress (prefixed with "WIP:" on github) branches but they must be removed before approving a PR.
|
||||
* Applications (e.g. clis/servers) *should* panic on unexpected unrecoverable errors and print a stack trace.
|
||||
|
||||
## Comments
|
||||
|
||||
* Use a space after comment deliminter (ex. `// your comment`).
|
||||
* Many comments are not sentences. These should begin with a lower case letter and end without a period.
|
||||
* Conversely, sentences in comments should be sentenced-cased and end with a period.
|
||||
|
||||
## Linters
|
||||
|
||||
These must be applied to all (Go) repos.
|
||||
|
||||
* [shellcheck](https://github.com/koalaman/shellcheck)
|
||||
* [golangci-lint](https://github.com/golangci/golangci-lint) (covers all important linters)
|
||||
- See the `.golangci.yml` file in each repo for linter configuration.
|
||||
|
||||
## Various
|
||||
|
||||
* Reserve "Save" and "Load" for long-running persistence operations. When parsing bytes, use "Encode" or "Decode".
|
||||
* Maintain consistency across the codebase.
|
||||
* Functions that return functions should have the suffix `Fn`
|
||||
* Names should not [stutter](https://blog.golang.org/package-names). For example, a struct generally shouldn’t have
|
||||
a field named after itself; e.g., this shouldn't occur:
|
||||
``` golang
|
||||
type middleware struct {
|
||||
middleware Middleware
|
||||
}
|
||||
```
|
||||
* In comments, use "iff" to mean, "if and only if".
|
||||
* Product names are capitalized, like "Tendermint", "Basecoin", "Protobuf", etc except in command lines: `tendermint --help`
|
||||
* Acronyms are all capitalized, like "RPC", "gRPC", "API". "MyID", rather than "MyId".
|
||||
* Prefer errors.New() instead of fmt.Errorf() unless you're actually using the format feature with arguments.
|
||||
|
||||
## Importing Libraries
|
||||
|
||||
Sometimes it's necessary to rename libraries to avoid naming collisions or ambiguity.
|
||||
|
||||
* Use [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports)
|
||||
* Separate imports into blocks - one for the standard lib, one for external libs and one for application libs.
|
||||
* Here are some common library labels for consistency:
|
||||
- dbm "github.com/tendermint/tm-db"
|
||||
- tmcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
|
||||
- tmcfg "github.com/tendermint/tendermint/config/tendermint"
|
||||
- tmtypes "github.com/tendermint/tendermint/types"
|
||||
* Never use anonymous imports (the `.`), for example, `tmlibs/common` or anything else.
|
||||
* When importing a pkg from the `tendermint/libs` directory, prefix the pkg alias with tm.
|
||||
- tmbits "github.com/tendermint/tendermint/libs/bits"
|
||||
* tip: Use the `_` library import to import a library for initialization effects (side effects)
|
||||
|
||||
## Dependencies
|
||||
|
||||
* Dependencies should be pinned by a release tag, or specific commit, to avoid breaking `go get` when external dependencies are updated.
|
||||
* Refer to the [contributing](CONTRIBUTING.md) document for more details
|
||||
|
||||
## Testing
|
||||
|
||||
* The first rule of testing is: we add tests to our code
|
||||
* The second rule of testing is: we add tests to our code
|
||||
* For Golang testing:
|
||||
* Make use of table driven testing where possible and not-cumbersome
|
||||
- [Inspiration](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go)
|
||||
* Make use of [assert](https://godoc.org/github.com/stretchr/testify/assert) and [require](https://godoc.org/github.com/stretchr/testify/require)
|
||||
* When using mocks, it is recommended to use Testify [mock] (https://pkg.go.dev/github.com/stretchr/testify/mock
|
||||
) along with [Mockery](https://github.com/vektra/mockery) for autogeneration
|
||||
|
||||
## Errors
|
||||
|
||||
* Ensure that errors are concise, clear and traceable.
|
||||
* Use stdlib errors package.
|
||||
* For wrapping errors, use `fmt.Errorf()` with `%w`.
|
||||
* Panic is appropriate when an internal invariant of a system is broken, while all other cases (in particular,
|
||||
incorrect or invalid usage) should return errors.
|
||||
|
||||
## Config
|
||||
|
||||
* Currently the TOML filetype is being used for config files
|
||||
* A good practice is to store per-user config files under `~/.[yourAppName]/config.toml`
|
||||
|
||||
## CLI
|
||||
|
||||
* When implementing a CLI use [Cobra](https://github.com/spf13/cobra) and [Viper](https://github.com/spf13/viper).
|
||||
* Helper messages for commands and flags must be all lowercase.
|
||||
* Instead of using pointer flags (eg. `FlagSet().StringVar`) use Viper to retrieve flag values (eg. `viper.GetString`)
|
||||
- The flag key used when setting and getting the flag should always be stored in a
|
||||
variable taking the form `FlagXxx` or `flagXxx`.
|
||||
- Flag short variable descriptions should always start with a lower case character as to remain consistent with
|
||||
the description provided in the default `--help` flag.
|
||||
|
||||
## Version
|
||||
|
||||
* Every repo should have a version/version.go file that mimics the Tendermint Core repo
|
||||
* We read the value of the constant version in our build scripts and hence it has to be a string
|
||||
|
||||
## Non-Go Code
|
||||
|
||||
* All non-Go code (`*.proto`, `Makefile`, `*.sh`), where there is no common
|
||||
agreement on style, should be formatted according to
|
||||
[EditorConfig](http://editorconfig.org/) config:
|
||||
|
||||
```
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
[*.sh]
|
||||
indent_style = tab
|
||||
|
||||
[*.proto]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
```
|
||||
|
||||
Make sure the file above (`.editorconfig`) are in the root directory of your
|
||||
repo and you have a [plugin for your
|
||||
editor](http://editorconfig.org/#download) installed.
|
||||
@@ -6,11 +6,12 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
"github.com/tendermint/tendermint/abci/example/code"
|
||||
"github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/libs/kv"
|
||||
"github.com/tendermint/tendermint/version"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -63,7 +64,8 @@ var _ types.Application = (*Application)(nil)
|
||||
type Application struct {
|
||||
types.BaseApplication
|
||||
|
||||
state State
|
||||
state State
|
||||
RetainBlocks int64 // blocks to retain after commit (via ResponseCommit.RetainHeight)
|
||||
}
|
||||
|
||||
func NewApplication() *Application {
|
||||
@@ -118,7 +120,12 @@ func (app *Application) Commit() types.ResponseCommit {
|
||||
app.state.AppHash = appHash
|
||||
app.state.Height++
|
||||
saveState(app.state)
|
||||
return types.ResponseCommit{Data: appHash}
|
||||
|
||||
resp := types.ResponseCommit{Data: appHash}
|
||||
if app.RetainBlocks > 0 && app.state.Height >= app.RetainBlocks {
|
||||
resp.RetainHeight = app.state.Height - app.RetainBlocks + 1
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
// Returns an associated value or nil if missing.
|
||||
@@ -136,6 +143,7 @@ func (app *Application) Query(reqQuery types.RequestQuery) (resQuery types.Respo
|
||||
resQuery.Index = -1 // TODO make Proof return index
|
||||
resQuery.Key = reqQuery.Data
|
||||
resQuery.Value = value
|
||||
resQuery.Height = app.state.Height
|
||||
|
||||
return
|
||||
}
|
||||
@@ -151,6 +159,7 @@ func (app *Application) Query(reqQuery types.RequestQuery) (resQuery types.Respo
|
||||
resQuery.Log = "exists"
|
||||
}
|
||||
resQuery.Value = value
|
||||
resQuery.Height = app.state.Height
|
||||
|
||||
return resQuery
|
||||
}
|
||||
|
||||
@@ -30,6 +30,11 @@ func testKVStore(t *testing.T, app types.Application, tx []byte, key, value stri
|
||||
// repeating tx doesn't raise error
|
||||
ar = app.DeliverTx(req)
|
||||
require.False(t, ar.IsErr(), ar)
|
||||
// commit
|
||||
app.Commit()
|
||||
|
||||
info := app.Info(types.RequestInfo{})
|
||||
require.NotZero(t, info.LastBlockHeight)
|
||||
|
||||
// make sure query is fine
|
||||
resQuery := app.Query(types.RequestQuery{
|
||||
@@ -37,7 +42,9 @@ func testKVStore(t *testing.T, app types.Application, tx []byte, key, value stri
|
||||
Data: []byte(key),
|
||||
})
|
||||
require.Equal(t, code.CodeTypeOK, resQuery.Code)
|
||||
require.Equal(t, key, string(resQuery.Key))
|
||||
require.Equal(t, value, string(resQuery.Value))
|
||||
require.EqualValues(t, info.LastBlockHeight, resQuery.Height)
|
||||
|
||||
// make sure proof is fine
|
||||
resQuery = app.Query(types.RequestQuery{
|
||||
@@ -46,7 +53,9 @@ func testKVStore(t *testing.T, app types.Application, tx []byte, key, value stri
|
||||
Prove: true,
|
||||
})
|
||||
require.EqualValues(t, code.CodeTypeOK, resQuery.Code)
|
||||
require.Equal(t, key, string(resQuery.Key))
|
||||
require.Equal(t, value, string(resQuery.Value))
|
||||
require.EqualValues(t, info.LastBlockHeight, resQuery.Height)
|
||||
}
|
||||
|
||||
func TestKVStoreKV(t *testing.T) {
|
||||
@@ -300,6 +309,13 @@ func testClient(t *testing.T, app abcicli.Client, tx []byte, key, value string)
|
||||
ar, err = app.DeliverTxSync(types.RequestDeliverTx{Tx: tx})
|
||||
require.NoError(t, err)
|
||||
require.False(t, ar.IsErr(), ar)
|
||||
// commit
|
||||
_, err = app.CommitSync()
|
||||
require.NoError(t, err)
|
||||
|
||||
info, err := app.InfoSync(types.RequestInfo{})
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, info.LastBlockHeight)
|
||||
|
||||
// make sure query is fine
|
||||
resQuery, err := app.QuerySync(types.RequestQuery{
|
||||
@@ -308,7 +324,9 @@ func testClient(t *testing.T, app abcicli.Client, tx []byte, key, value string)
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, code.CodeTypeOK, resQuery.Code)
|
||||
require.Equal(t, key, string(resQuery.Key))
|
||||
require.Equal(t, value, string(resQuery.Value))
|
||||
require.EqualValues(t, info.LastBlockHeight, resQuery.Height)
|
||||
|
||||
// make sure proof is fine
|
||||
resQuery, err = app.QuerySync(types.RequestQuery{
|
||||
@@ -318,5 +336,7 @@ func testClient(t *testing.T, app abcicli.Client, tx []byte, key, value string)
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, code.CodeTypeOK, resQuery.Code)
|
||||
require.Equal(t, key, string(resQuery.Key))
|
||||
require.Equal(t, value, string(resQuery.Value))
|
||||
require.EqualValues(t, info.LastBlockHeight, resQuery.Height)
|
||||
}
|
||||
|
||||
@@ -7,12 +7,13 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
"github.com/tendermint/tendermint/abci/example/code"
|
||||
"github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
> query "abc"
|
||||
-> code: OK
|
||||
-> log: exists
|
||||
-> height: 0
|
||||
-> height: 2
|
||||
-> key: abc
|
||||
-> key.hex: 616263
|
||||
-> value: abc
|
||||
@@ -43,7 +43,7 @@
|
||||
> query "def"
|
||||
-> code: OK
|
||||
-> log: exists
|
||||
-> height: 0
|
||||
-> height: 3
|
||||
-> key: def
|
||||
-> key.hex: 646566
|
||||
-> value: xyz
|
||||
|
||||
+193
-150
@@ -1865,6 +1865,7 @@ func (m *ResponseEndBlock) GetEvents() []Event {
|
||||
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"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
@@ -1910,6 +1911,13 @@ func (m *ResponseCommit) GetData() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ResponseCommit) GetRetainHeight() int64 {
|
||||
if m != nil {
|
||||
return m.RetainHeight
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ConsensusParams contains all consensus-relevant parameters
|
||||
// that can be adjusted by the abci app
|
||||
type ConsensusParams struct {
|
||||
@@ -2571,7 +2579,7 @@ func (m *PartSetHeader) GetHash() []byte {
|
||||
// Validator
|
||||
type Validator struct {
|
||||
Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
|
||||
//PubKey pub_key = 2 [(gogoproto.nullable)=false];
|
||||
// PubKey pub_key = 2 [(gogoproto.nullable)=false];
|
||||
Power int64 `protobuf:"varint,3,opt,name=power,proto3" json:"power,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
@@ -2960,155 +2968,156 @@ func init() { proto.RegisterFile("abci/types/types.proto", fileDescriptor_9f1eaa
|
||||
func init() { golang_proto.RegisterFile("abci/types/types.proto", fileDescriptor_9f1eaa49c51fa1ac) }
|
||||
|
||||
var fileDescriptor_9f1eaa49c51fa1ac = []byte{
|
||||
// 2370 bytes of a gzipped FileDescriptorProto
|
||||
// 2386 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x59, 0x4d, 0x90, 0x1b, 0x47,
|
||||
0x15, 0xde, 0xd1, 0x6a, 0x57, 0xd2, 0xd3, 0xee, 0x4a, 0x69, 0x3b, 0x89, 0x22, 0x92, 0x5d, 0xd7,
|
||||
0xf8, 0x6f, 0x9d, 0x04, 0x6d, 0x58, 0x2a, 0x54, 0x8c, 0x5d, 0xa1, 0x56, 0x6b, 0x07, 0xa9, 0x62,
|
||||
0x3b, 0x9b, 0xb1, 0xbd, 0x18, 0xa8, 0xca, 0x54, 0x4b, 0xd3, 0x96, 0xa6, 0x56, 0x9a, 0x99, 0xcc,
|
||||
0xb4, 0x64, 0x89, 0xe2, 0x4e, 0x51, 0xc5, 0x81, 0x0b, 0x55, 0x5c, 0xb8, 0x73, 0xe4, 0xc0, 0x21,
|
||||
0x47, 0x8e, 0x39, 0x70, 0xe0, 0xc0, 0xd9, 0xc0, 0xc2, 0x89, 0xca, 0x91, 0xa2, 0x38, 0x52, 0xfd,
|
||||
0xba, 0xe7, 0x4f, 0x2b, 0xad, 0xc6, 0xc1, 0x37, 0x2e, 0xd2, 0x74, 0xf7, 0x7b, 0xaf, 0xbb, 0x5f,
|
||||
0xbf, 0x7e, 0xdf, 0x7b, 0xaf, 0xe1, 0x35, 0xda, 0xe9, 0xda, 0x7b, 0x7c, 0xea, 0xb1, 0x40, 0xfe,
|
||||
0x36, 0x3c, 0xdf, 0xe5, 0x2e, 0x79, 0x95, 0x33, 0xc7, 0x62, 0xfe, 0xd0, 0x76, 0x78, 0x43, 0x90,
|
||||
0x34, 0x70, 0xb0, 0x7e, 0x8d, 0xf7, 0x6d, 0xdf, 0x32, 0x3d, 0xea, 0xf3, 0xe9, 0x1e, 0x52, 0xee,
|
||||
0xf5, 0xdc, 0x9e, 0x1b, 0x7f, 0x49, 0xf6, 0x7a, 0xbd, 0xeb, 0x4f, 0x3d, 0xee, 0xee, 0x0d, 0x99,
|
||||
0x7f, 0x32, 0x60, 0xea, 0x4f, 0x8d, 0x5d, 0x18, 0xd8, 0x9d, 0x60, 0xef, 0x64, 0x9c, 0x9c, 0xaf,
|
||||
0xbe, 0xd3, 0x73, 0xdd, 0xde, 0x80, 0x49, 0x99, 0x9d, 0xd1, 0xd3, 0x3d, 0x6e, 0x0f, 0x59, 0xc0,
|
||||
0xe9, 0xd0, 0x53, 0x04, 0xdb, 0xb3, 0x04, 0xd6, 0xc8, 0xa7, 0xdc, 0x76, 0x1d, 0x39, 0xae, 0xff,
|
||||
0x7b, 0x0d, 0x0a, 0x06, 0xfb, 0x7c, 0xc4, 0x02, 0x4e, 0x3e, 0x80, 0x3c, 0xeb, 0xf6, 0xdd, 0x5a,
|
||||
0xee, 0x92, 0xb6, 0x5b, 0xde, 0xd7, 0x1b, 0x73, 0xf7, 0xd2, 0x50, 0xd4, 0x77, 0xbb, 0x7d, 0xb7,
|
||||
0xb5, 0x62, 0x20, 0x07, 0xb9, 0x05, 0x6b, 0x4f, 0x07, 0xa3, 0xa0, 0x5f, 0x5b, 0x45, 0xd6, 0xcb,
|
||||
0xe7, 0xb3, 0x7e, 0x24, 0x48, 0x5b, 0x2b, 0x86, 0xe4, 0x11, 0xd3, 0xda, 0xce, 0x53, 0xb7, 0x96,
|
||||
0xcf, 0x32, 0x6d, 0xdb, 0x79, 0x8a, 0xd3, 0x0a, 0x0e, 0xd2, 0x02, 0x08, 0x18, 0x37, 0x5d, 0x4f,
|
||||
0x6c, 0xa8, 0xb6, 0x86, 0xfc, 0xd7, 0xcf, 0xe7, 0x7f, 0xc8, 0xf8, 0x27, 0x48, 0xde, 0x5a, 0x31,
|
||||
0x4a, 0x41, 0xd8, 0x10, 0x92, 0x6c, 0xc7, 0xe6, 0x66, 0xb7, 0x4f, 0x6d, 0xa7, 0xb6, 0x9e, 0x45,
|
||||
0x52, 0xdb, 0xb1, 0xf9, 0xa1, 0x20, 0x17, 0x92, 0xec, 0xb0, 0x21, 0x54, 0xf1, 0xf9, 0x88, 0xf9,
|
||||
0xd3, 0x5a, 0x21, 0x8b, 0x2a, 0x3e, 0x15, 0xa4, 0x42, 0x15, 0xc8, 0x43, 0x3e, 0x86, 0x72, 0x87,
|
||||
0xf5, 0x6c, 0xc7, 0xec, 0x0c, 0xdc, 0xee, 0x49, 0xad, 0x88, 0x22, 0x76, 0xcf, 0x17, 0xd1, 0x14,
|
||||
0x0c, 0x4d, 0x41, 0xdf, 0x5a, 0x31, 0xa0, 0x13, 0xb5, 0x48, 0x13, 0x8a, 0xdd, 0x3e, 0xeb, 0x9e,
|
||||
0x98, 0x7c, 0x52, 0x2b, 0xa1, 0xa4, 0xab, 0xe7, 0x4b, 0x3a, 0x14, 0xd4, 0x8f, 0x26, 0xad, 0x15,
|
||||
0xa3, 0xd0, 0x95, 0x9f, 0x42, 0x2f, 0x16, 0x1b, 0xd8, 0x63, 0xe6, 0x0b, 0x29, 0x17, 0xb2, 0xe8,
|
||||
0xe5, 0x8e, 0xa4, 0x47, 0x39, 0x25, 0x2b, 0x6c, 0x90, 0xbb, 0x50, 0x62, 0x8e, 0xa5, 0x36, 0x56,
|
||||
0x46, 0x41, 0xd7, 0x96, 0x58, 0x98, 0x63, 0x85, 0xdb, 0x2a, 0x32, 0xf5, 0x4d, 0x3e, 0x84, 0xf5,
|
||||
0xae, 0x3b, 0x1c, 0xda, 0xbc, 0xb6, 0x81, 0x32, 0xae, 0x2c, 0xd9, 0x12, 0xd2, 0xb6, 0x56, 0x0c,
|
||||
0xc5, 0xd5, 0x2c, 0xc0, 0xda, 0x98, 0x0e, 0x46, 0x4c, 0xbf, 0x0e, 0xe5, 0x84, 0x25, 0x93, 0x1a,
|
||||
0x14, 0x86, 0x2c, 0x08, 0x68, 0x8f, 0xd5, 0xb4, 0x4b, 0xda, 0x6e, 0xc9, 0x08, 0x9b, 0xfa, 0x16,
|
||||
0x6c, 0x24, 0xed, 0x56, 0x1f, 0x46, 0x8c, 0xc2, 0x16, 0x05, 0xe3, 0x98, 0xf9, 0x81, 0x30, 0x40,
|
||||
0xc5, 0xa8, 0x9a, 0xe4, 0x32, 0x6c, 0xe2, 0x6e, 0xcd, 0x70, 0x5c, 0xdc, 0xab, 0xbc, 0xb1, 0x81,
|
||||
0x9d, 0xc7, 0x8a, 0x68, 0x07, 0xca, 0xde, 0xbe, 0x17, 0x91, 0xac, 0x22, 0x09, 0x78, 0xfb, 0x9e,
|
||||
0x22, 0xd0, 0xbf, 0x0b, 0xd5, 0x59, 0xd3, 0x25, 0x55, 0x58, 0x3d, 0x61, 0x53, 0x35, 0x9f, 0xf8,
|
||||
0x24, 0x17, 0xd5, 0xb6, 0x70, 0x8e, 0x92, 0xa1, 0xf6, 0xf8, 0xbb, 0x5c, 0xc4, 0x1c, 0x59, 0xab,
|
||||
0xb8, 0x6e, 0xc2, 0x49, 0x20, 0x77, 0x79, 0xbf, 0xde, 0x90, 0x0e, 0xa2, 0x11, 0x3a, 0x88, 0xc6,
|
||||
0xa3, 0xd0, 0x83, 0x34, 0x8b, 0x5f, 0x3e, 0xdf, 0x59, 0xf9, 0xe5, 0x5f, 0x76, 0x34, 0x03, 0x39,
|
||||
0xc8, 0x1b, 0xc2, 0xa0, 0xa8, 0xed, 0x98, 0xb6, 0xa5, 0xe6, 0x29, 0x60, 0xbb, 0x6d, 0x91, 0x4f,
|
||||
0xa1, 0xda, 0x75, 0x9d, 0x80, 0x39, 0xc1, 0x28, 0x10, 0x6e, 0x8e, 0x0e, 0x03, 0xe5, 0x0b, 0x16,
|
||||
0x1d, 0xf2, 0x61, 0x48, 0x7e, 0x84, 0xd4, 0x46, 0xa5, 0x9b, 0xee, 0x20, 0xf7, 0x00, 0xc6, 0x74,
|
||||
0x60, 0x5b, 0x94, 0xbb, 0x7e, 0x50, 0xcb, 0x5f, 0x5a, 0x3d, 0x47, 0xd8, 0x71, 0x48, 0xf8, 0xd8,
|
||||
0xb3, 0x28, 0x67, 0xcd, 0xbc, 0x58, 0xb9, 0x91, 0xe0, 0x27, 0xd7, 0xa0, 0x42, 0x3d, 0xcf, 0x0c,
|
||||
0x38, 0xe5, 0xcc, 0xec, 0x4c, 0x39, 0x0b, 0xd0, 0x5f, 0x6c, 0x18, 0x9b, 0xd4, 0xf3, 0x1e, 0x8a,
|
||||
0xde, 0xa6, 0xe8, 0xd4, 0xad, 0xe8, 0xb4, 0xf1, 0x6a, 0x12, 0x02, 0x79, 0x8b, 0x72, 0x8a, 0xda,
|
||||
0xda, 0x30, 0xf0, 0x5b, 0xf4, 0x79, 0x94, 0xf7, 0x95, 0x0e, 0xf0, 0x9b, 0xbc, 0x06, 0xeb, 0x7d,
|
||||
0x66, 0xf7, 0xfa, 0x1c, 0xb7, 0xbd, 0x6a, 0xa8, 0x96, 0x38, 0x18, 0xcf, 0x77, 0xc7, 0x0c, 0xbd,
|
||||
0x5b, 0xd1, 0x90, 0x0d, 0xfd, 0x57, 0x39, 0x78, 0xe5, 0xcc, 0xf5, 0x15, 0x72, 0xfb, 0x34, 0xe8,
|
||||
0x87, 0x73, 0x89, 0x6f, 0x72, 0x4b, 0xc8, 0xa5, 0x16, 0xf3, 0x95, 0x57, 0x7e, 0x6b, 0x81, 0x06,
|
||||
0x5a, 0x48, 0xa4, 0x36, 0xae, 0x58, 0xc8, 0x63, 0xa8, 0x0e, 0x68, 0xc0, 0x4d, 0x69, 0xfb, 0x26,
|
||||
0x7a, 0xd9, 0xd5, 0x73, 0x3d, 0xc1, 0x3d, 0x1a, 0xde, 0x19, 0x61, 0xdc, 0x4a, 0xdc, 0xd6, 0x20,
|
||||
0xd5, 0x4b, 0x9e, 0xc0, 0xc5, 0xce, 0xf4, 0x27, 0xd4, 0xe1, 0xb6, 0xc3, 0xcc, 0x33, 0x67, 0xb4,
|
||||
0xb3, 0x40, 0xf4, 0xdd, 0xb1, 0x6d, 0x31, 0xa7, 0x1b, 0x1e, 0xce, 0x85, 0x48, 0x44, 0x74, 0x78,
|
||||
0x81, 0xfe, 0x04, 0xb6, 0xd2, 0xbe, 0x88, 0x6c, 0x41, 0x8e, 0x4f, 0x94, 0x46, 0x72, 0x7c, 0x42,
|
||||
0xbe, 0x03, 0x79, 0x21, 0x0e, 0xb5, 0xb1, 0xb5, 0x10, 0x2c, 0x14, 0xf7, 0xa3, 0xa9, 0xc7, 0x0c,
|
||||
0xa4, 0xd7, 0xf5, 0xe8, 0x26, 0x44, 0xfe, 0x69, 0x56, 0xb6, 0x7e, 0x03, 0x2a, 0x33, 0xae, 0x27,
|
||||
0x71, 0xac, 0x5a, 0xf2, 0x58, 0xf5, 0x0a, 0x6c, 0xa6, 0x3c, 0x8c, 0xfe, 0xc7, 0x75, 0x28, 0x1a,
|
||||
0x2c, 0xf0, 0x84, 0x11, 0x93, 0x16, 0x94, 0xd8, 0xa4, 0xcb, 0x24, 0x2c, 0x69, 0x4b, 0x9c, 0xb8,
|
||||
0xe4, 0xb9, 0x1b, 0xd2, 0x0b, 0xaf, 0x19, 0x31, 0x93, 0x9b, 0x29, 0x48, 0xbe, 0xbc, 0x4c, 0x48,
|
||||
0x12, 0x93, 0x6f, 0xa7, 0x31, 0xf9, 0xca, 0x12, 0xde, 0x19, 0x50, 0xbe, 0x99, 0x02, 0xe5, 0x65,
|
||||
0x13, 0xa7, 0x50, 0xb9, 0x3d, 0x07, 0x95, 0x97, 0x6d, 0x7f, 0x01, 0x2c, 0xb7, 0xe7, 0xc0, 0xf2,
|
||||
0xee, 0xd2, 0xb5, 0xcc, 0xc5, 0xe5, 0xdb, 0x69, 0x5c, 0x5e, 0xa6, 0x8e, 0x19, 0x60, 0xbe, 0x37,
|
||||
0x0f, 0x98, 0x6f, 0x2c, 0x91, 0xb1, 0x10, 0x99, 0x0f, 0xcf, 0x20, 0xf3, 0xb5, 0x25, 0xa2, 0xe6,
|
||||
0x40, 0x73, 0x3b, 0x05, 0xcd, 0x90, 0x49, 0x37, 0x0b, 0xb0, 0xf9, 0xa3, 0xb3, 0xd8, 0x7c, 0x7d,
|
||||
0x99, 0xa9, 0xcd, 0x03, 0xe7, 0xef, 0xcd, 0x80, 0xf3, 0xd5, 0x65, 0xbb, 0x5a, 0x88, 0xce, 0x37,
|
||||
0x84, 0x7f, 0x9c, 0xb9, 0x19, 0xc2, 0x97, 0x32, 0xdf, 0x77, 0x7d, 0x05, 0x7c, 0xb2, 0xa1, 0xef,
|
||||
0x0a, 0x8f, 0x1d, 0xdb, 0xff, 0x39, 0x48, 0x8e, 0x97, 0x36, 0x61, 0xed, 0xfa, 0x17, 0x5a, 0xcc,
|
||||
0x8b, 0x9e, 0x2d, 0xe9, 0xed, 0x4b, 0xca, 0xdb, 0x27, 0x00, 0x3e, 0x97, 0x06, 0xf8, 0x1d, 0x28,
|
||||
0x0b, 0x4c, 0x99, 0xc1, 0x6e, 0xea, 0x85, 0xd8, 0x4d, 0xde, 0x86, 0x57, 0xd0, 0xff, 0xca, 0x30,
|
||||
0x40, 0x39, 0x92, 0x3c, 0x3a, 0x92, 0x8a, 0x18, 0x90, 0x1a, 0x94, 0x40, 0xf1, 0x4d, 0xb8, 0x90,
|
||||
0xa0, 0x15, 0x72, 0x11, 0x0b, 0x24, 0x48, 0x55, 0x23, 0xea, 0x03, 0xcf, 0x6b, 0xd1, 0xa0, 0xaf,
|
||||
0xdf, 0x8f, 0x15, 0x14, 0xc7, 0x05, 0x04, 0xf2, 0x5d, 0xd7, 0x92, 0xfb, 0xde, 0x34, 0xf0, 0x5b,
|
||||
0xc4, 0x0a, 0x03, 0xb7, 0x87, 0x8b, 0x2b, 0x19, 0xe2, 0x53, 0x50, 0x45, 0x57, 0xbb, 0x24, 0xef,
|
||||
0xac, 0xfe, 0x7b, 0x2d, 0x96, 0x17, 0x87, 0x0a, 0xf3, 0x50, 0x5d, 0x7b, 0x99, 0xa8, 0x9e, 0xfb,
|
||||
0xdf, 0x50, 0x5d, 0xff, 0x97, 0x16, 0x1f, 0x69, 0x84, 0xd7, 0x5f, 0x4f, 0x05, 0xc2, 0xba, 0x6c,
|
||||
0xc7, 0x62, 0x13, 0x54, 0xf9, 0xaa, 0x21, 0x1b, 0x61, 0xa8, 0xb5, 0x8e, 0xc7, 0x90, 0x0e, 0xb5,
|
||||
0x0a, 0xd8, 0x27, 0x1b, 0xe4, 0x7d, 0xc4, 0x79, 0xf7, 0xa9, 0x72, 0x0d, 0x29, 0x10, 0x94, 0x49,
|
||||
0x5d, 0x43, 0x65, 0x73, 0x47, 0x82, 0xcc, 0x90, 0xd4, 0x09, 0x7c, 0x29, 0xa5, 0xc2, 0x86, 0x37,
|
||||
0xa1, 0x24, 0x96, 0x1e, 0x78, 0xb4, 0xcb, 0xf0, 0x6e, 0x97, 0x8c, 0xb8, 0x43, 0xb7, 0x80, 0x9c,
|
||||
0xf5, 0x31, 0xe4, 0x01, 0xac, 0xb3, 0x31, 0x73, 0xb8, 0x38, 0x23, 0xa1, 0xd6, 0x37, 0x17, 0x02,
|
||||
0x31, 0x73, 0x78, 0xb3, 0x26, 0x94, 0xf9, 0xcf, 0xe7, 0x3b, 0x55, 0xc9, 0xf3, 0xae, 0x3b, 0xb4,
|
||||
0x39, 0x1b, 0x7a, 0x7c, 0x6a, 0x28, 0x29, 0xfa, 0xcf, 0x72, 0x02, 0x0f, 0x53, 0xfe, 0x67, 0xae,
|
||||
0x7a, 0xc3, 0x4b, 0x93, 0x4b, 0x84, 0x48, 0xd9, 0x54, 0xfe, 0x16, 0x40, 0x8f, 0x06, 0xe6, 0x33,
|
||||
0xea, 0x70, 0x66, 0x29, 0xbd, 0x97, 0x7a, 0x34, 0xf8, 0x01, 0x76, 0x88, 0x78, 0x53, 0x0c, 0x8f,
|
||||
0x02, 0x66, 0xe1, 0x01, 0xac, 0x1a, 0x85, 0x1e, 0x0d, 0x1e, 0x07, 0xcc, 0x4a, 0xec, 0xb5, 0xf0,
|
||||
0x32, 0xf6, 0x9a, 0xd6, 0x77, 0x71, 0x56, 0xdf, 0x3f, 0xcf, 0xc5, 0xb7, 0x23, 0x0e, 0x1f, 0xfe,
|
||||
0x3f, 0x75, 0xf1, 0x1b, 0xcc, 0x29, 0xd2, 0x20, 0x40, 0x7e, 0x08, 0xaf, 0x44, 0xb7, 0xd2, 0x1c,
|
||||
0xe1, 0x6d, 0x0d, 0xad, 0xf0, 0xc5, 0x2e, 0x77, 0x75, 0x9c, 0xee, 0x0e, 0xc8, 0x67, 0xf0, 0xfa,
|
||||
0x8c, 0x0f, 0x8a, 0x26, 0xc8, 0xbd, 0x90, 0x2b, 0x7a, 0x35, 0xed, 0x8a, 0x42, 0xf9, 0xb1, 0xf6,
|
||||
0x56, 0x5f, 0xca, 0xad, 0xb9, 0x22, 0x42, 0xd8, 0x24, 0xbc, 0xcd, 0xb3, 0x09, 0xfd, 0xcf, 0x1a,
|
||||
0x54, 0x66, 0x16, 0x48, 0x3e, 0x80, 0x35, 0x89, 0xc0, 0xda, 0xb9, 0x85, 0x10, 0xd4, 0xb8, 0xda,
|
||||
0x93, 0x64, 0x20, 0x07, 0x50, 0x64, 0x2a, 0xba, 0x56, 0x4a, 0xb9, 0xba, 0x24, 0x08, 0x57, 0xfc,
|
||||
0x11, 0x1b, 0xb9, 0x03, 0xa5, 0x48, 0xf5, 0x4b, 0x32, 0xb7, 0xe8, 0xe4, 0x94, 0x90, 0x98, 0x51,
|
||||
0x3f, 0x84, 0x72, 0x62, 0x79, 0xe4, 0x1b, 0x50, 0x1a, 0xd2, 0x89, 0x4a, 0xb7, 0x64, 0x00, 0x5d,
|
||||
0x1c, 0xd2, 0x09, 0x66, 0x5a, 0xe4, 0x75, 0x28, 0x88, 0xc1, 0x1e, 0x95, 0x07, 0xb9, 0x6a, 0xac,
|
||||
0x0f, 0xe9, 0xe4, 0xfb, 0x34, 0xd0, 0x7f, 0xa1, 0xc1, 0x56, 0x7a, 0x9d, 0xe4, 0x1d, 0x20, 0x82,
|
||||
0x96, 0xf6, 0x98, 0xe9, 0x8c, 0x86, 0x12, 0x23, 0x43, 0x89, 0x95, 0x21, 0x9d, 0x1c, 0xf4, 0xd8,
|
||||
0x83, 0xd1, 0x10, 0xa7, 0x0e, 0xc8, 0x7d, 0xa8, 0x86, 0xc4, 0x61, 0xb1, 0x4b, 0x69, 0xe5, 0x8d,
|
||||
0x33, 0xc9, 0xee, 0x1d, 0x45, 0x20, 0x73, 0xdd, 0x5f, 0x8b, 0x5c, 0x77, 0x4b, 0xca, 0x0b, 0x47,
|
||||
0xf4, 0xf7, 0xa1, 0x32, 0xb3, 0x63, 0xa2, 0xc3, 0xa6, 0x37, 0xea, 0x98, 0x27, 0x6c, 0x6a, 0xa2,
|
||||
0x4a, 0xd0, 0xd4, 0x4b, 0x46, 0xd9, 0x1b, 0x75, 0x3e, 0x66, 0x53, 0x91, 0x75, 0x04, 0x7a, 0x17,
|
||||
0xb6, 0xd2, 0xc9, 0x94, 0x00, 0x0e, 0xdf, 0x1d, 0x39, 0x16, 0xae, 0x7b, 0xcd, 0x90, 0x0d, 0x72,
|
||||
0x0b, 0xd6, 0xc6, 0xae, 0xb4, 0xe6, 0xf3, 0xb2, 0xa7, 0x63, 0x97, 0xb3, 0x44, 0x4a, 0x26, 0x79,
|
||||
0xf4, 0x00, 0xd6, 0xd0, 0x2e, 0x85, 0x8d, 0x61, 0x5a, 0xa4, 0x02, 0x17, 0xf1, 0x4d, 0x8e, 0x01,
|
||||
0x28, 0xe7, 0xbe, 0xdd, 0x19, 0xc5, 0xe2, 0x6b, 0x49, 0xf1, 0x03, 0xbb, 0x13, 0x34, 0x4e, 0xc6,
|
||||
0x8d, 0x23, 0x6a, 0xfb, 0xcd, 0x37, 0x95, 0x65, 0x5f, 0x8c, 0x79, 0x12, 0xd6, 0x9d, 0x90, 0xa4,
|
||||
0x7f, 0x95, 0x87, 0x75, 0x99, 0x6e, 0x92, 0x0f, 0xd3, 0xc5, 0x8f, 0xf2, 0xfe, 0xf6, 0xa2, 0xe5,
|
||||
0x4b, 0x2a, 0xb5, 0xfa, 0x28, 0x82, 0xba, 0x36, 0x5b, 0x51, 0x68, 0x96, 0x4f, 0x9f, 0xef, 0x14,
|
||||
0x30, 0xfa, 0x68, 0xdf, 0x89, 0xcb, 0x0b, 0x8b, 0xb2, 0xeb, 0xb0, 0x96, 0x91, 0x7f, 0xe1, 0x5a,
|
||||
0x46, 0x0b, 0x36, 0x13, 0xe1, 0x96, 0x6d, 0xa9, 0x3c, 0x65, 0xfb, 0xbc, 0x4b, 0xd7, 0xbe, 0xa3,
|
||||
0xd6, 0x5f, 0x8e, 0xc2, 0xb1, 0xb6, 0x45, 0x76, 0xd3, 0x49, 0x36, 0x46, 0x6d, 0x32, 0x5c, 0x48,
|
||||
0xe4, 0xcd, 0x22, 0x66, 0x13, 0xd7, 0x41, 0x5c, 0x7e, 0x49, 0x22, 0xa3, 0x87, 0xa2, 0xe8, 0xc0,
|
||||
0xc1, 0xeb, 0x50, 0x89, 0x03, 0x1b, 0x49, 0x52, 0x94, 0x52, 0xe2, 0x6e, 0x24, 0x7c, 0x0f, 0x2e,
|
||||
0x3a, 0x6c, 0xc2, 0xcd, 0x59, 0xea, 0x12, 0x52, 0x13, 0x31, 0x76, 0x9c, 0xe6, 0xb8, 0x0a, 0x5b,
|
||||
0xb1, 0x0b, 0x45, 0x5a, 0x90, 0xa5, 0x8f, 0xa8, 0x17, 0xc9, 0xde, 0x80, 0x62, 0x14, 0x76, 0x96,
|
||||
0x91, 0xa0, 0x40, 0x65, 0xb4, 0x19, 0x05, 0xb2, 0x3e, 0x0b, 0x46, 0x03, 0xae, 0x84, 0x6c, 0x20,
|
||||
0x0d, 0x06, 0xb2, 0x86, 0xec, 0x47, 0xda, 0xcb, 0xb0, 0x19, 0x7a, 0x15, 0x49, 0xb7, 0x89, 0x74,
|
||||
0x1b, 0x61, 0x27, 0x12, 0xdd, 0x80, 0xaa, 0xe7, 0xbb, 0x9e, 0x1b, 0x30, 0xdf, 0xa4, 0x96, 0xe5,
|
||||
0xb3, 0x20, 0xa8, 0x6d, 0x49, 0x79, 0x61, 0xff, 0x81, 0xec, 0xd6, 0xbf, 0x05, 0x85, 0x30, 0x9e,
|
||||
0xbe, 0x08, 0x6b, 0xcd, 0xc8, 0x43, 0xe6, 0x0d, 0xd9, 0x10, 0xf8, 0x7a, 0xe0, 0x79, 0xaa, 0xba,
|
||||
0x26, 0x3e, 0xf5, 0x01, 0x14, 0xd4, 0x81, 0xcd, 0xad, 0xa9, 0xdc, 0x87, 0x0d, 0x8f, 0xfa, 0x62,
|
||||
0x1b, 0xc9, 0xca, 0xca, 0xa2, 0x8c, 0xf0, 0x88, 0xfa, 0xfc, 0x21, 0xe3, 0xa9, 0x02, 0x4b, 0x19,
|
||||
0xf9, 0x65, 0x97, 0x7e, 0x13, 0x36, 0x53, 0x34, 0x62, 0x99, 0xdc, 0xe5, 0x74, 0x10, 0x5e, 0x74,
|
||||
0x6c, 0x44, 0x2b, 0xc9, 0xc5, 0x2b, 0xd1, 0x6f, 0x41, 0x29, 0x3a, 0x2b, 0x91, 0x68, 0x84, 0xaa,
|
||||
0xd0, 0x94, 0xfa, 0x65, 0x13, 0x8b, 0x48, 0xee, 0x33, 0xe6, 0x2b, 0xeb, 0x97, 0x0d, 0x9d, 0x25,
|
||||
0x1c, 0x93, 0x44, 0x33, 0x72, 0x1b, 0x0a, 0xca, 0x31, 0xa9, 0xfb, 0xb8, 0xa8, 0x5c, 0x74, 0x84,
|
||||
0x9e, 0x2a, 0x2c, 0x17, 0x49, 0xbf, 0x15, 0x4f, 0x93, 0x4b, 0x4e, 0xf3, 0x53, 0x28, 0x86, 0xce,
|
||||
0x27, 0x8d, 0x12, 0x72, 0x86, 0x4b, 0xcb, 0x50, 0x42, 0x4d, 0x12, 0x33, 0x0a, 0x6b, 0x0a, 0xec,
|
||||
0x9e, 0xc3, 0x2c, 0x33, 0xbe, 0x82, 0x38, 0x67, 0xd1, 0xa8, 0xc8, 0x81, 0x7b, 0xe1, 0xfd, 0xd2,
|
||||
0xdf, 0x83, 0x75, 0xb9, 0xd6, 0xb9, 0x2e, 0x6e, 0x1e, 0xb4, 0xfe, 0x43, 0x83, 0x62, 0x08, 0x1f,
|
||||
0x73, 0x99, 0x52, 0x9b, 0xc8, 0x7d, 0xdd, 0x4d, 0xbc, 0x7c, 0x97, 0xf4, 0x2e, 0x10, 0xb4, 0x14,
|
||||
0x73, 0xec, 0x72, 0xdb, 0xe9, 0x99, 0xf2, 0x2c, 0x64, 0x24, 0x58, 0xc5, 0x91, 0x63, 0x1c, 0x38,
|
||||
0x12, 0xfd, 0x6f, 0x5f, 0x86, 0x72, 0xa2, 0xca, 0x45, 0x0a, 0xb0, 0xfa, 0x80, 0x3d, 0xab, 0xae,
|
||||
0x90, 0x32, 0x14, 0x0c, 0x86, 0x35, 0x82, 0xaa, 0xb6, 0xff, 0x55, 0x01, 0x2a, 0x07, 0xcd, 0xc3,
|
||||
0xf6, 0x81, 0xe7, 0x0d, 0xec, 0x2e, 0xe2, 0x19, 0xf9, 0x04, 0xf2, 0x98, 0x27, 0x67, 0x78, 0xdf,
|
||||
0xa9, 0x67, 0x29, 0x38, 0x11, 0x03, 0xd6, 0x30, 0x9d, 0x26, 0x59, 0x9e, 0x7d, 0xea, 0x99, 0xea,
|
||||
0x50, 0x62, 0x91, 0x68, 0x70, 0x19, 0x5e, 0x83, 0xea, 0x59, 0x8a, 0x53, 0xe4, 0x33, 0x28, 0xc5,
|
||||
0x79, 0x72, 0xd6, 0x37, 0xa2, 0x7a, 0xe6, 0xb2, 0x95, 0x90, 0x1f, 0x67, 0x06, 0x59, 0x5f, 0x48,
|
||||
0xea, 0x99, 0xeb, 0x35, 0xe4, 0x09, 0x14, 0xc2, 0x1c, 0x2c, 0xdb, 0x2b, 0x4e, 0x3d, 0x63, 0x49,
|
||||
0x49, 0x1c, 0x9f, 0x4c, 0x9d, 0xb3, 0x3c, 0x55, 0xd5, 0x33, 0xd5, 0xcd, 0xc8, 0x63, 0x58, 0x57,
|
||||
0xc1, 0x6f, 0xa6, 0xf7, 0x99, 0x7a, 0xb6, 0x42, 0x91, 0x50, 0x72, 0x5c, 0x9c, 0xc8, 0xfa, 0x3c,
|
||||
0x57, 0xcf, 0x5c, 0x30, 0x24, 0x14, 0x20, 0x91, 0x4f, 0x67, 0x7e, 0x77, 0xab, 0x67, 0x2f, 0x04,
|
||||
0x92, 0x1f, 0x43, 0x31, 0xca, 0x9a, 0x32, 0xbe, 0x7f, 0xd5, 0xb3, 0xd6, 0xe2, 0x9a, 0xed, 0xff,
|
||||
0xfc, 0x6d, 0x5b, 0xfb, 0xed, 0xe9, 0xb6, 0xf6, 0xc5, 0xe9, 0xb6, 0xf6, 0xe5, 0xe9, 0xb6, 0xf6,
|
||||
0xa7, 0xd3, 0x6d, 0xed, 0xaf, 0xa7, 0xdb, 0xda, 0x1f, 0xfe, 0xbe, 0xad, 0xfd, 0xe8, 0x9d, 0x9e,
|
||||
0xcd, 0xfb, 0xa3, 0x4e, 0xa3, 0xeb, 0x0e, 0xf7, 0x62, 0x81, 0xc9, 0xcf, 0xf8, 0x51, 0xbb, 0xb3,
|
||||
0x8e, 0x0e, 0xeb, 0xdb, 0xff, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xce, 0x64, 0xb9, 0xe4, 0xe9, 0x1e,
|
||||
0x15, 0xde, 0xd1, 0x6a, 0x57, 0xd2, 0xd3, 0xfe, 0xc8, 0x6d, 0x27, 0x91, 0x85, 0xb3, 0xeb, 0x9a,
|
||||
0x8d, 0xed, 0x75, 0x12, 0xb4, 0x61, 0xa9, 0x50, 0x31, 0x76, 0x85, 0x5a, 0xad, 0x1d, 0xa4, 0x8a,
|
||||
0xed, 0x6c, 0xc6, 0xf6, 0x62, 0xa0, 0x2a, 0x53, 0x2d, 0x4d, 0x5b, 0x9a, 0x5a, 0x69, 0x66, 0x32,
|
||||
0xd3, 0x92, 0x25, 0x8a, 0x3b, 0x45, 0x15, 0x07, 0x2e, 0x54, 0x71, 0xe1, 0xce, 0x91, 0x03, 0x87,
|
||||
0x1c, 0x39, 0xe6, 0xc0, 0x81, 0x03, 0x67, 0x03, 0x0b, 0x27, 0x2a, 0x47, 0x8a, 0xe2, 0x48, 0xf5,
|
||||
0xeb, 0x9e, 0x3f, 0xad, 0xb4, 0x1a, 0x07, 0xdf, 0xb8, 0x48, 0xd3, 0x3d, 0xef, 0xbd, 0xee, 0x7e,
|
||||
0xfd, 0xde, 0xfb, 0xde, 0x7b, 0x03, 0xaf, 0xd3, 0x76, 0xc7, 0xde, 0xe3, 0x13, 0x8f, 0x05, 0xf2,
|
||||
0xb7, 0xee, 0xf9, 0x2e, 0x77, 0xc9, 0x6b, 0x9c, 0x39, 0x16, 0xf3, 0x07, 0xb6, 0xc3, 0xeb, 0x82,
|
||||
0xa4, 0x8e, 0x2f, 0x6b, 0xd7, 0x79, 0xcf, 0xf6, 0x2d, 0xd3, 0xa3, 0x3e, 0x9f, 0xec, 0x21, 0xe5,
|
||||
0x5e, 0xd7, 0xed, 0xba, 0xf1, 0x93, 0x64, 0xaf, 0xd5, 0x3a, 0xfe, 0xc4, 0xe3, 0xee, 0xde, 0x80,
|
||||
0xf9, 0x27, 0x7d, 0xa6, 0xfe, 0xd4, 0xbb, 0x8b, 0x7d, 0xbb, 0x1d, 0xec, 0x9d, 0x8c, 0x92, 0xeb,
|
||||
0xd5, 0xb6, 0xbb, 0xae, 0xdb, 0xed, 0x33, 0x29, 0xb3, 0x3d, 0x7c, 0xb6, 0xc7, 0xed, 0x01, 0x0b,
|
||||
0x38, 0x1d, 0x78, 0x8a, 0x60, 0x6b, 0x9a, 0xc0, 0x1a, 0xfa, 0x94, 0xdb, 0xae, 0x23, 0xdf, 0xeb,
|
||||
0xff, 0x5e, 0x81, 0x82, 0xc1, 0x3e, 0x1f, 0xb2, 0x80, 0x93, 0x0f, 0x20, 0xcf, 0x3a, 0x3d, 0xb7,
|
||||
0x9a, 0xbb, 0xaa, 0xed, 0x96, 0xf7, 0xf5, 0xfa, 0xcc, 0xb3, 0xd4, 0x15, 0xf5, 0xbd, 0x4e, 0xcf,
|
||||
0x6d, 0x2e, 0x19, 0xc8, 0x41, 0x6e, 0xc3, 0xca, 0xb3, 0xfe, 0x30, 0xe8, 0x55, 0x97, 0x91, 0x75,
|
||||
0xe7, 0x7c, 0xd6, 0x8f, 0x04, 0x69, 0x73, 0xc9, 0x90, 0x3c, 0x62, 0x59, 0xdb, 0x79, 0xe6, 0x56,
|
||||
0xf3, 0x59, 0x96, 0x6d, 0x39, 0xcf, 0x70, 0x59, 0xc1, 0x41, 0x9a, 0x00, 0x01, 0xe3, 0xa6, 0xeb,
|
||||
0x89, 0x03, 0x55, 0x57, 0x90, 0xff, 0xc6, 0xf9, 0xfc, 0x8f, 0x18, 0xff, 0x04, 0xc9, 0x9b, 0x4b,
|
||||
0x46, 0x29, 0x08, 0x07, 0x42, 0x92, 0xed, 0xd8, 0xdc, 0xec, 0xf4, 0xa8, 0xed, 0x54, 0x57, 0xb3,
|
||||
0x48, 0x6a, 0x39, 0x36, 0x3f, 0x14, 0xe4, 0x42, 0x92, 0x1d, 0x0e, 0x84, 0x2a, 0x3e, 0x1f, 0x32,
|
||||
0x7f, 0x52, 0x2d, 0x64, 0x51, 0xc5, 0xa7, 0x82, 0x54, 0xa8, 0x02, 0x79, 0xc8, 0xc7, 0x50, 0x6e,
|
||||
0xb3, 0xae, 0xed, 0x98, 0xed, 0xbe, 0xdb, 0x39, 0xa9, 0x16, 0x51, 0xc4, 0xee, 0xf9, 0x22, 0x1a,
|
||||
0x82, 0xa1, 0x21, 0xe8, 0x9b, 0x4b, 0x06, 0xb4, 0xa3, 0x11, 0x69, 0x40, 0xb1, 0xd3, 0x63, 0x9d,
|
||||
0x13, 0x93, 0x8f, 0xab, 0x25, 0x94, 0x74, 0xed, 0x7c, 0x49, 0x87, 0x82, 0xfa, 0xf1, 0xb8, 0xb9,
|
||||
0x64, 0x14, 0x3a, 0xf2, 0x51, 0xe8, 0xc5, 0x62, 0x7d, 0x7b, 0xc4, 0x7c, 0x21, 0xe5, 0x62, 0x16,
|
||||
0xbd, 0xdc, 0x95, 0xf4, 0x28, 0xa7, 0x64, 0x85, 0x03, 0x72, 0x0f, 0x4a, 0xcc, 0xb1, 0xd4, 0xc1,
|
||||
0xca, 0x28, 0xe8, 0xfa, 0x02, 0x0b, 0x73, 0xac, 0xf0, 0x58, 0x45, 0xa6, 0x9e, 0xc9, 0x87, 0xb0,
|
||||
0xda, 0x71, 0x07, 0x03, 0x9b, 0x57, 0xd7, 0x50, 0xc6, 0x5b, 0x0b, 0x8e, 0x84, 0xb4, 0xcd, 0x25,
|
||||
0x43, 0x71, 0x35, 0x0a, 0xb0, 0x32, 0xa2, 0xfd, 0x21, 0xd3, 0x6f, 0x40, 0x39, 0x61, 0xc9, 0xa4,
|
||||
0x0a, 0x85, 0x01, 0x0b, 0x02, 0xda, 0x65, 0x55, 0xed, 0xaa, 0xb6, 0x5b, 0x32, 0xc2, 0xa1, 0xbe,
|
||||
0x01, 0x6b, 0x49, 0xbb, 0xd5, 0x07, 0x11, 0xa3, 0xb0, 0x45, 0xc1, 0x38, 0x62, 0x7e, 0x20, 0x0c,
|
||||
0x50, 0x31, 0xaa, 0x21, 0xd9, 0x81, 0x75, 0x3c, 0xad, 0x19, 0xbe, 0x17, 0x7e, 0x95, 0x37, 0xd6,
|
||||
0x70, 0xf2, 0x58, 0x11, 0x6d, 0x43, 0xd9, 0xdb, 0xf7, 0x22, 0x92, 0x65, 0x24, 0x01, 0x6f, 0xdf,
|
||||
0x53, 0x04, 0xfa, 0x77, 0xa1, 0x32, 0x6d, 0xba, 0xa4, 0x02, 0xcb, 0x27, 0x6c, 0xa2, 0xd6, 0x13,
|
||||
0x8f, 0xe4, 0x92, 0x3a, 0x16, 0xae, 0x51, 0x32, 0xd4, 0x19, 0x7f, 0x97, 0x8b, 0x98, 0x23, 0x6b,
|
||||
0x15, 0xee, 0x26, 0x82, 0x04, 0x72, 0x97, 0xf7, 0x6b, 0x75, 0x19, 0x20, 0xea, 0x61, 0x80, 0xa8,
|
||||
0x3f, 0x0e, 0x23, 0x48, 0xa3, 0xf8, 0xe5, 0x8b, 0xed, 0xa5, 0x5f, 0xfe, 0x65, 0x5b, 0x33, 0x90,
|
||||
0x83, 0x5c, 0x16, 0x06, 0x45, 0x6d, 0xc7, 0xb4, 0x2d, 0xb5, 0x4e, 0x01, 0xc7, 0x2d, 0x8b, 0x7c,
|
||||
0x0a, 0x95, 0x8e, 0xeb, 0x04, 0xcc, 0x09, 0x86, 0x81, 0x08, 0x73, 0x74, 0x10, 0xa8, 0x58, 0x30,
|
||||
0xef, 0x92, 0x0f, 0x43, 0xf2, 0x23, 0xa4, 0x36, 0x36, 0x3b, 0xe9, 0x09, 0x72, 0x1f, 0x60, 0x44,
|
||||
0xfb, 0xb6, 0x45, 0xb9, 0xeb, 0x07, 0xd5, 0xfc, 0xd5, 0xe5, 0x73, 0x84, 0x1d, 0x87, 0x84, 0x4f,
|
||||
0x3c, 0x8b, 0x72, 0xd6, 0xc8, 0x8b, 0x9d, 0x1b, 0x09, 0x7e, 0x72, 0x1d, 0x36, 0xa9, 0xe7, 0x99,
|
||||
0x01, 0xa7, 0x9c, 0x99, 0xed, 0x09, 0x67, 0x01, 0xc6, 0x8b, 0x35, 0x63, 0x9d, 0x7a, 0xde, 0x23,
|
||||
0x31, 0xdb, 0x10, 0x93, 0xba, 0x15, 0xdd, 0x36, 0xba, 0x26, 0x21, 0x90, 0xb7, 0x28, 0xa7, 0xa8,
|
||||
0xad, 0x35, 0x03, 0x9f, 0xc5, 0x9c, 0x47, 0x79, 0x4f, 0xe9, 0x00, 0x9f, 0xc9, 0xeb, 0xb0, 0xda,
|
||||
0x63, 0x76, 0xb7, 0xc7, 0xf1, 0xd8, 0xcb, 0x86, 0x1a, 0x89, 0x8b, 0xf1, 0x7c, 0x77, 0xc4, 0x30,
|
||||
0xba, 0x15, 0x0d, 0x39, 0xd0, 0x7f, 0x95, 0x83, 0x0b, 0x67, 0xdc, 0x57, 0xc8, 0xed, 0xd1, 0xa0,
|
||||
0x17, 0xae, 0x25, 0x9e, 0xc9, 0x6d, 0x21, 0x97, 0x5a, 0xcc, 0x57, 0x51, 0xf9, 0xcd, 0x39, 0x1a,
|
||||
0x68, 0x22, 0x91, 0x3a, 0xb8, 0x62, 0x21, 0x4f, 0xa0, 0xd2, 0xa7, 0x01, 0x37, 0xa5, 0xed, 0x9b,
|
||||
0x18, 0x65, 0x97, 0xcf, 0x8d, 0x04, 0xf7, 0x69, 0xe8, 0x33, 0xc2, 0xb8, 0x95, 0xb8, 0x8d, 0x7e,
|
||||
0x6a, 0x96, 0x3c, 0x85, 0x4b, 0xed, 0xc9, 0x4f, 0xa8, 0xc3, 0x6d, 0x87, 0x99, 0x67, 0xee, 0x68,
|
||||
0x7b, 0x8e, 0xe8, 0x7b, 0x23, 0xdb, 0x62, 0x4e, 0x27, 0xbc, 0x9c, 0x8b, 0x91, 0x88, 0xe8, 0xf2,
|
||||
0x02, 0xfd, 0x29, 0x6c, 0xa4, 0x63, 0x11, 0xd9, 0x80, 0x1c, 0x1f, 0x2b, 0x8d, 0xe4, 0xf8, 0x98,
|
||||
0x7c, 0x07, 0xf2, 0x42, 0x1c, 0x6a, 0x63, 0x63, 0x2e, 0x58, 0x28, 0xee, 0xc7, 0x13, 0x8f, 0x19,
|
||||
0x48, 0xaf, 0xeb, 0x91, 0x27, 0x44, 0xf1, 0x69, 0x5a, 0xb6, 0x7e, 0x13, 0x36, 0xa7, 0x42, 0x4f,
|
||||
0xe2, 0x5a, 0xb5, 0xe4, 0xb5, 0xea, 0x9b, 0xb0, 0x9e, 0x8a, 0x30, 0xfa, 0x1f, 0x57, 0xa1, 0x68,
|
||||
0xb0, 0xc0, 0x13, 0x46, 0x4c, 0x9a, 0x50, 0x62, 0xe3, 0x0e, 0x93, 0xb0, 0xa4, 0x2d, 0x08, 0xe2,
|
||||
0x92, 0xe7, 0x5e, 0x48, 0x2f, 0xa2, 0x66, 0xc4, 0x4c, 0x6e, 0xa5, 0x20, 0x79, 0x67, 0x91, 0x90,
|
||||
0x24, 0x26, 0xdf, 0x49, 0x63, 0xf2, 0x5b, 0x0b, 0x78, 0xa7, 0x40, 0xf9, 0x56, 0x0a, 0x94, 0x17,
|
||||
0x2d, 0x9c, 0x42, 0xe5, 0xd6, 0x0c, 0x54, 0x5e, 0x74, 0xfc, 0x39, 0xb0, 0xdc, 0x9a, 0x01, 0xcb,
|
||||
0xbb, 0x0b, 0xf7, 0x32, 0x13, 0x97, 0xef, 0xa4, 0x71, 0x79, 0x91, 0x3a, 0xa6, 0x80, 0xf9, 0xfe,
|
||||
0x2c, 0x60, 0xbe, 0xb9, 0x40, 0xc6, 0x5c, 0x64, 0x3e, 0x3c, 0x83, 0xcc, 0xd7, 0x17, 0x88, 0x9a,
|
||||
0x01, 0xcd, 0xad, 0x14, 0x34, 0x43, 0x26, 0xdd, 0xcc, 0xc1, 0xe6, 0x8f, 0xce, 0x62, 0xf3, 0x8d,
|
||||
0x45, 0xa6, 0x36, 0x0b, 0x9c, 0xbf, 0x37, 0x05, 0xce, 0xd7, 0x16, 0x9d, 0x6a, 0x2e, 0x3a, 0xdf,
|
||||
0x14, 0xf1, 0x71, 0xca, 0x33, 0x44, 0x2c, 0x65, 0xbe, 0xef, 0xfa, 0x0a, 0xf8, 0xe4, 0x40, 0xdf,
|
||||
0x15, 0x11, 0x3b, 0xb6, 0xff, 0x73, 0x90, 0x1c, 0x9d, 0x36, 0x61, 0xed, 0xfa, 0x17, 0x5a, 0xcc,
|
||||
0x8b, 0x91, 0x2d, 0x19, 0xed, 0x4b, 0x2a, 0xda, 0x27, 0x00, 0x3e, 0x97, 0x06, 0xf8, 0x6d, 0x28,
|
||||
0x0b, 0x4c, 0x99, 0xc2, 0x6e, 0xea, 0x85, 0xd8, 0x4d, 0xde, 0x86, 0x0b, 0x18, 0x7f, 0x65, 0x1a,
|
||||
0xa0, 0x02, 0x49, 0x1e, 0x03, 0xc9, 0xa6, 0x78, 0x21, 0x35, 0x28, 0x81, 0xe2, 0x9b, 0x70, 0x31,
|
||||
0x41, 0x2b, 0xe4, 0x22, 0x16, 0x48, 0x90, 0xaa, 0x44, 0xd4, 0x07, 0x9e, 0xd7, 0xa4, 0x41, 0x4f,
|
||||
0x7f, 0x10, 0x2b, 0x28, 0xce, 0x0b, 0x08, 0xe4, 0x3b, 0xae, 0x25, 0xcf, 0xbd, 0x6e, 0xe0, 0xb3,
|
||||
0xc8, 0x15, 0xfa, 0x6e, 0x17, 0x37, 0x57, 0x32, 0xc4, 0xa3, 0xa0, 0x8a, 0x5c, 0xbb, 0x24, 0x7d,
|
||||
0x56, 0xff, 0xbd, 0x16, 0xcb, 0x8b, 0x53, 0x85, 0x59, 0xa8, 0xae, 0xbd, 0x4a, 0x54, 0xcf, 0xfd,
|
||||
0x6f, 0xa8, 0xae, 0xff, 0x4b, 0x8b, 0xaf, 0x34, 0xc2, 0xeb, 0xaf, 0xa7, 0x02, 0x61, 0x5d, 0xb6,
|
||||
0x63, 0xb1, 0x31, 0xaa, 0x7c, 0xd9, 0x90, 0x83, 0x30, 0xd5, 0x5a, 0xc5, 0x6b, 0x48, 0xa7, 0x5a,
|
||||
0x05, 0x9c, 0x93, 0x03, 0xf2, 0x3e, 0xe2, 0xbc, 0xfb, 0x4c, 0x85, 0x86, 0x14, 0x08, 0xca, 0xa2,
|
||||
0xae, 0xae, 0xaa, 0xb9, 0x23, 0x41, 0x66, 0x48, 0xea, 0x04, 0xbe, 0x94, 0x52, 0x69, 0xc3, 0x15,
|
||||
0x28, 0x89, 0xad, 0x07, 0x1e, 0xed, 0x30, 0xf4, 0xed, 0x92, 0x11, 0x4f, 0xe8, 0x16, 0x90, 0xb3,
|
||||
0x31, 0x86, 0x3c, 0x84, 0x55, 0x36, 0x62, 0x0e, 0x17, 0x77, 0x24, 0xd4, 0x7a, 0x65, 0x2e, 0x10,
|
||||
0x33, 0x87, 0x37, 0xaa, 0x42, 0x99, 0xff, 0x7c, 0xb1, 0x5d, 0x91, 0x3c, 0xef, 0xba, 0x03, 0x9b,
|
||||
0xb3, 0x81, 0xc7, 0x27, 0x86, 0x92, 0xa2, 0xff, 0x2c, 0x27, 0xf0, 0x30, 0x15, 0x7f, 0x66, 0xaa,
|
||||
0x37, 0x74, 0x9a, 0x5c, 0x22, 0x45, 0xca, 0xa6, 0xf2, 0x37, 0x01, 0xba, 0x34, 0x30, 0x9f, 0x53,
|
||||
0x87, 0x33, 0x4b, 0xe9, 0xbd, 0xd4, 0xa5, 0xc1, 0x0f, 0x70, 0x42, 0xe4, 0x9b, 0xe2, 0xf5, 0x30,
|
||||
0x60, 0x16, 0x5e, 0xc0, 0xb2, 0x51, 0xe8, 0xd2, 0xe0, 0x49, 0xc0, 0xac, 0xc4, 0x59, 0x0b, 0xaf,
|
||||
0xe2, 0xac, 0x69, 0x7d, 0x17, 0xa7, 0xf5, 0xfd, 0xf3, 0x5c, 0xec, 0x1d, 0x71, 0xfa, 0xf0, 0xff,
|
||||
0xa9, 0x8b, 0xdf, 0x60, 0x4d, 0x91, 0x06, 0x01, 0xf2, 0x43, 0xb8, 0x10, 0x79, 0xa5, 0x39, 0x44,
|
||||
0x6f, 0x0d, 0xad, 0xf0, 0xe5, 0x9c, 0xbb, 0x32, 0x4a, 0x4f, 0x07, 0xe4, 0x33, 0x78, 0x63, 0x2a,
|
||||
0x06, 0x45, 0x0b, 0xe4, 0x5e, 0x2a, 0x14, 0xbd, 0x96, 0x0e, 0x45, 0xa1, 0xfc, 0x58, 0x7b, 0xcb,
|
||||
0xaf, 0xc4, 0x6b, 0x5a, 0x22, 0x85, 0x4d, 0xc2, 0xdb, 0x4c, 0x9b, 0xd8, 0x81, 0x75, 0x9f, 0x71,
|
||||
0x51, 0x4b, 0xa5, 0xaa, 0x86, 0x35, 0x39, 0x29, 0x21, 0x41, 0xff, 0xb3, 0x06, 0x9b, 0x53, 0xa7,
|
||||
0x20, 0x1f, 0xc0, 0x8a, 0x84, 0x69, 0xed, 0xdc, 0x6e, 0x09, 0x5e, 0x8b, 0x3a, 0xb8, 0x64, 0x20,
|
||||
0x07, 0x50, 0x64, 0x2a, 0x05, 0x57, 0x9a, 0xbb, 0xb6, 0x20, 0x53, 0x57, 0xfc, 0x11, 0x1b, 0xb9,
|
||||
0x0b, 0xa5, 0xe8, 0x7e, 0x16, 0x94, 0x77, 0xd1, 0xf5, 0x2a, 0x21, 0x31, 0xa3, 0x7e, 0x08, 0xe5,
|
||||
0xc4, 0xf6, 0xc8, 0x37, 0xa0, 0x34, 0xa0, 0x63, 0x55, 0x93, 0xc9, 0x2c, 0xbb, 0x38, 0xa0, 0x63,
|
||||
0x2c, 0xc7, 0xc8, 0x1b, 0x50, 0x10, 0x2f, 0xbb, 0x54, 0xde, 0xf6, 0xb2, 0xb1, 0x3a, 0xa0, 0xe3,
|
||||
0xef, 0xd3, 0x40, 0xff, 0x85, 0x06, 0x1b, 0xe9, 0x7d, 0x92, 0x77, 0x80, 0x08, 0x5a, 0xda, 0x65,
|
||||
0xa6, 0x33, 0x1c, 0x48, 0x20, 0x0d, 0x25, 0x6e, 0x0e, 0xe8, 0xf8, 0xa0, 0xcb, 0x1e, 0x0e, 0x07,
|
||||
0xb8, 0x74, 0x40, 0x1e, 0x40, 0x25, 0x24, 0x0e, 0x3b, 0x62, 0x4a, 0x2b, 0x97, 0xcf, 0x54, 0xc4,
|
||||
0x77, 0x15, 0x81, 0x2c, 0x88, 0x7f, 0x2d, 0x0a, 0xe2, 0x0d, 0x29, 0x2f, 0x7c, 0xa3, 0xbf, 0x0f,
|
||||
0x9b, 0x53, 0x27, 0x26, 0x3a, 0xac, 0x7b, 0xc3, 0xb6, 0x79, 0xc2, 0x26, 0x26, 0xaa, 0x04, 0xfd,
|
||||
0xa1, 0x64, 0x94, 0xbd, 0x61, 0xfb, 0x63, 0x36, 0x11, 0xa5, 0x49, 0xa0, 0x77, 0x60, 0x23, 0x5d,
|
||||
0x71, 0x09, 0x74, 0xf1, 0xdd, 0xa1, 0x63, 0xe1, 0xbe, 0x57, 0x0c, 0x39, 0x20, 0xb7, 0x61, 0x65,
|
||||
0xe4, 0x4a, 0x93, 0x3f, 0xaf, 0xc4, 0x3a, 0x76, 0x39, 0x4b, 0xd4, 0x6d, 0x92, 0x47, 0x0f, 0x60,
|
||||
0x05, 0x8d, 0x57, 0x18, 0x22, 0xd6, 0x4e, 0x2a, 0xbb, 0x11, 0xcf, 0xe4, 0x18, 0x80, 0x72, 0xee,
|
||||
0xdb, 0xed, 0x61, 0x2c, 0xbe, 0x9a, 0x14, 0xdf, 0xb7, 0xdb, 0x41, 0xfd, 0x64, 0x54, 0x3f, 0xa2,
|
||||
0xb6, 0xdf, 0xb8, 0xa2, 0xcc, 0xff, 0x52, 0xcc, 0x93, 0x70, 0x81, 0x84, 0x24, 0xfd, 0xab, 0x3c,
|
||||
0xac, 0xca, 0x9a, 0x94, 0x7c, 0x98, 0xee, 0x90, 0x94, 0xf7, 0xb7, 0xe6, 0x6d, 0x5f, 0x52, 0xa9,
|
||||
0xdd, 0x47, 0x69, 0xd6, 0xf5, 0xe9, 0xb6, 0x43, 0xa3, 0x7c, 0xfa, 0x62, 0xbb, 0x80, 0x29, 0x4a,
|
||||
0xeb, 0x6e, 0xdc, 0x83, 0x98, 0x57, 0x82, 0x87, 0x0d, 0x8f, 0xfc, 0x4b, 0x37, 0x3c, 0x9a, 0xb0,
|
||||
0x9e, 0xc8, 0xc9, 0x6c, 0x4b, 0x15, 0x33, 0x5b, 0xe7, 0x39, 0x5d, 0xeb, 0xae, 0xda, 0x7f, 0x39,
|
||||
0xca, 0xd9, 0x5a, 0x16, 0xd9, 0x4d, 0x57, 0xe2, 0x98, 0xda, 0xc9, 0x9c, 0x22, 0x51, 0x5c, 0x8b,
|
||||
0xc4, 0x4e, 0xb8, 0x83, 0x88, 0x10, 0x92, 0x44, 0xa6, 0x18, 0x45, 0x31, 0x81, 0x2f, 0x6f, 0xc0,
|
||||
0x66, 0x9c, 0xfd, 0x48, 0x92, 0xa2, 0x94, 0x12, 0x4f, 0x23, 0xe1, 0x7b, 0x70, 0xc9, 0x61, 0x63,
|
||||
0x6e, 0x4e, 0x53, 0x97, 0x90, 0x9a, 0x88, 0x77, 0xc7, 0x69, 0x8e, 0x6b, 0xb0, 0x11, 0xc7, 0x59,
|
||||
0xa4, 0x05, 0xd9, 0x1f, 0x89, 0x66, 0x91, 0xec, 0x32, 0x14, 0xa3, 0xdc, 0xb4, 0x8c, 0x04, 0x05,
|
||||
0x2a, 0x53, 0xd2, 0x28, 0xdb, 0xf5, 0x59, 0x30, 0xec, 0x73, 0x25, 0x64, 0x0d, 0x69, 0x30, 0xdb,
|
||||
0x35, 0xe4, 0x3c, 0xd2, 0xee, 0xc0, 0x7a, 0x18, 0x55, 0x24, 0xdd, 0x3a, 0xd2, 0xad, 0x85, 0x93,
|
||||
0x48, 0x74, 0x13, 0x2a, 0x9e, 0xef, 0x7a, 0x6e, 0xc0, 0x7c, 0x93, 0x5a, 0x96, 0xcf, 0x82, 0xa0,
|
||||
0xba, 0x21, 0xe5, 0x85, 0xf3, 0x07, 0x72, 0x5a, 0xff, 0x16, 0x14, 0xc2, 0xa4, 0xfb, 0x12, 0xac,
|
||||
0x34, 0xa2, 0x08, 0x99, 0x37, 0xe4, 0x40, 0x80, 0xf0, 0x81, 0xe7, 0xa9, 0x16, 0x9c, 0x78, 0xd4,
|
||||
0xfb, 0x50, 0x50, 0x17, 0x36, 0xb3, 0xf1, 0xf2, 0x00, 0xd6, 0x3c, 0xea, 0x8b, 0x63, 0x24, 0xdb,
|
||||
0x2f, 0xf3, 0xca, 0xc6, 0x23, 0xea, 0xf3, 0x47, 0x8c, 0xa7, 0xba, 0x30, 0x65, 0xe4, 0x97, 0x53,
|
||||
0xfa, 0x2d, 0x58, 0x4f, 0xd1, 0x88, 0x6d, 0x72, 0x97, 0xd3, 0x7e, 0xe8, 0xe8, 0x38, 0x88, 0x76,
|
||||
0x92, 0x8b, 0x77, 0xa2, 0xdf, 0x86, 0x52, 0x74, 0x57, 0xa2, 0x1a, 0x09, 0x55, 0xa1, 0x29, 0xf5,
|
||||
0xcb, 0x21, 0x76, 0x9a, 0xdc, 0xe7, 0xcc, 0x57, 0xd6, 0x2f, 0x07, 0x3a, 0x4b, 0x04, 0x26, 0x09,
|
||||
0x79, 0xe4, 0x0e, 0x14, 0x54, 0x60, 0x52, 0xfe, 0x38, 0xaf, 0xa7, 0x74, 0x84, 0x91, 0x2a, 0xec,
|
||||
0x29, 0xc9, 0xb8, 0x15, 0x2f, 0x93, 0x4b, 0x2e, 0xf3, 0x53, 0x28, 0x86, 0xc1, 0x27, 0x8d, 0x12,
|
||||
0x72, 0x85, 0xab, 0x8b, 0x50, 0x42, 0x2d, 0x12, 0x33, 0x0a, 0x6b, 0x0a, 0xec, 0xae, 0xc3, 0x2c,
|
||||
0x33, 0x76, 0x41, 0x5c, 0xb3, 0x68, 0x6c, 0xca, 0x17, 0xf7, 0x43, 0xff, 0xd2, 0xdf, 0x83, 0x55,
|
||||
0xb9, 0xd7, 0x99, 0x21, 0x6e, 0x06, 0xfe, 0xea, 0xff, 0xd0, 0xa0, 0x18, 0xc2, 0xc7, 0x4c, 0xa6,
|
||||
0xd4, 0x21, 0x72, 0x5f, 0xf7, 0x10, 0xaf, 0x3e, 0x24, 0xbd, 0x0b, 0x04, 0x2d, 0xc5, 0x1c, 0xb9,
|
||||
0xdc, 0x76, 0xba, 0xa6, 0xbc, 0x0b, 0x99, 0x2e, 0x56, 0xf0, 0xcd, 0x31, 0xbe, 0x38, 0x12, 0xf3,
|
||||
0x6f, 0xef, 0x40, 0x39, 0xd1, 0x0a, 0x23, 0x05, 0x58, 0x7e, 0xc8, 0x9e, 0x57, 0x96, 0x48, 0x19,
|
||||
0x0a, 0x06, 0xc3, 0x46, 0x42, 0x45, 0xdb, 0xff, 0xaa, 0x00, 0x9b, 0x07, 0x8d, 0xc3, 0xd6, 0x81,
|
||||
0xe7, 0xf5, 0xed, 0x0e, 0xe2, 0x19, 0xf9, 0x04, 0xf2, 0x58, 0x4c, 0x67, 0xf8, 0x08, 0x54, 0xcb,
|
||||
0xd2, 0x95, 0x22, 0x06, 0xac, 0x60, 0xcd, 0x4d, 0xb2, 0x7c, 0x1b, 0xaa, 0x65, 0x6a, 0x56, 0x89,
|
||||
0x4d, 0xa2, 0xc1, 0x65, 0xf8, 0x64, 0x54, 0xcb, 0xd2, 0xc1, 0x22, 0x9f, 0x41, 0x29, 0x2e, 0xa6,
|
||||
0xb3, 0x7e, 0x48, 0xaa, 0x65, 0xee, 0x6d, 0x09, 0xf9, 0x71, 0xf9, 0x90, 0xf5, 0x33, 0x4a, 0x2d,
|
||||
0x73, 0x53, 0x87, 0x3c, 0x85, 0x42, 0x58, 0xa8, 0x65, 0xfb, 0xd4, 0x53, 0xcb, 0xd8, 0x77, 0x12,
|
||||
0xd7, 0x27, 0xeb, 0xeb, 0x2c, 0xdf, 0xb3, 0x6a, 0x99, 0x9a, 0x6b, 0xe4, 0x09, 0xac, 0xaa, 0x0c,
|
||||
0x39, 0xd3, 0x47, 0x9c, 0x5a, 0xb6, 0x6e, 0x92, 0x50, 0x72, 0xdc, 0xc1, 0xc8, 0xfa, 0x0d, 0xaf,
|
||||
0x96, 0xb9, 0xab, 0x48, 0x28, 0x40, 0xa2, 0xe8, 0xce, 0xfc, 0x71, 0xae, 0x96, 0xbd, 0x5b, 0x48,
|
||||
0x7e, 0x0c, 0xc5, 0xa8, 0xb4, 0xca, 0xf8, 0x91, 0xac, 0x96, 0xb5, 0x61, 0xd7, 0x68, 0xfd, 0xe7,
|
||||
0x6f, 0x5b, 0xda, 0x6f, 0x4f, 0xb7, 0xb4, 0x2f, 0x4e, 0xb7, 0xb4, 0x2f, 0x4f, 0xb7, 0xb4, 0x3f,
|
||||
0x9d, 0x6e, 0x69, 0x7f, 0x3d, 0xdd, 0xd2, 0xfe, 0xf0, 0xf7, 0x2d, 0xed, 0x47, 0xef, 0x74, 0x6d,
|
||||
0xde, 0x1b, 0xb6, 0xeb, 0x1d, 0x77, 0xb0, 0x17, 0x0b, 0x4c, 0x3e, 0xc6, 0x5f, 0xbe, 0xdb, 0xab,
|
||||
0x18, 0xb0, 0xbe, 0xfd, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x0c, 0x66, 0x8a, 0xe9, 0x0e, 0x1f,
|
||||
0x00, 0x00,
|
||||
}
|
||||
|
||||
@@ -4512,6 +4521,9 @@ func (this *ResponseCommit) Equal(that interface{}) bool {
|
||||
if !bytes.Equal(this.Data, that1.Data) {
|
||||
return false
|
||||
}
|
||||
if this.RetainHeight != that1.RetainHeight {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(this.XXX_unrecognized, that1.XXX_unrecognized) {
|
||||
return false
|
||||
}
|
||||
@@ -7143,6 +7155,11 @@ func (m *ResponseCommit) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i -= len(m.XXX_unrecognized)
|
||||
copy(dAtA[i:], m.XXX_unrecognized)
|
||||
}
|
||||
if m.RetainHeight != 0 {
|
||||
i = encodeVarintTypes(dAtA, i, uint64(m.RetainHeight))
|
||||
i--
|
||||
dAtA[i] = 0x18
|
||||
}
|
||||
if len(m.Data) > 0 {
|
||||
i -= len(m.Data)
|
||||
copy(dAtA[i:], m.Data)
|
||||
@@ -8479,8 +8496,12 @@ func NewPopulatedResponseCommit(r randyTypes, easy bool) *ResponseCommit {
|
||||
for i := 0; i < v30; i++ {
|
||||
this.Data[i] = byte(r.Intn(256))
|
||||
}
|
||||
this.RetainHeight = int64(r.Int63())
|
||||
if r.Intn(2) == 0 {
|
||||
this.RetainHeight *= -1
|
||||
}
|
||||
if !easy && r.Intn(10) != 0 {
|
||||
this.XXX_unrecognized = randUnrecognizedTypes(r, 3)
|
||||
this.XXX_unrecognized = randUnrecognizedTypes(r, 4)
|
||||
}
|
||||
return this
|
||||
}
|
||||
@@ -9665,6 +9686,9 @@ func (m *ResponseCommit) Size() (n int) {
|
||||
if l > 0 {
|
||||
n += 1 + l + sovTypes(uint64(l))
|
||||
}
|
||||
if m.RetainHeight != 0 {
|
||||
n += 1 + sovTypes(uint64(m.RetainHeight))
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
}
|
||||
@@ -14046,6 +14070,25 @@ func (m *ResponseCommit) Unmarshal(dAtA []byte) error {
|
||||
m.Data = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 3:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field RetainHeight", wireType)
|
||||
}
|
||||
m.RetainHeight = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.RetainHeight |= int64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipTypes(dAtA[iNdEx:])
|
||||
|
||||
@@ -207,7 +207,8 @@ message ResponseEndBlock {
|
||||
|
||||
message ResponseCommit {
|
||||
// reserve 1
|
||||
bytes data = 2;
|
||||
bytes data = 2;
|
||||
int64 retain_height = 3;
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
|
||||
@@ -2,6 +2,7 @@ package v0
|
||||
|
||||
import (
|
||||
amino "github.com/tendermint/go-amino"
|
||||
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
|
||||
+10
-7
@@ -284,16 +284,17 @@ func (pool *BlockPool) MaxPeerHeight() int64 {
|
||||
return pool.maxPeerHeight
|
||||
}
|
||||
|
||||
// SetPeerHeight sets the peer's alleged blockchain height.
|
||||
func (pool *BlockPool) SetPeerHeight(peerID p2p.ID, height int64) {
|
||||
// SetPeerRange sets the peer's alleged blockchain base and height.
|
||||
func (pool *BlockPool) SetPeerRange(peerID p2p.ID, base int64, height int64) {
|
||||
pool.mtx.Lock()
|
||||
defer pool.mtx.Unlock()
|
||||
|
||||
peer := pool.peers[peerID]
|
||||
if peer != nil {
|
||||
peer.base = base
|
||||
peer.height = height
|
||||
} else {
|
||||
peer = newBPPeer(pool, peerID, height)
|
||||
peer = newBPPeer(pool, peerID, base, height)
|
||||
peer.setLogger(pool.Logger.With("peer", peerID))
|
||||
pool.peers[peerID] = peer
|
||||
}
|
||||
@@ -346,9 +347,9 @@ func (pool *BlockPool) updateMaxPeerHeight() {
|
||||
pool.maxPeerHeight = max
|
||||
}
|
||||
|
||||
// Pick an available peer with at least the given minHeight.
|
||||
// Pick an available peer with the given height available.
|
||||
// If no peers are available, returns nil.
|
||||
func (pool *BlockPool) pickIncrAvailablePeer(minHeight int64) *bpPeer {
|
||||
func (pool *BlockPool) pickIncrAvailablePeer(height int64) *bpPeer {
|
||||
pool.mtx.Lock()
|
||||
defer pool.mtx.Unlock()
|
||||
|
||||
@@ -360,7 +361,7 @@ func (pool *BlockPool) pickIncrAvailablePeer(minHeight int64) *bpPeer {
|
||||
if peer.numPending >= maxPendingRequestsPerPeer {
|
||||
continue
|
||||
}
|
||||
if peer.height < minHeight {
|
||||
if height < peer.base || height > peer.height {
|
||||
continue
|
||||
}
|
||||
peer.incrPending()
|
||||
@@ -432,6 +433,7 @@ type bpPeer struct {
|
||||
didTimeout bool
|
||||
numPending int32
|
||||
height int64
|
||||
base int64
|
||||
pool *BlockPool
|
||||
id p2p.ID
|
||||
recvMonitor *flow.Monitor
|
||||
@@ -441,10 +443,11 @@ type bpPeer struct {
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
func newBPPeer(pool *BlockPool, peerID p2p.ID, height int64) *bpPeer {
|
||||
func newBPPeer(pool *BlockPool, peerID p2p.ID, base int64, height int64) *bpPeer {
|
||||
peer := &bpPeer{
|
||||
pool: pool,
|
||||
id: peerID,
|
||||
base: base,
|
||||
height: height,
|
||||
numPending: 0,
|
||||
logger: log.NewNopLogger(),
|
||||
|
||||
@@ -20,6 +20,7 @@ func init() {
|
||||
|
||||
type testPeer struct {
|
||||
id p2p.ID
|
||||
base int64
|
||||
height int64
|
||||
inputChan chan inputData //make sure each peer's data is sequential
|
||||
}
|
||||
@@ -67,7 +68,11 @@ func makePeers(numPeers int, minHeight, maxHeight int64) testPeers {
|
||||
for i := 0; i < numPeers; i++ {
|
||||
peerID := p2p.ID(tmrand.Str(12))
|
||||
height := minHeight + tmrand.Int63n(maxHeight-minHeight)
|
||||
peers[peerID] = testPeer{peerID, height, make(chan inputData, 10)}
|
||||
base := minHeight + int64(i)
|
||||
if base > height {
|
||||
base = height
|
||||
}
|
||||
peers[peerID] = testPeer{peerID, base, height, make(chan inputData, 10)}
|
||||
}
|
||||
return peers
|
||||
}
|
||||
@@ -93,7 +98,7 @@ func TestBlockPoolBasic(t *testing.T) {
|
||||
// Introduce each peer.
|
||||
go func() {
|
||||
for _, peer := range peers {
|
||||
pool.SetPeerHeight(peer.id, peer.height)
|
||||
pool.SetPeerRange(peer.id, peer.base, peer.height)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -148,7 +153,7 @@ func TestBlockPoolTimeout(t *testing.T) {
|
||||
// Introduce each peer.
|
||||
go func() {
|
||||
for _, peer := range peers {
|
||||
pool.SetPeerHeight(peer.id, peer.height)
|
||||
pool.SetPeerRange(peer.id, peer.base, peer.height)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -192,7 +197,7 @@ func TestBlockPoolRemovePeer(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
peerID := p2p.ID(fmt.Sprintf("%d", i+1))
|
||||
height := int64(i + 1)
|
||||
peers[peerID] = testPeer{peerID, height, make(chan inputData)}
|
||||
peers[peerID] = testPeer{peerID, 0, height, make(chan inputData)}
|
||||
}
|
||||
requestsCh := make(chan BlockRequest)
|
||||
errorsCh := make(chan peerError)
|
||||
@@ -205,7 +210,7 @@ func TestBlockPoolRemovePeer(t *testing.T) {
|
||||
|
||||
// add peers
|
||||
for peerID, peer := range peers {
|
||||
pool.SetPeerHeight(peerID, peer.height)
|
||||
pool.SetPeerRange(peerID, peer.base, peer.height)
|
||||
}
|
||||
assert.EqualValues(t, 10, pool.MaxPeerHeight())
|
||||
|
||||
|
||||
+34
-12
@@ -140,12 +140,15 @@ func (bcR *BlockchainReactor) GetChannels() []*p2p.ChannelDescriptor {
|
||||
|
||||
// AddPeer implements Reactor by sending our state to peer.
|
||||
func (bcR *BlockchainReactor) AddPeer(peer p2p.Peer) {
|
||||
msgBytes := cdc.MustMarshalBinaryBare(&bcStatusResponseMessage{bcR.store.Height()})
|
||||
msgBytes := cdc.MustMarshalBinaryBare(&bcStatusResponseMessage{
|
||||
Height: bcR.store.Height(),
|
||||
Base: bcR.store.Base(),
|
||||
})
|
||||
peer.Send(BlockchainChannel, msgBytes)
|
||||
// it's OK if send fails. will try later in poolRoutine
|
||||
|
||||
// peer is added to the pool once we receive the first
|
||||
// bcStatusResponseMessage from the peer and call pool.SetPeerHeight
|
||||
// bcStatusResponseMessage from the peer and call pool.SetPeerRange
|
||||
}
|
||||
|
||||
// RemovePeer implements Reactor by removing peer from the pool.
|
||||
@@ -155,8 +158,6 @@ func (bcR *BlockchainReactor) RemovePeer(peer p2p.Peer, reason interface{}) {
|
||||
|
||||
// respondToPeer loads a block and sends it to the requesting peer,
|
||||
// if we have it. Otherwise, we'll respond saying we don't have it.
|
||||
// According to the Tendermint spec, if all nodes are honest,
|
||||
// no node should be requesting for a block that's non-existent.
|
||||
func (bcR *BlockchainReactor) respondToPeer(msg *bcBlockRequestMessage,
|
||||
src p2p.Peer) (queued bool) {
|
||||
|
||||
@@ -196,11 +197,15 @@ func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte)
|
||||
bcR.pool.AddBlock(src.ID(), msg.Block, len(msgBytes))
|
||||
case *bcStatusRequestMessage:
|
||||
// Send peer our state.
|
||||
msgBytes := cdc.MustMarshalBinaryBare(&bcStatusResponseMessage{bcR.store.Height()})
|
||||
src.TrySend(BlockchainChannel, msgBytes)
|
||||
src.TrySend(BlockchainChannel, cdc.MustMarshalBinaryBare(&bcStatusResponseMessage{
|
||||
Height: bcR.store.Height(),
|
||||
Base: bcR.store.Base(),
|
||||
}))
|
||||
case *bcStatusResponseMessage:
|
||||
// Got a peer status. Unverified.
|
||||
bcR.pool.SetPeerHeight(src.ID(), msg.Height)
|
||||
bcR.pool.SetPeerRange(src.ID(), msg.Base, msg.Height)
|
||||
case *bcNoBlockResponseMessage:
|
||||
bcR.Logger.Debug("Peer does not have requested block", "peer", src, "height", msg.Height)
|
||||
default:
|
||||
bcR.Logger.Error(fmt.Sprintf("Unknown message type %v", reflect.TypeOf(msg)))
|
||||
}
|
||||
@@ -338,7 +343,7 @@ FOR_LOOP:
|
||||
// TODO: same thing for app - but we would need a way to
|
||||
// get the hash without persisting the state
|
||||
var err error
|
||||
state, err = bcR.blockExec.ApplyBlock(state, firstID, first)
|
||||
state, _, err = bcR.blockExec.ApplyBlock(state, firstID, first)
|
||||
if err != nil {
|
||||
// TODO This is bad, are we zombie?
|
||||
panic(fmt.Sprintf("Failed to process committed block (%d:%X): %v", first.Height, first.Hash(), err))
|
||||
@@ -360,9 +365,12 @@ FOR_LOOP:
|
||||
}
|
||||
}
|
||||
|
||||
// BroadcastStatusRequest broadcasts `BlockStore` height.
|
||||
// BroadcastStatusRequest broadcasts `BlockStore` base and height.
|
||||
func (bcR *BlockchainReactor) BroadcastStatusRequest() error {
|
||||
msgBytes := cdc.MustMarshalBinaryBare(&bcStatusRequestMessage{bcR.store.Height()})
|
||||
msgBytes := cdc.MustMarshalBinaryBare(&bcStatusRequestMessage{
|
||||
Base: bcR.store.Base(),
|
||||
Height: bcR.store.Height(),
|
||||
})
|
||||
bcR.Switch.Broadcast(BlockchainChannel, msgBytes)
|
||||
return nil
|
||||
}
|
||||
@@ -446,34 +454,48 @@ func (m *bcBlockResponseMessage) String() string {
|
||||
|
||||
type bcStatusRequestMessage struct {
|
||||
Height int64
|
||||
Base int64
|
||||
}
|
||||
|
||||
// ValidateBasic performs basic validation.
|
||||
func (m *bcStatusRequestMessage) ValidateBasic() error {
|
||||
if m.Base < 0 {
|
||||
return errors.New("negative Base")
|
||||
}
|
||||
if m.Height < 0 {
|
||||
return errors.New("negative Height")
|
||||
}
|
||||
if m.Base > m.Height {
|
||||
return fmt.Errorf("base %v cannot be greater than height %v", m.Base, m.Height)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *bcStatusRequestMessage) String() string {
|
||||
return fmt.Sprintf("[bcStatusRequestMessage %v]", m.Height)
|
||||
return fmt.Sprintf("[bcStatusRequestMessage %v:%v]", m.Base, m.Height)
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
type bcStatusResponseMessage struct {
|
||||
Height int64
|
||||
Base int64
|
||||
}
|
||||
|
||||
// ValidateBasic performs basic validation.
|
||||
func (m *bcStatusResponseMessage) ValidateBasic() error {
|
||||
if m.Base < 0 {
|
||||
return errors.New("negative Base")
|
||||
}
|
||||
if m.Height < 0 {
|
||||
return errors.New("negative Height")
|
||||
}
|
||||
if m.Base > m.Height {
|
||||
return fmt.Errorf("base %v cannot be greater than height %v", m.Base, m.Height)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *bcStatusResponseMessage) String() string {
|
||||
return fmt.Sprintf("[bcStatusResponseMessage %v]", m.Height)
|
||||
return fmt.Sprintf("[bcStatusResponseMessage %v:%v]", m.Base, m.Height)
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tendermint/tendermint/store"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
@@ -18,9 +18,9 @@ import (
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
"github.com/tendermint/tendermint/proxy"
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/store"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
)
|
||||
|
||||
var config *cfg.Config
|
||||
@@ -112,7 +112,7 @@ func newBlockchainReactor(
|
||||
thisParts := thisBlock.MakePartSet(types.BlockPartSizeBytes)
|
||||
blockID := types.BlockID{Hash: thisBlock.Hash(), PartsHeader: thisParts.Header()}
|
||||
|
||||
state, err = blockExec.ApplyBlock(state, blockID, thisBlock)
|
||||
state, _, err = blockExec.ApplyBlock(state, blockID, thisBlock)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "error apply block"))
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package v1
|
||||
|
||||
import (
|
||||
amino "github.com/tendermint/go-amino"
|
||||
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ type BpPeer struct {
|
||||
logger log.Logger
|
||||
ID p2p.ID
|
||||
|
||||
Base int64 // the peer reported base
|
||||
Height int64 // the peer reported height
|
||||
NumPendingBlockRequests int // number of requests still waiting for block responses
|
||||
blocks map[int64]*types.Block // blocks received or expected to be received from this peer
|
||||
@@ -38,14 +39,15 @@ type BpPeer struct {
|
||||
}
|
||||
|
||||
// NewBpPeer creates a new peer.
|
||||
func NewBpPeer(
|
||||
peerID p2p.ID, height int64, onErr func(err error, peerID p2p.ID), params *BpPeerParams) *BpPeer {
|
||||
func NewBpPeer(peerID p2p.ID, base int64, height int64,
|
||||
onErr func(err error, peerID p2p.ID), params *BpPeerParams) *BpPeer {
|
||||
|
||||
if params == nil {
|
||||
params = BpPeerDefaultParams()
|
||||
}
|
||||
return &BpPeer{
|
||||
ID: peerID,
|
||||
Base: base,
|
||||
Height: height,
|
||||
blocks: make(map[int64]*types.Block, maxRequestsPerPeer),
|
||||
logger: log.NewNopLogger(),
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
|
||||
func TestPeerMonitor(t *testing.T) {
|
||||
peer := NewBpPeer(
|
||||
p2p.ID(tmrand.Str(12)), 10,
|
||||
p2p.ID(tmrand.Str(12)), 0, 10,
|
||||
func(err error, _ p2p.ID) {},
|
||||
nil)
|
||||
peer.SetLogger(log.TestingLogger())
|
||||
@@ -35,7 +35,7 @@ func TestPeerResetBlockResponseTimer(t *testing.T) {
|
||||
params := &BpPeerParams{timeout: 2 * time.Millisecond}
|
||||
|
||||
peer := NewBpPeer(
|
||||
p2p.ID(tmrand.Str(12)), 10,
|
||||
p2p.ID(tmrand.Str(12)), 0, 10,
|
||||
func(err error, _ p2p.ID) {
|
||||
peerTestMtx.Lock()
|
||||
defer peerTestMtx.Unlock()
|
||||
@@ -75,7 +75,7 @@ func TestPeerRequestSent(t *testing.T) {
|
||||
params := &BpPeerParams{timeout: 2 * time.Millisecond}
|
||||
|
||||
peer := NewBpPeer(
|
||||
p2p.ID(tmrand.Str(12)), 10,
|
||||
p2p.ID(tmrand.Str(12)), 0, 10,
|
||||
func(err error, _ p2p.ID) {},
|
||||
params)
|
||||
|
||||
@@ -94,7 +94,7 @@ func TestPeerRequestSent(t *testing.T) {
|
||||
|
||||
func TestPeerGetAndRemoveBlock(t *testing.T) {
|
||||
peer := NewBpPeer(
|
||||
p2p.ID(tmrand.Str(12)), 100,
|
||||
p2p.ID(tmrand.Str(12)), 0, 100,
|
||||
func(err error, _ p2p.ID) {},
|
||||
nil)
|
||||
|
||||
@@ -142,7 +142,7 @@ func TestPeerGetAndRemoveBlock(t *testing.T) {
|
||||
|
||||
func TestPeerAddBlock(t *testing.T) {
|
||||
peer := NewBpPeer(
|
||||
p2p.ID(tmrand.Str(12)), 100,
|
||||
p2p.ID(tmrand.Str(12)), 0, 100,
|
||||
func(err error, _ p2p.ID) {},
|
||||
nil)
|
||||
|
||||
@@ -189,7 +189,7 @@ func TestPeerOnErrFuncCalledDueToExpiration(t *testing.T) {
|
||||
)
|
||||
|
||||
peer := NewBpPeer(
|
||||
p2p.ID(tmrand.Str(12)), 10,
|
||||
p2p.ID(tmrand.Str(12)), 0, 10,
|
||||
func(err error, _ p2p.ID) {
|
||||
peerTestMtx.Lock()
|
||||
defer peerTestMtx.Unlock()
|
||||
@@ -215,7 +215,7 @@ func TestPeerCheckRate(t *testing.T) {
|
||||
minRecvRate: int64(100), // 100 bytes/sec exponential moving average
|
||||
}
|
||||
peer := NewBpPeer(
|
||||
p2p.ID(tmrand.Str(12)), 10,
|
||||
p2p.ID(tmrand.Str(12)), 0, 10,
|
||||
func(err error, _ p2p.ID) {},
|
||||
params)
|
||||
peer.SetLogger(log.TestingLogger())
|
||||
@@ -249,7 +249,7 @@ func TestPeerCleanup(t *testing.T) {
|
||||
params := &BpPeerParams{timeout: 2 * time.Millisecond}
|
||||
|
||||
peer := NewBpPeer(
|
||||
p2p.ID(tmrand.Str(12)), 10,
|
||||
p2p.ID(tmrand.Str(12)), 0, 10,
|
||||
func(err error, _ p2p.ID) {},
|
||||
params)
|
||||
peer.SetLogger(log.TestingLogger())
|
||||
|
||||
@@ -66,9 +66,9 @@ func (pool *BlockPool) updateMaxPeerHeight() {
|
||||
pool.MaxPeerHeight = newMax
|
||||
}
|
||||
|
||||
// UpdatePeer adds a new peer or updates an existing peer with a new height.
|
||||
// UpdatePeer adds a new peer or updates an existing peer with a new base and height.
|
||||
// If a peer is short it is not added.
|
||||
func (pool *BlockPool) UpdatePeer(peerID p2p.ID, height int64) error {
|
||||
func (pool *BlockPool) UpdatePeer(peerID p2p.ID, base int64, height int64) error {
|
||||
|
||||
peer := pool.peers[peerID]
|
||||
|
||||
@@ -79,10 +79,10 @@ func (pool *BlockPool) UpdatePeer(peerID p2p.ID, height int64) error {
|
||||
return errPeerTooShort
|
||||
}
|
||||
// Add new peer.
|
||||
peer = NewBpPeer(peerID, height, pool.toBcR.sendPeerError, nil)
|
||||
peer = NewBpPeer(peerID, base, height, pool.toBcR.sendPeerError, nil)
|
||||
peer.SetLogger(pool.logger.With("peer", peerID))
|
||||
pool.peers[peerID] = peer
|
||||
pool.logger.Info("added peer", "peerID", peerID, "height", height, "num_peers", len(pool.peers))
|
||||
pool.logger.Info("added peer", "peerID", peerID, "base", base, "height", height, "num_peers", len(pool.peers))
|
||||
} else {
|
||||
// Check if peer is lowering its height. This is not allowed.
|
||||
if height < peer.Height {
|
||||
@@ -90,6 +90,7 @@ func (pool *BlockPool) UpdatePeer(peerID p2p.ID, height int64) error {
|
||||
return errPeerLowersItsHeight
|
||||
}
|
||||
// Update existing peer.
|
||||
peer.Base = base
|
||||
peer.Height = height
|
||||
}
|
||||
|
||||
@@ -213,7 +214,7 @@ func (pool *BlockPool) sendRequest(height int64) bool {
|
||||
if peer.NumPendingBlockRequests >= maxRequestsPerPeer {
|
||||
continue
|
||||
}
|
||||
if peer.Height < height {
|
||||
if peer.Base > height || peer.Height < height {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
+43
-23
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
type testPeer struct {
|
||||
id p2p.ID
|
||||
base int64
|
||||
height int64
|
||||
}
|
||||
|
||||
@@ -70,7 +71,7 @@ func makeBlockPool(bcr *testBcR, height int64, peers []BpPeer, blocks map[int64]
|
||||
if p.Height > maxH {
|
||||
maxH = p.Height
|
||||
}
|
||||
bPool.peers[p.ID] = NewBpPeer(p.ID, p.Height, bcr.sendPeerError, nil)
|
||||
bPool.peers[p.ID] = NewBpPeer(p.ID, p.Base, p.Height, bcr.sendPeerError, nil)
|
||||
bPool.peers[p.ID].SetLogger(bcr.logger)
|
||||
|
||||
}
|
||||
@@ -93,6 +94,7 @@ func assertPeerSetsEquivalent(t *testing.T, set1 map[p2p.ID]*BpPeer, set2 map[p2
|
||||
assert.NotNil(t, peer2)
|
||||
assert.Equal(t, peer1.NumPendingBlockRequests, peer2.NumPendingBlockRequests)
|
||||
assert.Equal(t, peer1.Height, peer2.Height)
|
||||
assert.Equal(t, peer1.Base, peer2.Base)
|
||||
assert.Equal(t, len(peer1.blocks), len(peer2.blocks))
|
||||
for h, block1 := range peer1.blocks {
|
||||
block2 := peer2.blocks[h]
|
||||
@@ -123,26 +125,32 @@ func TestBlockPoolUpdatePeer(t *testing.T) {
|
||||
{
|
||||
name: "add a first short peer",
|
||||
pool: makeBlockPool(testBcR, 100, []BpPeer{}, map[int64]tPBlocks{}),
|
||||
args: testPeer{"P1", 50},
|
||||
args: testPeer{"P1", 0, 50},
|
||||
errWanted: errPeerTooShort,
|
||||
poolWanted: makeBlockPool(testBcR, 100, []BpPeer{}, map[int64]tPBlocks{}),
|
||||
},
|
||||
{
|
||||
name: "add a first good peer",
|
||||
pool: makeBlockPool(testBcR, 100, []BpPeer{}, map[int64]tPBlocks{}),
|
||||
args: testPeer{"P1", 101},
|
||||
args: testPeer{"P1", 0, 101},
|
||||
poolWanted: makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 101}}, map[int64]tPBlocks{}),
|
||||
},
|
||||
{
|
||||
name: "add a first good peer with base",
|
||||
pool: makeBlockPool(testBcR, 100, []BpPeer{}, map[int64]tPBlocks{}),
|
||||
args: testPeer{"P1", 10, 101},
|
||||
poolWanted: makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Base: 10, Height: 101}}, map[int64]tPBlocks{}),
|
||||
},
|
||||
{
|
||||
name: "increase the height of P1 from 120 to 123",
|
||||
pool: makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 120}}, map[int64]tPBlocks{}),
|
||||
args: testPeer{"P1", 123},
|
||||
args: testPeer{"P1", 0, 123},
|
||||
poolWanted: makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 123}}, map[int64]tPBlocks{}),
|
||||
},
|
||||
{
|
||||
name: "decrease the height of P1 from 120 to 110",
|
||||
pool: makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 120}}, map[int64]tPBlocks{}),
|
||||
args: testPeer{"P1", 110},
|
||||
args: testPeer{"P1", 0, 110},
|
||||
errWanted: errPeerLowersItsHeight,
|
||||
poolWanted: makeBlockPool(testBcR, 100, []BpPeer{}, map[int64]tPBlocks{}),
|
||||
},
|
||||
@@ -151,7 +159,7 @@ func TestBlockPoolUpdatePeer(t *testing.T) {
|
||||
pool: makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 105}},
|
||||
map[int64]tPBlocks{
|
||||
100: {"P1", true}, 101: {"P1", true}, 102: {"P1", true}}),
|
||||
args: testPeer{"P1", 102},
|
||||
args: testPeer{"P1", 0, 102},
|
||||
errWanted: errPeerLowersItsHeight,
|
||||
poolWanted: makeBlockPool(testBcR, 100, []BpPeer{},
|
||||
map[int64]tPBlocks{}),
|
||||
@@ -162,7 +170,7 @@ func TestBlockPoolUpdatePeer(t *testing.T) {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pool := tt.pool
|
||||
err := pool.UpdatePeer(tt.args.id, tt.args.height)
|
||||
err := pool.UpdatePeer(tt.args.id, tt.args.base, tt.args.height)
|
||||
assert.Equal(t, tt.errWanted, err)
|
||||
assert.Equal(t, tt.poolWanted.blocks, tt.pool.blocks)
|
||||
assertPeerSetsEquivalent(t, tt.poolWanted.peers, tt.pool.peers)
|
||||
@@ -300,20 +308,34 @@ func TestBlockPoolSendRequestBatch(t *testing.T) {
|
||||
testBcR := newTestBcR()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
pool *BlockPool
|
||||
maxRequestsPerPeer int
|
||||
expRequests map[int64]bool
|
||||
expPeerResults []testPeerResult
|
||||
expnumPendingBlockRequests int
|
||||
name string
|
||||
pool *BlockPool
|
||||
maxRequestsPerPeer int
|
||||
expRequests map[int64]bool
|
||||
expRequestsSent int
|
||||
expPeerResults []testPeerResult
|
||||
}{
|
||||
{
|
||||
name: "one peer - send up to maxRequestsPerPeer block requests",
|
||||
pool: makeBlockPool(testBcR, 10, []BpPeer{{ID: "P1", Height: 100}}, map[int64]tPBlocks{}),
|
||||
maxRequestsPerPeer: 2,
|
||||
expRequests: map[int64]bool{10: true, 11: true},
|
||||
expPeerResults: []testPeerResult{{id: "P1", numPendingBlockRequests: 2}},
|
||||
expnumPendingBlockRequests: 2,
|
||||
name: "one peer - send up to maxRequestsPerPeer block requests",
|
||||
pool: makeBlockPool(testBcR, 10, []BpPeer{{ID: "P1", Height: 100}}, map[int64]tPBlocks{}),
|
||||
maxRequestsPerPeer: 2,
|
||||
expRequests: map[int64]bool{10: true, 11: true},
|
||||
expRequestsSent: 2,
|
||||
expPeerResults: []testPeerResult{{id: "P1", numPendingBlockRequests: 2}},
|
||||
},
|
||||
{
|
||||
name: "multiple peers - stops at gap between height and base",
|
||||
pool: makeBlockPool(testBcR, 10, []BpPeer{
|
||||
{ID: "P1", Base: 1, Height: 12},
|
||||
{ID: "P2", Base: 15, Height: 100},
|
||||
}, map[int64]tPBlocks{}),
|
||||
maxRequestsPerPeer: 10,
|
||||
expRequests: map[int64]bool{10: true, 11: true, 12: true},
|
||||
expRequestsSent: 3,
|
||||
expPeerResults: []testPeerResult{
|
||||
{id: "P1", numPendingBlockRequests: 3},
|
||||
{id: "P2", numPendingBlockRequests: 0},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "n peers - send n*maxRequestsPerPeer block requests",
|
||||
@@ -324,10 +346,10 @@ func TestBlockPoolSendRequestBatch(t *testing.T) {
|
||||
map[int64]tPBlocks{}),
|
||||
maxRequestsPerPeer: 2,
|
||||
expRequests: map[int64]bool{10: true, 11: true},
|
||||
expRequestsSent: 4,
|
||||
expPeerResults: []testPeerResult{
|
||||
{id: "P1", numPendingBlockRequests: 2},
|
||||
{id: "P2", numPendingBlockRequests: 2}},
|
||||
expnumPendingBlockRequests: 4,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -339,15 +361,13 @@ func TestBlockPoolSendRequestBatch(t *testing.T) {
|
||||
var pool = tt.pool
|
||||
maxRequestsPerPeer = tt.maxRequestsPerPeer
|
||||
pool.MakeNextRequests(10)
|
||||
assert.Equal(t, testResults.numRequestsSent, maxRequestsPerPeer*len(pool.peers))
|
||||
|
||||
assert.Equal(t, tt.expRequestsSent, testResults.numRequestsSent)
|
||||
for _, tPeer := range tt.expPeerResults {
|
||||
var peer = pool.peers[tPeer.id]
|
||||
assert.NotNil(t, peer)
|
||||
assert.Equal(t, tPeer.numPendingBlockRequests, peer.NumPendingBlockRequests)
|
||||
}
|
||||
assert.Equal(t, testResults.numRequestsSent, maxRequestsPerPeer*len(pool.peers))
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
amino "github.com/tendermint/go-amino"
|
||||
|
||||
"github.com/tendermint/tendermint/behaviour"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
@@ -168,7 +169,10 @@ func (bcR *BlockchainReactor) GetChannels() []*p2p.ChannelDescriptor {
|
||||
|
||||
// AddPeer implements Reactor by sending our state to peer.
|
||||
func (bcR *BlockchainReactor) AddPeer(peer p2p.Peer) {
|
||||
msgBytes := cdc.MustMarshalBinaryBare(&bcStatusResponseMessage{bcR.store.Height()})
|
||||
msgBytes := cdc.MustMarshalBinaryBare(&bcStatusResponseMessage{
|
||||
Base: bcR.store.Base(),
|
||||
Height: bcR.store.Height(),
|
||||
})
|
||||
peer.Send(BlockchainChannel, msgBytes)
|
||||
// it's OK if send fails. will try later in poolRoutine
|
||||
|
||||
@@ -195,7 +199,10 @@ func (bcR *BlockchainReactor) sendBlockToPeer(msg *bcBlockRequestMessage,
|
||||
}
|
||||
|
||||
func (bcR *BlockchainReactor) sendStatusResponseToPeer(msg *bcStatusRequestMessage, src p2p.Peer) (queued bool) {
|
||||
msgBytes := cdc.MustMarshalBinaryBare(&bcStatusResponseMessage{bcR.store.Height()})
|
||||
msgBytes := cdc.MustMarshalBinaryBare(&bcStatusResponseMessage{
|
||||
Base: bcR.store.Base(),
|
||||
Height: bcR.store.Height(),
|
||||
})
|
||||
return src.TrySend(BlockchainChannel, msgBytes)
|
||||
}
|
||||
|
||||
@@ -429,7 +436,7 @@ func (bcR *BlockchainReactor) processBlock() error {
|
||||
|
||||
bcR.store.SaveBlock(first, firstParts, second.LastCommit)
|
||||
|
||||
bcR.state, err = bcR.blockExec.ApplyBlock(bcR.state, firstID, first)
|
||||
bcR.state, _, err = bcR.blockExec.ApplyBlock(bcR.state, firstID, first)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to process committed block (%d:%X): %v", first.Height, first.Hash(), err))
|
||||
}
|
||||
@@ -440,7 +447,10 @@ func (bcR *BlockchainReactor) processBlock() error {
|
||||
// Implements bcRNotifier
|
||||
// sendStatusRequest broadcasts `BlockStore` height.
|
||||
func (bcR *BlockchainReactor) sendStatusRequest() {
|
||||
msgBytes := cdc.MustMarshalBinaryBare(&bcStatusRequestMessage{bcR.store.Height()})
|
||||
msgBytes := cdc.MustMarshalBinaryBare(&bcStatusRequestMessage{
|
||||
Base: bcR.store.Base(),
|
||||
Height: bcR.store.Height(),
|
||||
})
|
||||
bcR.Switch.Broadcast(BlockchainChannel, msgBytes)
|
||||
}
|
||||
|
||||
@@ -589,6 +599,7 @@ func (m *bcBlockResponseMessage) String() string {
|
||||
|
||||
type bcStatusRequestMessage struct {
|
||||
Height int64
|
||||
Base int64
|
||||
}
|
||||
|
||||
// ValidateBasic performs basic validation.
|
||||
@@ -596,17 +607,24 @@ func (m *bcStatusRequestMessage) ValidateBasic() error {
|
||||
if m.Height < 0 {
|
||||
return errors.New("negative Height")
|
||||
}
|
||||
if m.Base < 0 {
|
||||
return errors.New("negative Base")
|
||||
}
|
||||
if m.Base > m.Height {
|
||||
return fmt.Errorf("base %v cannot be greater than height %v", m.Base, m.Height)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *bcStatusRequestMessage) String() string {
|
||||
return fmt.Sprintf("[bcStatusRequestMessage %v]", m.Height)
|
||||
return fmt.Sprintf("[bcStatusRequestMessage %v:%v]", m.Base, m.Height)
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
type bcStatusResponseMessage struct {
|
||||
Height int64
|
||||
Base int64
|
||||
}
|
||||
|
||||
// ValidateBasic performs basic validation.
|
||||
@@ -614,9 +632,15 @@ func (m *bcStatusResponseMessage) ValidateBasic() error {
|
||||
if m.Height < 0 {
|
||||
return errors.New("negative Height")
|
||||
}
|
||||
if m.Base < 0 {
|
||||
return errors.New("negative Base")
|
||||
}
|
||||
if m.Base > m.Height {
|
||||
return fmt.Errorf("base %v cannot be greater than height %v", m.Base, m.Height)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *bcStatusResponseMessage) String() string {
|
||||
return fmt.Sprintf("[bcStatusResponseMessage %v]", m.Height)
|
||||
return fmt.Sprintf("[bcStatusResponseMessage %v:%v]", m.Base, m.Height)
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ func NewFSM(height int64, toBcR bcReactor) *BcReactorFSM {
|
||||
type bReactorEventData struct {
|
||||
peerID p2p.ID
|
||||
err error // for peer error: timeout, slow; for processed block event if error occurred
|
||||
base int64 // for status response
|
||||
height int64 // for status response; for processed block event
|
||||
block *types.Block // for block response
|
||||
stateName string // for state timeout events
|
||||
@@ -89,7 +90,7 @@ func (msg *bcReactorMessage) String() string {
|
||||
case startFSMEv:
|
||||
dataStr = ""
|
||||
case statusResponseEv:
|
||||
dataStr = fmt.Sprintf("peer=%v height=%v", msg.data.peerID, msg.data.height)
|
||||
dataStr = fmt.Sprintf("peer=%v base=%v height=%v", msg.data.peerID, msg.data.base, msg.data.height)
|
||||
case blockResponseEv:
|
||||
dataStr = fmt.Sprintf("peer=%v block.height=%v length=%v",
|
||||
msg.data.peerID, msg.data.block.Height, msg.data.length)
|
||||
@@ -213,7 +214,7 @@ func init() {
|
||||
return finished, errNoTallerPeer
|
||||
|
||||
case statusResponseEv:
|
||||
if err := fsm.pool.UpdatePeer(data.peerID, data.height); err != nil {
|
||||
if err := fsm.pool.UpdatePeer(data.peerID, data.base, data.height); err != nil {
|
||||
if fsm.pool.NumPeers() == 0 {
|
||||
return waitForPeer, err
|
||||
}
|
||||
@@ -246,7 +247,7 @@ func init() {
|
||||
switch ev {
|
||||
|
||||
case statusResponseEv:
|
||||
err := fsm.pool.UpdatePeer(data.peerID, data.height)
|
||||
err := fsm.pool.UpdatePeer(data.peerID, data.base, data.height)
|
||||
if fsm.pool.NumPeers() == 0 {
|
||||
return waitForPeer, err
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmmath "github.com/tendermint/tendermint/libs/math"
|
||||
tmrand "github.com/tendermint/tendermint/libs/rand"
|
||||
|
||||
@@ -10,6 +10,10 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
@@ -20,7 +24,6 @@ import (
|
||||
"github.com/tendermint/tendermint/store"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
)
|
||||
|
||||
var config *cfg.Config
|
||||
@@ -46,15 +49,19 @@ func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.G
|
||||
}
|
||||
|
||||
func makeVote(
|
||||
t *testing.T,
|
||||
header *types.Header,
|
||||
blockID types.BlockID,
|
||||
valset *types.ValidatorSet,
|
||||
privVal types.PrivValidator) *types.Vote {
|
||||
addr := privVal.GetPubKey().Address()
|
||||
idx, _ := valset.GetByAddress(addr)
|
||||
|
||||
pubKey, err := privVal.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
valIdx, _ := valset.GetByAddress(pubKey.Address())
|
||||
vote := &types.Vote{
|
||||
ValidatorAddress: addr,
|
||||
ValidatorIndex: idx,
|
||||
ValidatorAddress: pubKey.Address(),
|
||||
ValidatorIndex: valIdx,
|
||||
Height: header.Height,
|
||||
Round: 1,
|
||||
Timestamp: tmtime.Now(),
|
||||
@@ -73,6 +80,7 @@ type BlockchainReactorPair struct {
|
||||
}
|
||||
|
||||
func newBlockchainReactor(
|
||||
t *testing.T,
|
||||
logger log.Logger,
|
||||
genDoc *types.GenesisDoc,
|
||||
privVals []types.PrivValidator,
|
||||
@@ -114,7 +122,7 @@ func newBlockchainReactor(
|
||||
lastBlockMeta := blockStore.LoadBlockMeta(blockHeight - 1)
|
||||
lastBlock := blockStore.LoadBlock(blockHeight - 1)
|
||||
|
||||
vote := makeVote(&lastBlock.Header, lastBlockMeta.BlockID, state.Validators, privVals[0])
|
||||
vote := makeVote(t, &lastBlock.Header, lastBlockMeta.BlockID, state.Validators, privVals[0])
|
||||
lastCommit = types.NewCommit(vote.Height, vote.Round, lastBlockMeta.BlockID, []types.CommitSig{vote.CommitSig()})
|
||||
}
|
||||
|
||||
@@ -123,7 +131,7 @@ func newBlockchainReactor(
|
||||
thisParts := thisBlock.MakePartSet(types.BlockPartSizeBytes)
|
||||
blockID := types.BlockID{Hash: thisBlock.Hash(), PartsHeader: thisParts.Header()}
|
||||
|
||||
state, err = blockExec.ApplyBlock(state, blockID, thisBlock)
|
||||
state, _, err = blockExec.ApplyBlock(state, blockID, thisBlock)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "error apply block"))
|
||||
}
|
||||
@@ -138,6 +146,7 @@ func newBlockchainReactor(
|
||||
}
|
||||
|
||||
func newBlockchainReactorPair(
|
||||
t *testing.T,
|
||||
logger log.Logger,
|
||||
genDoc *types.GenesisDoc,
|
||||
privVals []types.PrivValidator,
|
||||
@@ -147,7 +156,7 @@ func newBlockchainReactorPair(
|
||||
consensusReactor.BaseReactor = *p2p.NewBaseReactor("Consensus reactor", consensusReactor)
|
||||
|
||||
return BlockchainReactorPair{
|
||||
newBlockchainReactor(logger, genDoc, privVals, maxBlockHeight),
|
||||
newBlockchainReactor(t, logger, genDoc, privVals, maxBlockHeight),
|
||||
consensusReactor}
|
||||
}
|
||||
|
||||
@@ -174,8 +183,8 @@ func TestFastSyncNoBlockResponse(t *testing.T) {
|
||||
reactorPairs := make([]BlockchainReactorPair, 2)
|
||||
|
||||
logger := log.TestingLogger()
|
||||
reactorPairs[0] = newBlockchainReactorPair(logger, genDoc, privVals, maxBlockHeight)
|
||||
reactorPairs[1] = newBlockchainReactorPair(logger, genDoc, privVals, 0)
|
||||
reactorPairs[0] = newBlockchainReactorPair(t, logger, genDoc, privVals, maxBlockHeight)
|
||||
reactorPairs[1] = newBlockchainReactorPair(t, logger, genDoc, privVals, 0)
|
||||
|
||||
p2p.MakeConnectedSwitches(config.P2P, 2, func(i int, s *p2p.Switch) *p2p.Switch {
|
||||
s.AddReactor("BLOCKCHAIN", reactorPairs[i].bcR)
|
||||
@@ -239,7 +248,7 @@ func TestFastSyncBadBlockStopsPeer(t *testing.T) {
|
||||
defer os.RemoveAll(config.RootDir)
|
||||
genDoc, privVals := randGenesisDoc(1, false, 30)
|
||||
|
||||
otherChain := newBlockchainReactorPair(log.TestingLogger(), genDoc, privVals, maxBlockHeight)
|
||||
otherChain := newBlockchainReactorPair(t, log.TestingLogger(), genDoc, privVals, maxBlockHeight)
|
||||
defer func() {
|
||||
_ = otherChain.bcR.Stop()
|
||||
_ = otherChain.conR.Stop()
|
||||
@@ -254,7 +263,7 @@ func TestFastSyncBadBlockStopsPeer(t *testing.T) {
|
||||
if i == 0 {
|
||||
height = maxBlockHeight
|
||||
}
|
||||
reactorPairs[i] = newBlockchainReactorPair(logger[i], genDoc, privVals, height)
|
||||
reactorPairs[i] = newBlockchainReactorPair(t, logger[i], genDoc, privVals, height)
|
||||
}
|
||||
|
||||
switches := p2p.MakeConnectedSwitches(config.P2P, numNodes, func(i int, s *p2p.Switch) *p2p.Switch {
|
||||
@@ -296,7 +305,7 @@ outerFor:
|
||||
reactorPairs[numNodes-1].bcR.store = otherChain.bcR.store
|
||||
|
||||
lastLogger := log.TestingLogger()
|
||||
lastReactorPair := newBlockchainReactorPair(lastLogger, genDoc, privVals, 0)
|
||||
lastReactorPair := newBlockchainReactorPair(t, lastLogger, genDoc, privVals, 0)
|
||||
reactorPairs = append(reactorPairs, lastReactorPair)
|
||||
|
||||
switches = append(switches, p2p.MakeConnectedSwitches(config.P2P, 1, func(i int, s *p2p.Switch) *p2p.Switch {
|
||||
|
||||
@@ -2,6 +2,7 @@ package v2
|
||||
|
||||
import (
|
||||
amino "github.com/tendermint/go-amino"
|
||||
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
|
||||
+6
-3
@@ -14,7 +14,7 @@ type iIO interface {
|
||||
sendBlockNotFound(height int64, peerID p2p.ID) error
|
||||
sendStatusResponse(height int64, peerID p2p.ID) error
|
||||
|
||||
broadcastStatusRequest(height int64)
|
||||
broadcastStatusRequest(base int64, height int64)
|
||||
|
||||
trySwitchToConsensus(state state.State, blocksSynced int)
|
||||
}
|
||||
@@ -104,8 +104,11 @@ func (sio *switchIO) trySwitchToConsensus(state state.State, blocksSynced int) {
|
||||
}
|
||||
}
|
||||
|
||||
func (sio *switchIO) broadcastStatusRequest(height int64) {
|
||||
msgBytes := cdc.MustMarshalBinaryBare(&bcStatusRequestMessage{height})
|
||||
func (sio *switchIO) broadcastStatusRequest(base int64, height int64) {
|
||||
msgBytes := cdc.MustMarshalBinaryBare(&bcStatusRequestMessage{
|
||||
Base: base,
|
||||
Height: height,
|
||||
})
|
||||
// XXX: maybe we should use an io specific peer list here
|
||||
sio.sw.Broadcast(BlockchainChannel, msgBytes)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func newProcessorContext(st blockStore, ex blockApplier, s state.State) *pContex
|
||||
}
|
||||
|
||||
func (pc *pContext) applyBlock(blockID types.BlockID, block *types.Block) error {
|
||||
newState, err := pc.applier.ApplyBlock(pc.state, blockID, block)
|
||||
newState, _, err := pc.applier.ApplyBlock(pc.state, blockID, block)
|
||||
pc.state = newState
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
tmState "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/go-amino"
|
||||
|
||||
"github.com/tendermint/tendermint/behaviour"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
@@ -71,41 +72,56 @@ func (m *bcBlockResponseMessage) String() string {
|
||||
|
||||
type bcStatusRequestMessage struct {
|
||||
Height int64
|
||||
Base int64
|
||||
}
|
||||
|
||||
// ValidateBasic performs basic validation.
|
||||
func (m *bcStatusRequestMessage) ValidateBasic() error {
|
||||
if m.Base < 0 {
|
||||
return errors.New("negative Base")
|
||||
}
|
||||
if m.Height < 0 {
|
||||
return errors.New("negative Height")
|
||||
}
|
||||
if m.Base > m.Height {
|
||||
return fmt.Errorf("base %v cannot be greater than height %v", m.Base, m.Height)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *bcStatusRequestMessage) String() string {
|
||||
return fmt.Sprintf("[bcStatusRequestMessage %v]", m.Height)
|
||||
return fmt.Sprintf("[bcStatusRequestMessage %v:%v]", m.Base, m.Height)
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
type bcStatusResponseMessage struct {
|
||||
Height int64
|
||||
Base int64
|
||||
}
|
||||
|
||||
// ValidateBasic performs basic validation.
|
||||
func (m *bcStatusResponseMessage) ValidateBasic() error {
|
||||
if m.Base < 0 {
|
||||
return errors.New("negative Base")
|
||||
}
|
||||
if m.Height < 0 {
|
||||
return errors.New("negative Height")
|
||||
}
|
||||
if m.Base > m.Height {
|
||||
return fmt.Errorf("base %v cannot be greater than height %v", m.Base, m.Height)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *bcStatusResponseMessage) String() string {
|
||||
return fmt.Sprintf("[bcStatusResponseMessage %v]", m.Height)
|
||||
return fmt.Sprintf("[bcStatusResponseMessage %v:%v]", m.Base, m.Height)
|
||||
}
|
||||
|
||||
type blockStore interface {
|
||||
LoadBlock(height int64) *types.Block
|
||||
SaveBlock(*types.Block, *types.PartSet, *types.Commit)
|
||||
Base() int64
|
||||
Height() int64
|
||||
}
|
||||
|
||||
@@ -135,7 +151,7 @@ type blockVerifier interface {
|
||||
|
||||
//nolint:deadcode
|
||||
type blockApplier interface {
|
||||
ApplyBlock(state state.State, blockID types.BlockID, block *types.Block) (state.State, error)
|
||||
ApplyBlock(state state.State, blockID types.BlockID, block *types.Block) (state.State, int64, error)
|
||||
}
|
||||
|
||||
// XXX: unify naming in this package around tmState
|
||||
@@ -265,6 +281,7 @@ type bcStatusResponse struct {
|
||||
priorityNormal
|
||||
time time.Time
|
||||
peerID p2p.ID
|
||||
base int64
|
||||
height int64
|
||||
}
|
||||
|
||||
@@ -336,7 +353,7 @@ func (r *BlockchainReactor) demux() {
|
||||
case <-doProcessBlockCh:
|
||||
r.processor.send(rProcessBlock{})
|
||||
case <-doStatusCh:
|
||||
r.io.broadcastStatusRequest(r.SyncHeight())
|
||||
r.io.broadcastStatusRequest(r.store.Base(), r.SyncHeight())
|
||||
|
||||
// Events from peers
|
||||
case event := <-r.events:
|
||||
@@ -482,7 +499,7 @@ func (r *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) {
|
||||
}
|
||||
|
||||
case *bcStatusResponseMessage:
|
||||
r.events <- bcStatusResponse{peerID: src.ID(), height: msg.Height}
|
||||
r.events <- bcStatusResponse{peerID: src.ID(), base: msg.Base, height: msg.Height}
|
||||
|
||||
case *bcBlockResponseMessage:
|
||||
r.events <- bcBlockResponse{
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/behaviour"
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
@@ -23,7 +25,6 @@ import (
|
||||
"github.com/tendermint/tendermint/store"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
)
|
||||
|
||||
type mockPeer struct {
|
||||
@@ -76,9 +77,9 @@ type mockBlockApplier struct {
|
||||
}
|
||||
|
||||
// XXX: Add whitelist/blacklist?
|
||||
func (mba *mockBlockApplier) ApplyBlock(state sm.State, blockID types.BlockID, block *types.Block) (sm.State, error) {
|
||||
func (mba *mockBlockApplier) ApplyBlock(state sm.State, blockID types.BlockID, block *types.Block) (sm.State, int64, error) {
|
||||
state.LastBlockHeight++
|
||||
return state, nil
|
||||
return state, 0, nil
|
||||
}
|
||||
|
||||
type mockSwitchIo struct {
|
||||
@@ -126,7 +127,7 @@ func (sio *mockSwitchIo) hasSwitchedToConsensus() bool {
|
||||
return sio.switchedToConsensus
|
||||
}
|
||||
|
||||
func (sio *mockSwitchIo) broadcastStatusRequest(height int64) {
|
||||
func (sio *mockSwitchIo) broadcastStatusRequest(base int64, height int64) {
|
||||
}
|
||||
|
||||
type testReactorParams struct {
|
||||
@@ -510,7 +511,7 @@ func newReactorStore(
|
||||
thisParts := thisBlock.MakePartSet(types.BlockPartSizeBytes)
|
||||
blockID := types.BlockID{Hash: thisBlock.Hash(), PartsHeader: thisParts.Header()}
|
||||
|
||||
state, err = blockExec.ApplyBlock(state, blockID, thisBlock)
|
||||
state, _, err = blockExec.ApplyBlock(state, blockID, thisBlock)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "error apply block"))
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/Workiva/go-datastructures/queue"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
)
|
||||
|
||||
|
||||
@@ -111,20 +111,22 @@ type scPeer struct {
|
||||
// updated to Removed when peer is removed
|
||||
state peerState
|
||||
|
||||
base int64 // updated when statusResponse is received
|
||||
height int64 // updated when statusResponse is received
|
||||
lastTouched time.Time
|
||||
lastRate int64 // last receive rate in bytes
|
||||
}
|
||||
|
||||
func (p scPeer) String() string {
|
||||
return fmt.Sprintf("{state %v, height %d, lastTouched %v, lastRate %d, id %v}",
|
||||
p.state, p.height, p.lastTouched, p.lastRate, p.peerID)
|
||||
return fmt.Sprintf("{state %v, base %d, height %d, lastTouched %v, lastRate %d, id %v}",
|
||||
p.state, p.base, p.height, p.lastTouched, p.lastRate, p.peerID)
|
||||
}
|
||||
|
||||
func newScPeer(peerID p2p.ID) *scPeer {
|
||||
return &scPeer{
|
||||
peerID: peerID,
|
||||
state: peerStateNew,
|
||||
base: -1,
|
||||
height: -1,
|
||||
lastTouched: time.Time{},
|
||||
}
|
||||
@@ -280,7 +282,7 @@ func (sc *scheduler) addNewBlocks() {
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *scheduler) setPeerHeight(peerID p2p.ID, height int64) error {
|
||||
func (sc *scheduler) setPeerRange(peerID p2p.ID, base int64, height int64) error {
|
||||
peer, ok := sc.peers[peerID]
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot find peer %s", peerID)
|
||||
@@ -295,6 +297,11 @@ func (sc *scheduler) setPeerHeight(peerID p2p.ID, height int64) error {
|
||||
return fmt.Errorf("cannot move peer height lower. from %d to %d", peer.height, height)
|
||||
}
|
||||
|
||||
if base > height {
|
||||
return fmt.Errorf("cannot set peer base higher than its height")
|
||||
}
|
||||
|
||||
peer.base = base
|
||||
peer.height = height
|
||||
peer.state = peerStateReady
|
||||
|
||||
@@ -312,13 +319,13 @@ func (sc *scheduler) getStateAtHeight(height int64) blockState {
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *scheduler) getPeersAtHeightOrAbove(height int64) []p2p.ID {
|
||||
func (sc *scheduler) getPeersWithHeight(height int64) []p2p.ID {
|
||||
peers := make([]p2p.ID, 0)
|
||||
for _, peer := range sc.peers {
|
||||
if peer.state != peerStateReady {
|
||||
continue
|
||||
}
|
||||
if peer.height >= height {
|
||||
if peer.base <= height && peer.height >= height {
|
||||
peers = append(peers, peer.peerID)
|
||||
}
|
||||
}
|
||||
@@ -395,6 +402,11 @@ func (sc *scheduler) markPending(peerID p2p.ID, height int64, time time.Time) er
|
||||
height, peerID, peer.height)
|
||||
}
|
||||
|
||||
if height < peer.base {
|
||||
return fmt.Errorf("cannot request height %d for peer %s with base %d",
|
||||
height, peerID, peer.base)
|
||||
}
|
||||
|
||||
sc.setStateAtHeight(height, blockStatePending)
|
||||
sc.pendingBlocks[height] = peerID
|
||||
sc.pendingTime[height] = time
|
||||
@@ -463,7 +475,7 @@ func (sc *scheduler) pendingFrom(peerID p2p.ID) []int64 {
|
||||
}
|
||||
|
||||
func (sc *scheduler) selectPeer(height int64) (p2p.ID, error) {
|
||||
peers := sc.getPeersAtHeightOrAbove(height)
|
||||
peers := sc.getPeersWithHeight(height)
|
||||
if len(peers) == 0 {
|
||||
return "", fmt.Errorf("cannot find peer for height %d", height)
|
||||
}
|
||||
@@ -535,8 +547,8 @@ func (sc *scheduler) handleNoBlockResponse(event bcNoBlockResponse) (Event, erro
|
||||
_ = sc.removePeer(event.peerID)
|
||||
|
||||
return scPeerError{peerID: event.peerID,
|
||||
reason: fmt.Errorf("peer %v with height %d claims no block for %d",
|
||||
event.peerID, peer.height, event.height)}, nil
|
||||
reason: fmt.Errorf("peer %v with base %d height %d claims no block for %d",
|
||||
event.peerID, peer.base, peer.height, event.height)}, nil
|
||||
}
|
||||
|
||||
func (sc *scheduler) handleBlockProcessed(event pcBlockProcessed) (Event, error) {
|
||||
@@ -653,7 +665,7 @@ func (sc *scheduler) handleTrySchedule(event rTrySchedule) (Event, error) {
|
||||
}
|
||||
|
||||
func (sc *scheduler) handleStatusResponse(event bcStatusResponse) (Event, error) {
|
||||
err := sc.setPeerHeight(event.peerID, event.height)
|
||||
err := sc.setPeerRange(event.peerID, event.base, event.height)
|
||||
if err != nil {
|
||||
return scPeerError{peerID: event.peerID, reason: err}, nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
@@ -144,8 +145,8 @@ func TestScMaxHeights(t *testing.T) {
|
||||
sc: scheduler{
|
||||
height: 1,
|
||||
peers: map[p2p.ID]*scPeer{
|
||||
"P1": {height: -1, state: peerStateNew},
|
||||
"P2": {height: -1, state: peerStateNew}},
|
||||
"P1": {base: -1, height: -1, state: peerStateNew},
|
||||
"P2": {base: -1, height: -1, state: peerStateNew}},
|
||||
},
|
||||
wantMax: 0,
|
||||
},
|
||||
@@ -193,15 +194,15 @@ func TestScAddPeer(t *testing.T) {
|
||||
name: "add first peer",
|
||||
fields: scTestParams{},
|
||||
args: args{peerID: "P1"},
|
||||
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1, state: peerStateNew}}},
|
||||
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {base: -1, height: -1, state: peerStateNew}}},
|
||||
},
|
||||
{
|
||||
name: "add second peer",
|
||||
fields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1, state: peerStateNew}}},
|
||||
fields: scTestParams{peers: map[string]*scPeer{"P1": {base: -1, height: -1, state: peerStateNew}}},
|
||||
args: args{peerID: "P2"},
|
||||
wantFields: scTestParams{peers: map[string]*scPeer{
|
||||
"P1": {height: -1, state: peerStateNew},
|
||||
"P2": {height: -1, state: peerStateNew}}},
|
||||
"P1": {base: -1, height: -1, state: peerStateNew},
|
||||
"P2": {base: -1, height: -1, state: peerStateNew}}},
|
||||
},
|
||||
{
|
||||
name: "attempt to add duplicate peer",
|
||||
@@ -500,10 +501,11 @@ func TestScRemovePeer(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestScSetPeerHeight(t *testing.T) {
|
||||
func TestScSetPeerRange(t *testing.T) {
|
||||
|
||||
type args struct {
|
||||
peerID p2p.ID
|
||||
base int64
|
||||
height int64
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -575,13 +577,37 @@ func TestScSetPeerHeight(t *testing.T) {
|
||||
peers: map[string]*scPeer{"P2": {height: 10000000000, state: peerStateReady}},
|
||||
allB: []int64{1, 2, 3, 4}},
|
||||
},
|
||||
{
|
||||
name: "add peer with base > height should error",
|
||||
fields: scTestParams{
|
||||
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
||||
allB: []int64{1, 2, 3, 4}},
|
||||
args: args{peerID: "P1", base: 6, height: 5},
|
||||
wantFields: scTestParams{
|
||||
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
|
||||
allB: []int64{1, 2, 3, 4}},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "add peer with base == height is fine",
|
||||
fields: scTestParams{
|
||||
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateNew}},
|
||||
targetPending: 4,
|
||||
},
|
||||
args: args{peerID: "P1", base: 6, height: 6},
|
||||
wantFields: scTestParams{
|
||||
targetPending: 4,
|
||||
peers: map[string]*scPeer{"P1": {base: 6, height: 6, state: peerStateReady}},
|
||||
allB: []int64{1, 2, 3, 4}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
sc := newTestScheduler(tt.fields)
|
||||
if err := sc.setPeerHeight(tt.args.peerID, tt.args.height); (err != nil) != tt.wantErr {
|
||||
err := sc.setPeerRange(tt.args.peerID, tt.args.base, tt.args.height)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("setPeerHeight() wantErr %v, error = %v", tt.wantErr, err)
|
||||
}
|
||||
wantSc := newTestScheduler(tt.wantFields)
|
||||
@@ -590,7 +616,7 @@ func TestScSetPeerHeight(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestScGetPeersAtHeight(t *testing.T) {
|
||||
func TestScGetPeersWithHeight(t *testing.T) {
|
||||
|
||||
type args struct {
|
||||
height int64
|
||||
@@ -647,6 +673,26 @@ func TestScGetPeersAtHeight(t *testing.T) {
|
||||
args: args{height: 4},
|
||||
wantResult: []p2p.ID{"P1"},
|
||||
},
|
||||
{
|
||||
name: "one Ready higher peer at base",
|
||||
fields: scTestParams{
|
||||
targetPending: 4,
|
||||
peers: map[string]*scPeer{"P1": {base: 4, height: 20, state: peerStateReady}},
|
||||
allB: []int64{1, 2, 3, 4},
|
||||
},
|
||||
args: args{height: 4},
|
||||
wantResult: []p2p.ID{"P1"},
|
||||
},
|
||||
{
|
||||
name: "one Ready higher peer with higher base",
|
||||
fields: scTestParams{
|
||||
targetPending: 4,
|
||||
peers: map[string]*scPeer{"P1": {base: 10, height: 20, state: peerStateReady}},
|
||||
allB: []int64{1, 2, 3, 4},
|
||||
},
|
||||
args: args{height: 4},
|
||||
wantResult: []p2p.ID{},
|
||||
},
|
||||
{
|
||||
name: "multiple mixed peers",
|
||||
fields: scTestParams{
|
||||
@@ -668,9 +714,9 @@ func TestScGetPeersAtHeight(t *testing.T) {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
sc := newTestScheduler(tt.fields)
|
||||
// getPeersAtHeight should not mutate the scheduler
|
||||
// getPeersWithHeight should not mutate the scheduler
|
||||
wantSc := sc
|
||||
res := sc.getPeersAtHeightOrAbove(tt.args.height)
|
||||
res := sc.getPeersWithHeight(tt.args.height)
|
||||
sort.Sort(PeerByID(res))
|
||||
assert.Equal(t, tt.wantResult, res)
|
||||
assert.Equal(t, wantSc, sc)
|
||||
@@ -694,7 +740,7 @@ func TestScMarkPending(t *testing.T) {
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "attempt mark pending an unknown block",
|
||||
name: "attempt mark pending an unknown block above height",
|
||||
fields: scTestParams{
|
||||
peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}},
|
||||
allB: []int64{1, 2}},
|
||||
@@ -704,6 +750,17 @@ func TestScMarkPending(t *testing.T) {
|
||||
allB: []int64{1, 2}},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "attempt mark pending an unknown block below base",
|
||||
fields: scTestParams{
|
||||
peers: map[string]*scPeer{"P1": {base: 4, height: 6, state: peerStateReady}},
|
||||
allB: []int64{1, 2, 3, 4, 5, 6}},
|
||||
args: args{peerID: "P1", height: 3, tm: now},
|
||||
wantFields: scTestParams{
|
||||
peers: map[string]*scPeer{"P1": {base: 4, height: 6, state: peerStateReady}},
|
||||
allB: []int64{1, 2, 3, 4, 5, 6}},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "attempt mark pending from non existing peer",
|
||||
fields: scTestParams{
|
||||
@@ -1201,6 +1258,16 @@ func TestScSelectPeer(t *testing.T) {
|
||||
args: args{height: 4},
|
||||
wantResult: "P1",
|
||||
},
|
||||
{
|
||||
name: "one Ready higher peer with higher base",
|
||||
fields: scTestParams{
|
||||
peers: map[string]*scPeer{"P1": {base: 4, height: 6, state: peerStateReady}},
|
||||
allB: []int64{1, 2, 3, 4, 5, 6},
|
||||
},
|
||||
args: args{height: 3},
|
||||
wantResult: "",
|
||||
wantError: true,
|
||||
},
|
||||
{
|
||||
name: "many Ready higher peers with different number of pending requests",
|
||||
fields: scTestParams{
|
||||
@@ -1989,7 +2056,7 @@ func TestScHandle(t *testing.T) {
|
||||
args: args{event: bcAddNewPeer{peerID: "P1"}},
|
||||
wantEvent: noOpEvent{},
|
||||
wantSc: &scTestParams{startTime: now, peers: map[string]*scPeer{
|
||||
"P1": {height: -1, state: peerStateNew}}, height: 1},
|
||||
"P1": {base: -1, height: -1, state: peerStateNew}}, height: 1},
|
||||
},
|
||||
{ // set height of P1
|
||||
args: args{event: bcStatusResponse{peerID: "P1", time: tick[0], height: 3}},
|
||||
|
||||
@@ -2,6 +2,7 @@ package commands
|
||||
|
||||
import (
|
||||
amino "github.com/tendermint/go-amino"
|
||||
|
||||
cryptoamino "github.com/tendermint/tendermint/crypto/encoding/amino"
|
||||
)
|
||||
|
||||
|
||||
@@ -10,9 +10,10 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
rpchttp "github.com/tendermint/tendermint/rpc/client/http"
|
||||
)
|
||||
|
||||
var dumpCmd = &cobra.Command{
|
||||
@@ -58,7 +59,7 @@ func dumpCmdHandler(_ *cobra.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
rpc, err := rpcclient.NewHTTP(nodeRPCAddr, "/websocket")
|
||||
rpc, err := rpchttp.New(nodeRPCAddr, "/websocket")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create new http client")
|
||||
}
|
||||
@@ -78,7 +79,7 @@ func dumpCmdHandler(_ *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func dumpDebugData(outDir string, conf *cfg.Config, rpc *rpcclient.HTTP) {
|
||||
func dumpDebugData(outDir string, conf *cfg.Config, rpc *rpchttp.HTTP) {
|
||||
start := time.Now().UTC()
|
||||
|
||||
tmpDir, err := ioutil.TempDir(outDir, "tendermint_debug_tmp")
|
||||
@@ -126,7 +127,7 @@ func dumpDebugData(outDir string, conf *cfg.Config, rpc *rpcclient.HTTP) {
|
||||
}
|
||||
}
|
||||
|
||||
outFile := filepath.Join(outDir, fmt.Sprintf("%s.zip", start.Format(time.Stamp)))
|
||||
outFile := filepath.Join(outDir, fmt.Sprintf("%s.zip", start.Format(time.RFC3339)))
|
||||
if err := zipDir(tmpDir, outFile); err != nil {
|
||||
logger.Error("failed to create and compress archive", "file", outFile, "error", err)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
rpchttp "github.com/tendermint/tendermint/rpc/client/http"
|
||||
)
|
||||
|
||||
var killCmd = &cobra.Command{
|
||||
@@ -44,7 +44,7 @@ func killCmdHandler(cmd *cobra.Command, args []string) error {
|
||||
return errors.New("invalid output file")
|
||||
}
|
||||
|
||||
rpc, err := rpcclient.NewHTTP(nodeRPCAddr, "/websocket")
|
||||
rpc, err := rpchttp.New(nodeRPCAddr, "/websocket")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create new http client")
|
||||
}
|
||||
@@ -124,7 +124,7 @@ func killProc(pid uint64, dir string) error {
|
||||
go func() {
|
||||
// Killing the Tendermint process with the '-ABRT|-6' signal will result in
|
||||
// a goroutine stacktrace.
|
||||
p, err := os.FindProcess(os.Getpid())
|
||||
p, err := os.FindProcess(int(pid))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to find PID to kill Tendermint process: %s", err)
|
||||
} else if err = p.Signal(syscall.SIGABRT); err != nil {
|
||||
|
||||
@@ -9,13 +9,14 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
rpchttp "github.com/tendermint/tendermint/rpc/client/http"
|
||||
)
|
||||
|
||||
// dumpStatus gets node status state dump from the Tendermint RPC and writes it
|
||||
// to file. It returns an error upon failure.
|
||||
func dumpStatus(rpc *rpcclient.HTTP, dir, filename string) error {
|
||||
func dumpStatus(rpc *rpchttp.HTTP, dir, filename string) error {
|
||||
status, err := rpc.Status()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get node status")
|
||||
@@ -26,7 +27,7 @@ func dumpStatus(rpc *rpcclient.HTTP, dir, filename string) error {
|
||||
|
||||
// dumpNetInfo gets network information state dump from the Tendermint RPC and
|
||||
// writes it to file. It returns an error upon failure.
|
||||
func dumpNetInfo(rpc *rpcclient.HTTP, dir, filename string) error {
|
||||
func dumpNetInfo(rpc *rpchttp.HTTP, dir, filename string) error {
|
||||
netInfo, err := rpc.NetInfo()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get node network information")
|
||||
@@ -37,7 +38,7 @@ func dumpNetInfo(rpc *rpcclient.HTTP, dir, filename string) error {
|
||||
|
||||
// dumpConsensusState gets consensus state dump from the Tendermint RPC and
|
||||
// writes it to file. It returns an error upon failure.
|
||||
func dumpConsensusState(rpc *rpcclient.HTTP, dir, filename string) error {
|
||||
func dumpConsensusState(rpc *rpchttp.HTTP, dir, filename string) error {
|
||||
consDump, err := rpc.DumpConsensusState()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get node consensus dump")
|
||||
|
||||
@@ -3,7 +3,9 @@ package commands
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
tmos "github.com/tendermint/tendermint/libs/os"
|
||||
tmrand "github.com/tendermint/tendermint/libs/rand"
|
||||
@@ -60,10 +62,13 @@ func initFilesWithConfig(config *cfg.Config) error {
|
||||
GenesisTime: tmtime.Now(),
|
||||
ConsensusParams: types.DefaultConsensusParams(),
|
||||
}
|
||||
key := pv.GetPubKey()
|
||||
pubKey, err := pv.GetPubKey()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "can't get pubkey")
|
||||
}
|
||||
genDoc.Validators = []types.GenesisValidator{{
|
||||
Address: key.Address(),
|
||||
PubKey: key,
|
||||
Address: pubKey.Address(),
|
||||
PubKey: pubKey,
|
||||
Power: 10,
|
||||
}}
|
||||
|
||||
|
||||
@@ -2,59 +2,74 @@ package commands
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
amino "github.com/tendermint/go-amino"
|
||||
"github.com/tendermint/go-amino"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmos "github.com/tendermint/tendermint/libs/os"
|
||||
lite "github.com/tendermint/tendermint/lite2"
|
||||
"github.com/tendermint/tendermint/lite2/provider"
|
||||
httpp "github.com/tendermint/tendermint/lite2/provider/http"
|
||||
lproxy "github.com/tendermint/tendermint/lite2/proxy"
|
||||
lrpc "github.com/tendermint/tendermint/lite2/rpc"
|
||||
dbs "github.com/tendermint/tendermint/lite2/store/db"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
rpchttp "github.com/tendermint/tendermint/rpc/client/http"
|
||||
rpcserver "github.com/tendermint/tendermint/rpc/lib/server"
|
||||
)
|
||||
|
||||
// LiteCmd represents the base command when called without any subcommands
|
||||
var LiteCmd = &cobra.Command{
|
||||
Use: "lite",
|
||||
Use: "lite [chainID]",
|
||||
Short: "Run a light client proxy server, verifying Tendermint rpc",
|
||||
Long: `Run a light client proxy server, verifying Tendermint rpc.
|
||||
|
||||
All calls that can be tracked back to a block header by a proof
|
||||
will be verified before passing them back to the caller. Other than
|
||||
that, it will present the same interface as a full Tendermint node.`,
|
||||
RunE: runProxy,
|
||||
SilenceUsage: true,
|
||||
that, it will present the same interface as a full Tendermint node.
|
||||
|
||||
Example:
|
||||
|
||||
start a fresh instance:
|
||||
|
||||
lite cosmoshub-3 -p 52.57.29.196:26657 -w public-seed-node.cosmoshub.certus.one:26657
|
||||
--height 962118 --hash 28B97BE9F6DE51AC69F70E0B7BFD7E5C9CD1A595B7DC31AFF27C50D4948020CD
|
||||
|
||||
continue from latest state:
|
||||
|
||||
lite cosmoshub-3 -p 52.57.29.196:26657 -w public-seed-node.cosmoshub.certus.one:26657
|
||||
`,
|
||||
RunE: runProxy,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Example: `lite cosmoshub-3 -p 52.57.29.196:26657 -w public-seed-node.cosmoshub.certus.one:26657
|
||||
--height 962118 --hash 28B97BE9F6DE51AC69F70E0B7BFD7E5C9CD1A595B7DC31AFF27C50D4948020CD`,
|
||||
}
|
||||
|
||||
var (
|
||||
listenAddr string
|
||||
primaryAddr string
|
||||
witnessAddrsJoined string
|
||||
chainID string
|
||||
home string
|
||||
witnessesAddrs string
|
||||
maxOpenConnections int
|
||||
|
||||
trustingPeriod time.Duration
|
||||
trustedHeight int64
|
||||
trustedHash []byte
|
||||
|
||||
verbose bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
LiteCmd.Flags().StringVar(&listenAddr, "laddr", "tcp://localhost:8888",
|
||||
"Serve the proxy on the given address")
|
||||
LiteCmd.Flags().StringVar(&chainID, "chain-id", "tendermint", "Specify the Tendermint chain ID")
|
||||
LiteCmd.Flags().StringVar(&primaryAddr, "primary", "tcp://localhost:26657",
|
||||
LiteCmd.Flags().StringVarP(&primaryAddr, "primary", "p", "",
|
||||
"Connect to a Tendermint node at this address")
|
||||
LiteCmd.Flags().StringVar(&witnessesAddrs, "witnesses", "",
|
||||
LiteCmd.Flags().StringVarP(&witnessAddrsJoined, "witnesses", "w", "",
|
||||
"Tendermint nodes to cross-check the primary node, comma-separated")
|
||||
LiteCmd.Flags().StringVar(&home, "home-dir", ".tendermint-lite", "Specify the home directory")
|
||||
LiteCmd.Flags().IntVar(
|
||||
@@ -64,65 +79,77 @@ func init() {
|
||||
"Maximum number of simultaneous connections (including WebSocket).")
|
||||
LiteCmd.Flags().DurationVar(&trustingPeriod, "trusting-period", 168*time.Hour,
|
||||
"Trusting period. Should be significantly less than the unbonding period")
|
||||
LiteCmd.Flags().Int64Var(&trustedHeight, "trusted-height", 1, "Trusted header's height")
|
||||
LiteCmd.Flags().BytesHexVar(&trustedHash, "trusted-hash", []byte{}, "Trusted header's hash")
|
||||
LiteCmd.Flags().Int64Var(&trustedHeight, "height", 1, "Trusted header's height")
|
||||
LiteCmd.Flags().BytesHexVar(&trustedHash, "hash", []byte{}, "Trusted header's hash")
|
||||
LiteCmd.Flags().BoolVar(&verbose, "verbose", false, "Verbose output")
|
||||
}
|
||||
|
||||
func runProxy(cmd *cobra.Command, args []string) error {
|
||||
liteLogger := logger.With("module", "lite")
|
||||
// Initialise logger.
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||
var option log.Option
|
||||
if verbose {
|
||||
option, _ = log.AllowLevel("debug")
|
||||
} else {
|
||||
option, _ = log.AllowLevel("info")
|
||||
}
|
||||
logger = log.NewFilter(logger, option)
|
||||
|
||||
logger.Info("Connecting to the primary node...")
|
||||
rpcClient, err := rpcclient.NewHTTP(chainID, primaryAddr)
|
||||
chainID = args[0]
|
||||
logger.Info("Creating client...", "chainID", chainID)
|
||||
|
||||
witnessesAddrs := strings.Split(witnessAddrsJoined, ",")
|
||||
|
||||
db, err := dbm.NewGoLevelDB("lite-client-db", home)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "new goleveldb")
|
||||
}
|
||||
|
||||
var c *lite.Client
|
||||
if trustedHeight > 0 && len(trustedHash) > 0 { // fresh installation
|
||||
c, err = lite.NewHTTPClient(
|
||||
chainID,
|
||||
lite.TrustOptions{
|
||||
Period: trustingPeriod,
|
||||
Height: trustedHeight,
|
||||
Hash: trustedHash,
|
||||
},
|
||||
primaryAddr,
|
||||
witnessesAddrs,
|
||||
dbs.New(db, chainID),
|
||||
lite.Logger(logger),
|
||||
)
|
||||
} else { // continue from latest state
|
||||
c, err = lite.NewHTTPClientFromTrustedStore(
|
||||
chainID,
|
||||
trustingPeriod,
|
||||
primaryAddr,
|
||||
witnessesAddrs,
|
||||
dbs.New(db, chainID),
|
||||
lite.Logger(logger),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rpcClient, err := rpchttp.New(primaryAddr, "/websocket")
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "http client for %s", primaryAddr)
|
||||
}
|
||||
primary := httpp.NewWithClient(chainID, rpcClient)
|
||||
|
||||
logger.Info("Connecting to the witness nodes...")
|
||||
addrs := strings.Split(witnessesAddrs, ",")
|
||||
witnesses := make([]provider.Provider, len(addrs))
|
||||
for i, addr := range addrs {
|
||||
p, err := httpp.New(chainID, addr)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "http provider for %s", addr)
|
||||
}
|
||||
witnesses[i] = p
|
||||
}
|
||||
|
||||
logger.Info("Creating client...")
|
||||
db, err := dbm.NewGoLevelDB("lite-client-db", home)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c, err := lite.NewClient(
|
||||
chainID,
|
||||
lite.TrustOptions{
|
||||
Period: trustingPeriod,
|
||||
Height: trustedHeight,
|
||||
Hash: trustedHash,
|
||||
},
|
||||
primary,
|
||||
witnesses,
|
||||
dbs.New(db, chainID),
|
||||
lite.Logger(liteLogger),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p := lproxy.Proxy{
|
||||
Addr: listenAddr,
|
||||
Config: &rpcserver.Config{MaxOpenConnections: maxOpenConnections},
|
||||
Codec: amino.NewCodec(),
|
||||
Client: lrpc.NewClient(rpcClient, c),
|
||||
Logger: liteLogger,
|
||||
Logger: logger,
|
||||
}
|
||||
// Stop upon receiving SIGTERM or CTRL-C.
|
||||
tmos.TrapSignal(liteLogger, func() {
|
||||
tmos.TrapSignal(logger, func() {
|
||||
p.Listener.Close()
|
||||
})
|
||||
|
||||
logger.Info("Starting proxy...")
|
||||
logger.Info("Starting proxy...", "laddr", listenAddr)
|
||||
if err := p.ListenAndServe(); err != http.ErrServerClosed {
|
||||
// Error starting or closing listener:
|
||||
logger.Error("proxy ListenAndServe", "err", err)
|
||||
|
||||
@@ -78,7 +78,7 @@ func AddNodeFlags(cmd *cobra.Command) {
|
||||
"Set this to false to only produce blocks when there are txs or when the AppHash changes")
|
||||
cmd.Flags().String(
|
||||
"consensus.create_empty_blocks_interval",
|
||||
string(config.Consensus.CreateEmptyBlocksInterval),
|
||||
config.Consensus.CreateEmptyBlocksInterval.String(),
|
||||
"The possible interval between empty blocks")
|
||||
|
||||
// db flags
|
||||
@@ -99,11 +99,21 @@ func NewRunNodeCmd(nodeProvider nm.Provider) *cobra.Command {
|
||||
Use: "node",
|
||||
Short: "Run the tendermint node",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := checkGenesisHash(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n, err := nodeProvider(config, logger)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create node: %v", err)
|
||||
return fmt.Errorf("failed to create node: %w", err)
|
||||
}
|
||||
|
||||
if err := n.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start node: %w", err)
|
||||
}
|
||||
|
||||
logger.Info("Started node", "nodeInfo", n.Switch().NodeInfo())
|
||||
|
||||
// Stop upon receiving SIGTERM or CTRL-C.
|
||||
tmos.TrapSignal(logger, func() {
|
||||
if n.IsRunning() {
|
||||
@@ -111,15 +121,6 @@ func NewRunNodeCmd(nodeProvider nm.Provider) *cobra.Command {
|
||||
}
|
||||
})
|
||||
|
||||
if err := checkGenesisHash(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := n.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start node: %v", err)
|
||||
}
|
||||
logger.Info("Started node", "nodeInfo", n.Switch().NodeInfo())
|
||||
|
||||
// Run forever.
|
||||
select {}
|
||||
},
|
||||
|
||||
@@ -24,7 +24,13 @@ func showValidator(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
pv := privval.LoadFilePV(keyFilePath, config.PrivValidatorStateFile())
|
||||
bz, err := cdc.MarshalJSON(pv.GetPubKey())
|
||||
|
||||
pubKey, err := pv.GetPubKey()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "can't get pubkey")
|
||||
}
|
||||
|
||||
bz, err := cdc.MarshalJSON(pubKey)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal private validator pubkey")
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
@@ -137,11 +138,15 @@ func testnetFiles(cmd *cobra.Command, args []string) error {
|
||||
|
||||
pvKeyFile := filepath.Join(nodeDir, config.BaseConfig.PrivValidatorKey)
|
||||
pvStateFile := filepath.Join(nodeDir, config.BaseConfig.PrivValidatorState)
|
||||
|
||||
pv := privval.LoadFilePV(pvKeyFile, pvStateFile)
|
||||
|
||||
pubKey, err := pv.GetPubKey()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "can't get pubkey")
|
||||
}
|
||||
genVals[i] = types.GenesisValidator{
|
||||
Address: pv.GetPubKey().Address(),
|
||||
PubKey: pv.GetPubKey(),
|
||||
Address: pubKey.Address(),
|
||||
PubKey: pubKey,
|
||||
Power: 1,
|
||||
Name: nodeDirName,
|
||||
}
|
||||
|
||||
+36
-19
@@ -134,27 +134,44 @@ func TestFastSyncConfigValidateBasic(t *testing.T) {
|
||||
assert.Error(t, cfg.ValidateBasic())
|
||||
}
|
||||
|
||||
func TestConsensusConfigValidateBasic(t *testing.T) {
|
||||
cfg := TestConsensusConfig()
|
||||
assert.NoError(t, cfg.ValidateBasic())
|
||||
|
||||
fieldsToTest := []string{
|
||||
"TimeoutPropose",
|
||||
"TimeoutProposeDelta",
|
||||
"TimeoutPrevote",
|
||||
"TimeoutPrevoteDelta",
|
||||
"TimeoutPrecommit",
|
||||
"TimeoutPrecommitDelta",
|
||||
"TimeoutCommit",
|
||||
"CreateEmptyBlocksInterval",
|
||||
"PeerGossipSleepDuration",
|
||||
"PeerQueryMaj23SleepDuration",
|
||||
func TestConsensusConfig_ValidateBasic(t *testing.T) {
|
||||
// nolint: lll
|
||||
testcases := map[string]struct {
|
||||
modify func(*ConsensusConfig)
|
||||
expectErr bool
|
||||
}{
|
||||
"TimeoutPropose": {func(c *ConsensusConfig) { c.TimeoutPropose = time.Second }, false},
|
||||
"TimeoutPropose negative": {func(c *ConsensusConfig) { c.TimeoutPropose = -1 }, true},
|
||||
"TimeoutProposeDelta": {func(c *ConsensusConfig) { c.TimeoutProposeDelta = time.Second }, false},
|
||||
"TimeoutProposeDelta negative": {func(c *ConsensusConfig) { c.TimeoutProposeDelta = -1 }, true},
|
||||
"TimeoutPrevote": {func(c *ConsensusConfig) { c.TimeoutPrevote = time.Second }, false},
|
||||
"TimeoutPrevote negative": {func(c *ConsensusConfig) { c.TimeoutPrevote = -1 }, true},
|
||||
"TimeoutPrevoteDelta": {func(c *ConsensusConfig) { c.TimeoutPrevoteDelta = time.Second }, false},
|
||||
"TimeoutPrevoteDelta negative": {func(c *ConsensusConfig) { c.TimeoutPrevoteDelta = -1 }, true},
|
||||
"TimeoutPrecommit": {func(c *ConsensusConfig) { c.TimeoutPrecommit = time.Second }, false},
|
||||
"TimeoutPrecommit negative": {func(c *ConsensusConfig) { c.TimeoutPrecommit = -1 }, true},
|
||||
"TimeoutPrecommitDelta": {func(c *ConsensusConfig) { c.TimeoutPrecommitDelta = time.Second }, false},
|
||||
"TimeoutPrecommitDelta negative": {func(c *ConsensusConfig) { c.TimeoutPrecommitDelta = -1 }, true},
|
||||
"TimeoutCommit": {func(c *ConsensusConfig) { c.TimeoutCommit = time.Second }, false},
|
||||
"TimeoutCommit negative": {func(c *ConsensusConfig) { c.TimeoutCommit = -1 }, true},
|
||||
"PeerGossipSleepDuration": {func(c *ConsensusConfig) { c.PeerGossipSleepDuration = time.Second }, false},
|
||||
"PeerGossipSleepDuration negative": {func(c *ConsensusConfig) { c.PeerGossipSleepDuration = -1 }, true},
|
||||
"PeerQueryMaj23SleepDuration": {func(c *ConsensusConfig) { c.PeerQueryMaj23SleepDuration = time.Second }, false},
|
||||
"PeerQueryMaj23SleepDuration negative": {func(c *ConsensusConfig) { c.PeerQueryMaj23SleepDuration = -1 }, true},
|
||||
}
|
||||
for desc, tc := range testcases {
|
||||
tc := tc // appease linter
|
||||
t.Run(desc, func(t *testing.T) {
|
||||
cfg := DefaultConsensusConfig()
|
||||
tc.modify(cfg)
|
||||
|
||||
for _, fieldName := range fieldsToTest {
|
||||
reflect.ValueOf(cfg).Elem().FieldByName(fieldName).SetInt(-1)
|
||||
assert.Error(t, cfg.ValidateBasic())
|
||||
reflect.ValueOf(cfg).Elem().FieldByName(fieldName).SetInt(0)
|
||||
err := cfg.ValidateBasic()
|
||||
if tc.expectErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -321,6 +321,7 @@ max_tx_bytes = {{ .Mempool.MaxTxBytes }}
|
||||
# Fast Sync version to use:
|
||||
# 1) "v0" (default) - the legacy fast sync implementation
|
||||
# 2) "v1" - refactor of v0 version for better testability
|
||||
# 3) "v2" - refactor of v1 version for better usability
|
||||
version = "{{ .FastSync.Version }}"
|
||||
|
||||
##### consensus configuration options #####
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/service"
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
|
||||
@@ -2,6 +2,7 @@ package consensus
|
||||
|
||||
import (
|
||||
amino "github.com/tendermint/go-amino"
|
||||
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
|
||||
@@ -13,9 +13,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/log/term"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"path"
|
||||
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
abcicli "github.com/tendermint/tendermint/abci/client"
|
||||
"github.com/tendermint/tendermint/abci/example/counter"
|
||||
"github.com/tendermint/tendermint/abci/example/kvstore"
|
||||
@@ -33,7 +37,6 @@ import (
|
||||
"github.com/tendermint/tendermint/store"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -82,17 +85,23 @@ func (vs *validatorStub) signVote(
|
||||
voteType types.SignedMsgType,
|
||||
hash []byte,
|
||||
header types.PartSetHeader) (*types.Vote, error) {
|
||||
addr := vs.PrivValidator.GetPubKey().Address()
|
||||
|
||||
pubKey, err := vs.PrivValidator.GetPubKey()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can't get pubkey")
|
||||
}
|
||||
|
||||
vote := &types.Vote{
|
||||
ValidatorIndex: vs.Index,
|
||||
ValidatorAddress: addr,
|
||||
ValidatorAddress: pubKey.Address(),
|
||||
Height: vs.Height,
|
||||
Round: vs.Round,
|
||||
Timestamp: tmtime.Now(),
|
||||
Type: voteType,
|
||||
BlockID: types.BlockID{Hash: hash, PartsHeader: header},
|
||||
}
|
||||
err := vs.PrivValidator.SignVote(config.ChainID(), vote)
|
||||
|
||||
err = vs.PrivValidator.SignVote(config.ChainID(), vote)
|
||||
return vote, err
|
||||
}
|
||||
|
||||
@@ -136,7 +145,15 @@ func (vss ValidatorStubsByAddress) Len() int {
|
||||
}
|
||||
|
||||
func (vss ValidatorStubsByAddress) Less(i, j int) bool {
|
||||
return bytes.Compare(vss[i].GetPubKey().Address(), vss[j].GetPubKey().Address()) == -1
|
||||
vssi, err := vss[i].GetPubKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
vssj, err := vss[j].GetPubKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bytes.Compare(vssi.Address(), vssj.Address()) == -1
|
||||
}
|
||||
|
||||
func (vss ValidatorStubsByAddress) Swap(i, j int) {
|
||||
@@ -199,7 +216,9 @@ func signAddVotes(
|
||||
|
||||
func validatePrevote(t *testing.T, cs *State, round int, privVal *validatorStub, blockHash []byte) {
|
||||
prevotes := cs.Votes.Prevotes(round)
|
||||
address := privVal.GetPubKey().Address()
|
||||
pubKey, err := privVal.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
address := pubKey.Address()
|
||||
var vote *types.Vote
|
||||
if vote = prevotes.GetByAddress(address); vote == nil {
|
||||
panic("Failed to find prevote from validator")
|
||||
@@ -217,7 +236,9 @@ func validatePrevote(t *testing.T, cs *State, round int, privVal *validatorStub,
|
||||
|
||||
func validateLastPrecommit(t *testing.T, cs *State, privVal *validatorStub, blockHash []byte) {
|
||||
votes := cs.LastCommit
|
||||
address := privVal.GetPubKey().Address()
|
||||
pv, err := privVal.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
address := pv.Address()
|
||||
var vote *types.Vote
|
||||
if vote = votes.GetByAddress(address); vote == nil {
|
||||
panic("Failed to find precommit from validator")
|
||||
@@ -237,7 +258,9 @@ func validatePrecommit(
|
||||
lockedBlockHash []byte,
|
||||
) {
|
||||
precommits := cs.Votes.Precommits(thisRound)
|
||||
address := privVal.GetPubKey().Address()
|
||||
pv, err := privVal.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
address := pv.Address()
|
||||
var vote *types.Vote
|
||||
if vote = precommits.GetByAddress(address); vote == nil {
|
||||
panic("Failed to find precommit from validator")
|
||||
|
||||
@@ -9,12 +9,13 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
"github.com/tendermint/tendermint/abci/example/code"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
mempl "github.com/tendermint/tendermint/mempool"
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
)
|
||||
|
||||
// for testing
|
||||
|
||||
+18
-15
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
amino "github.com/tendermint/go-amino"
|
||||
|
||||
cstypes "github.com/tendermint/tendermint/consensus/types"
|
||||
"github.com/tendermint/tendermint/libs/bits"
|
||||
tmevents "github.com/tendermint/tendermint/libs/events"
|
||||
@@ -57,7 +58,7 @@ func NewReactor(consensusState *State, fastSync bool, options ...ReactorOption)
|
||||
metrics: NopMetrics(),
|
||||
}
|
||||
conR.updateFastSyncingMetric()
|
||||
conR.BaseReactor = *p2p.NewBaseReactor("Reactor", conR)
|
||||
conR.BaseReactor = *p2p.NewBaseReactor("Consensus", conR)
|
||||
|
||||
for _, option := range options {
|
||||
option(conR)
|
||||
@@ -493,8 +494,8 @@ OUTER_LOOP:
|
||||
}
|
||||
}
|
||||
|
||||
// If the peer is on a previous height, help catch up.
|
||||
if (0 < prs.Height) && (prs.Height < rs.Height) {
|
||||
// If the peer is on a previous height that we have, help catch up.
|
||||
if (0 < prs.Height) && (prs.Height < rs.Height) && (prs.Height >= conR.conS.blockStore.Base()) {
|
||||
heightLogger := logger.With("height", prs.Height)
|
||||
|
||||
// if we never received the commit message from the peer, the block parts wont be initialized
|
||||
@@ -502,7 +503,7 @@ OUTER_LOOP:
|
||||
blockMeta := conR.conS.blockStore.LoadBlockMeta(prs.Height)
|
||||
if blockMeta == nil {
|
||||
heightLogger.Error("Failed to load block meta",
|
||||
"blockstoreHeight", conR.conS.blockStore.Height())
|
||||
"blockstoreBase", conR.conS.blockStore.Base(), "blockstoreHeight", conR.conS.blockStore.Height())
|
||||
time.Sleep(conR.conS.config.PeerGossipSleepDuration)
|
||||
} else {
|
||||
ps.InitProposalBlockParts(blockMeta.BlockID.PartsHeader)
|
||||
@@ -566,8 +567,8 @@ func (conR *Reactor) gossipDataForCatchup(logger log.Logger, rs *cstypes.RoundSt
|
||||
// Ensure that the peer's PartSetHeader is correct
|
||||
blockMeta := conR.conS.blockStore.LoadBlockMeta(prs.Height)
|
||||
if blockMeta == nil {
|
||||
logger.Error("Failed to load block meta",
|
||||
"ourHeight", rs.Height, "blockstoreHeight", conR.conS.blockStore.Height())
|
||||
logger.Error("Failed to load block meta", "ourHeight", rs.Height,
|
||||
"blockstoreBase", conR.conS.blockStore.Base(), "blockstoreHeight", conR.conS.blockStore.Height())
|
||||
time.Sleep(conR.conS.config.PeerGossipSleepDuration)
|
||||
return
|
||||
} else if !blockMeta.BlockID.PartsHeader.Equals(prs.ProposalBlockPartsHeader) {
|
||||
@@ -802,15 +803,17 @@ OUTER_LOOP:
|
||||
// Maybe send Height/CatchupCommitRound/CatchupCommit.
|
||||
{
|
||||
prs := ps.GetRoundState()
|
||||
if prs.CatchupCommitRound != -1 && 0 < prs.Height && prs.Height <= conR.conS.blockStore.Height() {
|
||||
commit := conR.conS.LoadCommit(prs.Height)
|
||||
peer.TrySend(StateChannel, cdc.MustMarshalBinaryBare(&VoteSetMaj23Message{
|
||||
Height: prs.Height,
|
||||
Round: commit.Round,
|
||||
Type: types.PrecommitType,
|
||||
BlockID: commit.BlockID,
|
||||
}))
|
||||
time.Sleep(conR.conS.config.PeerQueryMaj23SleepDuration)
|
||||
if prs.CatchupCommitRound != -1 && prs.Height > 0 && prs.Height <= conR.conS.blockStore.Height() &&
|
||||
prs.Height >= conR.conS.blockStore.Base() {
|
||||
if commit := conR.conS.LoadCommit(prs.Height); commit != nil {
|
||||
peer.TrySend(StateChannel, cdc.MustMarshalBinaryBare(&VoteSetMaj23Message{
|
||||
Height: prs.Height,
|
||||
Round: commit.Round,
|
||||
Type: types.PrecommitType,
|
||||
BlockID: commit.BlockID,
|
||||
}))
|
||||
time.Sleep(conR.conS.config.PeerQueryMaj23SleepDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+21
-11
@@ -14,6 +14,8 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
abcicli "github.com/tendermint/tendermint/abci/client"
|
||||
"github.com/tendermint/tendermint/abci/example/kvstore"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
@@ -29,7 +31,6 @@ import (
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/store"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
)
|
||||
|
||||
//----------------------------------------------
|
||||
@@ -154,8 +155,9 @@ func TestReactorWithEvidence(t *testing.T) {
|
||||
// mock the evidence pool
|
||||
// everyone includes evidence of another double signing
|
||||
vIdx := (i + 1) % nValidators
|
||||
addr := privVals[vIdx].GetPubKey().Address()
|
||||
evpool := newMockEvidencePool(addr)
|
||||
pubKey, err := privVals[vIdx].GetPubKey()
|
||||
require.NoError(t, err)
|
||||
evpool := newMockEvidencePool(pubKey.Address())
|
||||
|
||||
// Make State
|
||||
blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyAppConnCon, mempool, evpool)
|
||||
@@ -331,7 +333,9 @@ func TestReactorVotingPowerChange(t *testing.T) {
|
||||
// map of active validators
|
||||
activeVals := make(map[string]struct{})
|
||||
for i := 0; i < nVals; i++ {
|
||||
addr := css[i].privValidator.GetPubKey().Address()
|
||||
pubKey, err := css[i].privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
addr := pubKey.Address()
|
||||
activeVals[string(addr)] = struct{}{}
|
||||
}
|
||||
|
||||
@@ -343,7 +347,8 @@ func TestReactorVotingPowerChange(t *testing.T) {
|
||||
//---------------------------------------------------------------------------
|
||||
logger.Debug("---------------------------- Testing changing the voting power of one validator a few times")
|
||||
|
||||
val1PubKey := css[0].privValidator.GetPubKey()
|
||||
val1PubKey, err := css[0].privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
val1PubKeyABCI := types.TM2PB.PubKey(val1PubKey)
|
||||
updateValidatorTx := kvstore.MakeValSetChangeTx(val1PubKeyABCI, 25)
|
||||
previousTotalVotingPower := css[0].GetRoundState().LastValidators.TotalVotingPower()
|
||||
@@ -410,8 +415,9 @@ func TestReactorValidatorSetChanges(t *testing.T) {
|
||||
// map of active validators
|
||||
activeVals := make(map[string]struct{})
|
||||
for i := 0; i < nVals; i++ {
|
||||
addr := css[i].privValidator.GetPubKey().Address()
|
||||
activeVals[string(addr)] = struct{}{}
|
||||
pubKey, err := css[i].privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
activeVals[string(pubKey.Address())] = struct{}{}
|
||||
}
|
||||
|
||||
// wait till everyone makes block 1
|
||||
@@ -422,7 +428,8 @@ func TestReactorValidatorSetChanges(t *testing.T) {
|
||||
//---------------------------------------------------------------------------
|
||||
logger.Info("---------------------------- Testing adding one validator")
|
||||
|
||||
newValidatorPubKey1 := css[nVals].privValidator.GetPubKey()
|
||||
newValidatorPubKey1, err := css[nVals].privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
valPubKey1ABCI := types.TM2PB.PubKey(newValidatorPubKey1)
|
||||
newValidatorTx1 := kvstore.MakeValSetChangeTx(valPubKey1ABCI, testMinPower)
|
||||
|
||||
@@ -449,7 +456,8 @@ func TestReactorValidatorSetChanges(t *testing.T) {
|
||||
//---------------------------------------------------------------------------
|
||||
logger.Info("---------------------------- Testing changing the voting power of one validator")
|
||||
|
||||
updateValidatorPubKey1 := css[nVals].privValidator.GetPubKey()
|
||||
updateValidatorPubKey1, err := css[nVals].privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
updatePubKey1ABCI := types.TM2PB.PubKey(updateValidatorPubKey1)
|
||||
updateValidatorTx1 := kvstore.MakeValSetChangeTx(updatePubKey1ABCI, 25)
|
||||
previousTotalVotingPower := css[nVals].GetRoundState().LastValidators.TotalVotingPower()
|
||||
@@ -469,11 +477,13 @@ func TestReactorValidatorSetChanges(t *testing.T) {
|
||||
//---------------------------------------------------------------------------
|
||||
logger.Info("---------------------------- Testing adding two validators at once")
|
||||
|
||||
newValidatorPubKey2 := css[nVals+1].privValidator.GetPubKey()
|
||||
newValidatorPubKey2, err := css[nVals+1].privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
newVal2ABCI := types.TM2PB.PubKey(newValidatorPubKey2)
|
||||
newValidatorTx2 := kvstore.MakeValSetChangeTx(newVal2ABCI, testMinPower)
|
||||
|
||||
newValidatorPubKey3 := css[nVals+2].privValidator.GetPubKey()
|
||||
newValidatorPubKey3, err := css[nVals+2].privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
newVal3ABCI := types.TM2PB.PubKey(newValidatorPubKey3)
|
||||
newValidatorTx3 := kvstore.MakeValSetChangeTx(newVal3ABCI, testMinPower)
|
||||
|
||||
|
||||
+7
-2
@@ -288,6 +288,7 @@ func (h *Handshaker) ReplayBlocks(
|
||||
appBlockHeight int64,
|
||||
proxyApp proxy.AppConns,
|
||||
) ([]byte, error) {
|
||||
storeBlockBase := h.store.Base()
|
||||
storeBlockHeight := h.store.Height()
|
||||
stateBlockHeight := state.LastBlockHeight
|
||||
h.logger.Info(
|
||||
@@ -341,12 +342,16 @@ func (h *Handshaker) ReplayBlocks(
|
||||
}
|
||||
}
|
||||
|
||||
// First handle edge cases and constraints on the storeBlockHeight.
|
||||
// First handle edge cases and constraints on the storeBlockHeight and storeBlockBase.
|
||||
switch {
|
||||
case storeBlockHeight == 0:
|
||||
assertAppHashEqualsOneFromState(appHash, state)
|
||||
return appHash, nil
|
||||
|
||||
case appBlockHeight < storeBlockBase-1:
|
||||
// the app is too far behind truncated store (can be 1 behind since we replay the next)
|
||||
return appHash, sm.ErrAppBlockHeightTooLow{AppHeight: appBlockHeight, StoreBase: storeBlockBase}
|
||||
|
||||
case storeBlockHeight < appBlockHeight:
|
||||
// the app should never be ahead of the store (but this is under app's control)
|
||||
return appHash, sm.ErrAppBlockHeightTooHigh{CoreHeight: storeBlockHeight, AppHeight: appBlockHeight}
|
||||
@@ -472,7 +477,7 @@ func (h *Handshaker) replayBlock(state sm.State, height int64, proxyApp proxy.Ap
|
||||
blockExec.SetEventBus(h.eventBus)
|
||||
|
||||
var err error
|
||||
state, err = blockExec.ApplyBlock(state, meta.BlockID, block)
|
||||
state, _, err = blockExec.ApplyBlock(state, meta.BlockID, block)
|
||||
if err != nil {
|
||||
return sm.State{}, err
|
||||
}
|
||||
|
||||
+76
-21
@@ -17,6 +17,8 @@ import (
|
||||
|
||||
"sort"
|
||||
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
"github.com/tendermint/tendermint/abci/example/kvstore"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
@@ -30,7 +32,6 @@ import (
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/tendermint/version"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -306,7 +307,8 @@ var (
|
||||
// 0 - all synced up
|
||||
// 1 - saved block but app and state are behind
|
||||
// 2 - save block and committed but state is behind
|
||||
var modes = []uint{0, 1, 2}
|
||||
// 3 - save block and committed with truncated block store and state behind
|
||||
var modes = []uint{0, 1, 2, 3}
|
||||
|
||||
// This is actually not a test, it's for storing validator change tx data for testHandshakeReplay
|
||||
func TestSimulateValidatorsChange(t *testing.T) {
|
||||
@@ -344,10 +346,11 @@ func TestSimulateValidatorsChange(t *testing.T) {
|
||||
//height 2
|
||||
height++
|
||||
incrementHeight(vss...)
|
||||
newValidatorPubKey1 := css[nVals].privValidator.GetPubKey()
|
||||
newValidatorPubKey1, err := css[nVals].privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
valPubKey1ABCI := types.TM2PB.PubKey(newValidatorPubKey1)
|
||||
newValidatorTx1 := kvstore.MakeValSetChangeTx(valPubKey1ABCI, testMinPower)
|
||||
err := assertMempool(css[0].txNotifier).CheckTx(newValidatorTx1, nil, mempl.TxInfo{})
|
||||
err = assertMempool(css[0].txNotifier).CheckTx(newValidatorTx1, nil, mempl.TxInfo{})
|
||||
assert.Nil(t, err)
|
||||
propBlock, _ := css[0].createProposalBlock() //changeProposer(t, cs1, vs2)
|
||||
propBlockParts := propBlock.MakePartSet(partSize)
|
||||
@@ -369,7 +372,8 @@ func TestSimulateValidatorsChange(t *testing.T) {
|
||||
//height 3
|
||||
height++
|
||||
incrementHeight(vss...)
|
||||
updateValidatorPubKey1 := css[nVals].privValidator.GetPubKey()
|
||||
updateValidatorPubKey1, err := css[nVals].privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
updatePubKey1ABCI := types.TM2PB.PubKey(updateValidatorPubKey1)
|
||||
updateValidatorTx1 := kvstore.MakeValSetChangeTx(updatePubKey1ABCI, 25)
|
||||
err = assertMempool(css[0].txNotifier).CheckTx(updateValidatorTx1, nil, mempl.TxInfo{})
|
||||
@@ -394,12 +398,14 @@ func TestSimulateValidatorsChange(t *testing.T) {
|
||||
//height 4
|
||||
height++
|
||||
incrementHeight(vss...)
|
||||
newValidatorPubKey2 := css[nVals+1].privValidator.GetPubKey()
|
||||
newValidatorPubKey2, err := css[nVals+1].privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
newVal2ABCI := types.TM2PB.PubKey(newValidatorPubKey2)
|
||||
newValidatorTx2 := kvstore.MakeValSetChangeTx(newVal2ABCI, testMinPower)
|
||||
err = assertMempool(css[0].txNotifier).CheckTx(newValidatorTx2, nil, mempl.TxInfo{})
|
||||
assert.Nil(t, err)
|
||||
newValidatorPubKey3 := css[nVals+2].privValidator.GetPubKey()
|
||||
newValidatorPubKey3, err := css[nVals+2].privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
newVal3ABCI := types.TM2PB.PubKey(newValidatorPubKey3)
|
||||
newValidatorTx3 := kvstore.MakeValSetChangeTx(newVal3ABCI, testMinPower)
|
||||
err = assertMempool(css[0].txNotifier).CheckTx(newValidatorTx3, nil, mempl.TxInfo{})
|
||||
@@ -412,7 +418,13 @@ func TestSimulateValidatorsChange(t *testing.T) {
|
||||
sort.Sort(ValidatorStubsByAddress(newVss))
|
||||
selfIndex := 0
|
||||
for i, vs := range newVss {
|
||||
if vs.GetPubKey().Equals(css[0].privValidator.GetPubKey()) {
|
||||
vsPubKey, err := vs.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
css0PubKey, err := css[0].privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
if vsPubKey.Equals(css0PubKey) {
|
||||
selfIndex = i
|
||||
break
|
||||
}
|
||||
@@ -469,7 +481,13 @@ func TestSimulateValidatorsChange(t *testing.T) {
|
||||
copy(newVss, vss[:nVals+3])
|
||||
sort.Sort(ValidatorStubsByAddress(newVss))
|
||||
for i, vs := range newVss {
|
||||
if vs.GetPubKey().Equals(css[0].privValidator.GetPubKey()) {
|
||||
vsKeyKey, err := vs.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
css0PubKey, err := css[0].privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
if vsKeyKey.Equals(css0PubKey) {
|
||||
selfIndex = i
|
||||
break
|
||||
}
|
||||
@@ -514,10 +532,10 @@ func TestHandshakeReplayAll(t *testing.T) {
|
||||
// Sync many, not from scratch
|
||||
func TestHandshakeReplaySome(t *testing.T) {
|
||||
for _, m := range modes {
|
||||
testHandshakeReplay(t, config, 1, m, false)
|
||||
testHandshakeReplay(t, config, 2, m, false)
|
||||
}
|
||||
for _, m := range modes {
|
||||
testHandshakeReplay(t, config, 1, m, true)
|
||||
testHandshakeReplay(t, config, 2, m, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,7 +638,7 @@ func testHandshakeReplay(t *testing.T, config *cfg.Config, nBlocks int, mode uin
|
||||
stateDB = dbm.NewMemDB()
|
||||
genisisState = sim.GenesisState
|
||||
config = sim.Config
|
||||
chain = sim.Chain
|
||||
chain = append([]*types.Block{}, sim.Chain...) // copy chain
|
||||
commits = sim.Commits
|
||||
store = newMockBlockStore(config, genisisState.ConsensusParams)
|
||||
} else { //test single node
|
||||
@@ -642,7 +660,9 @@ func testHandshakeReplay(t *testing.T, config *cfg.Config, nBlocks int, mode uin
|
||||
|
||||
chain, commits, err = makeBlockchainFromWAL(wal)
|
||||
require.NoError(t, err)
|
||||
stateDB, genisisState, store = stateAndStore(config, privVal.GetPubKey(), kvstore.ProtocolVersion)
|
||||
pubKey, err := privVal.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
stateDB, genisisState, store = stateAndStore(config, pubKey, kvstore.ProtocolVersion)
|
||||
}
|
||||
store.chain = chain
|
||||
store.commits = commits
|
||||
@@ -666,6 +686,15 @@ func testHandshakeReplay(t *testing.T, config *cfg.Config, nBlocks int, mode uin
|
||||
buildAppStateFromChain(proxyApp, stateDB1, genisisState, chain, nBlocks, mode)
|
||||
}
|
||||
|
||||
// Prune block store if requested
|
||||
expectError := false
|
||||
if mode == 3 {
|
||||
pruned, err := store.PruneBlocks(2)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, pruned)
|
||||
expectError = int64(nBlocks) < 2
|
||||
}
|
||||
|
||||
// now start the app using the handshake - it should sync
|
||||
genDoc, _ := sm.MakeGenesisDocFromFile(config.GenesisFile())
|
||||
handshaker := NewHandshaker(stateDB, state, store, genDoc)
|
||||
@@ -674,7 +703,11 @@ func testHandshakeReplay(t *testing.T, config *cfg.Config, nBlocks int, mode uin
|
||||
t.Fatalf("Error starting proxy app connections: %v", err)
|
||||
}
|
||||
defer proxyApp.Stop()
|
||||
if err := handshaker.Handshake(proxyApp); err != nil {
|
||||
err := handshaker.Handshake(proxyApp)
|
||||
if expectError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Fatalf("Error on abci handshake: %v", err)
|
||||
}
|
||||
|
||||
@@ -709,7 +742,7 @@ func applyBlock(stateDB dbm.DB, st sm.State, blk *types.Block, proxyApp proxy.Ap
|
||||
blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool)
|
||||
|
||||
blkID := types.BlockID{Hash: blk.Hash(), PartsHeader: blk.MakePartSet(testPartSize).Header()}
|
||||
newState, err := blockExec.ApplyBlock(st, blkID, blk)
|
||||
newState, _, err := blockExec.ApplyBlock(st, blkID, blk)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -739,17 +772,19 @@ func buildAppStateFromChain(proxyApp proxy.AppConns, stateDB dbm.DB,
|
||||
block := chain[i]
|
||||
state = applyBlock(stateDB, state, block, proxyApp)
|
||||
}
|
||||
case 1, 2:
|
||||
case 1, 2, 3:
|
||||
for i := 0; i < nBlocks-1; i++ {
|
||||
block := chain[i]
|
||||
state = applyBlock(stateDB, state, block, proxyApp)
|
||||
}
|
||||
|
||||
if mode == 2 {
|
||||
if mode == 2 || mode == 3 {
|
||||
// update the kvstore height and apphash
|
||||
// as if we ran commit but not
|
||||
state = applyBlock(stateDB, state, chain[nBlocks-1], proxyApp)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown mode %v", mode))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -787,7 +822,7 @@ func buildTMStateFromChain(
|
||||
state = applyBlock(stateDB, state, block, proxyApp)
|
||||
}
|
||||
|
||||
case 1, 2:
|
||||
case 1, 2, 3:
|
||||
// sync up to the penultimate as if we stored the block.
|
||||
// whether we commit or not depends on the appHash
|
||||
for _, block := range chain[:len(chain)-1] {
|
||||
@@ -797,6 +832,8 @@ func buildTMStateFromChain(
|
||||
// apply the final block to a state copy so we can
|
||||
// get the right next appHash but keep the state back
|
||||
applyBlock(stateDB, state, chain[len(chain)-1], proxyApp)
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown mode %v", mode))
|
||||
}
|
||||
|
||||
return state
|
||||
@@ -811,7 +848,9 @@ func TestHandshakePanicsIfAppReturnsWrongAppHash(t *testing.T) {
|
||||
t.Cleanup(func() { os.RemoveAll(config.RootDir) })
|
||||
privVal := privval.LoadFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile())
|
||||
const appVersion = 0x0
|
||||
stateDB, state, store := stateAndStore(config, privVal.GetPubKey(), appVersion)
|
||||
pubKey, err := privVal.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
stateDB, state, store := stateAndStore(config, pubKey, appVersion)
|
||||
genDoc, _ := sm.MakeGenesisDocFromFile(config.GenesisFile())
|
||||
state.LastValidators = state.Validators.Copy()
|
||||
// mode = 0 for committing all the blocks
|
||||
@@ -1054,14 +1093,17 @@ type mockBlockStore struct {
|
||||
params types.ConsensusParams
|
||||
chain []*types.Block
|
||||
commits []*types.Commit
|
||||
base int64
|
||||
}
|
||||
|
||||
// TODO: NewBlockStore(db.NewMemDB) ...
|
||||
func newMockBlockStore(config *cfg.Config, params types.ConsensusParams) *mockBlockStore {
|
||||
return &mockBlockStore{config, params, nil, nil}
|
||||
return &mockBlockStore{config, params, nil, nil, 0}
|
||||
}
|
||||
|
||||
func (bs *mockBlockStore) Height() int64 { return int64(len(bs.chain)) }
|
||||
func (bs *mockBlockStore) Base() int64 { return bs.base }
|
||||
func (bs *mockBlockStore) Size() int64 { return bs.Height() - bs.Base() + 1 }
|
||||
func (bs *mockBlockStore) LoadBlock(height int64) *types.Block { return bs.chain[height-1] }
|
||||
func (bs *mockBlockStore) LoadBlockByHash(hash []byte) *types.Block {
|
||||
return bs.chain[int64(len(bs.chain))-1]
|
||||
@@ -1083,6 +1125,17 @@ func (bs *mockBlockStore) LoadSeenCommit(height int64) *types.Commit {
|
||||
return bs.commits[height-1]
|
||||
}
|
||||
|
||||
func (bs *mockBlockStore) PruneBlocks(height int64) (uint64, error) {
|
||||
pruned := uint64(0)
|
||||
for i := int64(0); i < height-1; i++ {
|
||||
bs.chain[i] = nil
|
||||
bs.commits[i] = nil
|
||||
pruned++
|
||||
}
|
||||
bs.base = height
|
||||
return pruned, nil
|
||||
}
|
||||
|
||||
//---------------------------------------
|
||||
// Test handshake/init chain
|
||||
|
||||
@@ -1095,7 +1148,9 @@ func TestHandshakeUpdatesValidators(t *testing.T) {
|
||||
config := ResetConfig("handshake_test_")
|
||||
t.Cleanup(func() { os.RemoveAll(config.RootDir) })
|
||||
privVal := privval.LoadFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile())
|
||||
stateDB, state, store := stateAndStore(config, privVal.GetPubKey(), 0x0)
|
||||
pubKey, err := privVal.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
stateDB, state, store := stateAndStore(config, pubKey, 0x0)
|
||||
|
||||
oldValAddr := state.Validators.Validators[0].Address
|
||||
|
||||
|
||||
+120
-32
@@ -490,6 +490,10 @@ func (cs *State) reconstructLastCommit(state sm.State) {
|
||||
return
|
||||
}
|
||||
seenCommit := cs.blockStore.LoadSeenCommit(state.LastBlockHeight)
|
||||
if seenCommit == nil {
|
||||
panic(fmt.Sprintf("Failed to reconstruct LastCommit: seen commit for height %v not found",
|
||||
state.LastBlockHeight))
|
||||
}
|
||||
lastPrecommits := types.CommitToVoteSet(state.ChainID, seenCommit, state.LastValidators)
|
||||
if !lastPrecommits.HasTwoThirdsMajority() {
|
||||
panic("Failed to reconstruct LastCommit: Does not have +2/3 maj")
|
||||
@@ -878,6 +882,9 @@ func (cs *State) needProofBlock(height int64) bool {
|
||||
}
|
||||
|
||||
lastBlockMeta := cs.blockStore.LoadBlockMeta(height - 1)
|
||||
if lastBlockMeta == nil {
|
||||
panic(fmt.Sprintf("needProofBlock: last block meta for height %d not found", height-1))
|
||||
}
|
||||
return !bytes.Equal(cs.state.AppHash, lastBlockMeta.Header.AppHash)
|
||||
}
|
||||
|
||||
@@ -921,19 +928,27 @@ func (cs *State) enterPropose(height int64, round int) {
|
||||
logger.Debug("This node is not a validator")
|
||||
return
|
||||
}
|
||||
logger.Debug("This node is a validator")
|
||||
|
||||
pubKey, err := cs.privValidator.GetPubKey()
|
||||
if err != nil {
|
||||
// If this node is a validator & proposer in the current round, it will
|
||||
// miss the opportunity to create a block.
|
||||
logger.Error("Error on retrival of pubkey", "err", err)
|
||||
return
|
||||
}
|
||||
address := pubKey.Address()
|
||||
|
||||
// if not a validator, we're done
|
||||
address := cs.privValidator.GetPubKey().Address()
|
||||
if !cs.Validators.HasAddress(address) {
|
||||
logger.Debug("This node is not a validator", "addr", address, "vals", cs.Validators)
|
||||
return
|
||||
}
|
||||
logger.Debug("This node is a validator")
|
||||
|
||||
if cs.isProposer(address) {
|
||||
logger.Info("enterPropose: Our turn to propose",
|
||||
"proposer",
|
||||
cs.Validators.GetProposer().Address,
|
||||
address,
|
||||
"privValidator",
|
||||
cs.privValidator)
|
||||
cs.decideProposal(height, round)
|
||||
@@ -961,7 +976,7 @@ func (cs *State) defaultDecideProposal(height int64, round int) {
|
||||
} else {
|
||||
// Create a new proposal block from state/txs from the mempool.
|
||||
block, blockParts = cs.createProposalBlock()
|
||||
if block == nil { // on error
|
||||
if block == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1004,11 +1019,13 @@ func (cs *State) isProposalComplete() bool {
|
||||
|
||||
}
|
||||
|
||||
// Create the next block to propose and return it.
|
||||
// We really only need to return the parts, but the block
|
||||
// is returned for convenience so we can log the proposal block.
|
||||
// Returns nil block upon error.
|
||||
// Create the next block to propose and return it. Returns nil block upon error.
|
||||
//
|
||||
// We really only need to return the parts, but the block is returned for
|
||||
// convenience so we can log the proposal block.
|
||||
//
|
||||
// NOTE: keep it side-effect free for clarity.
|
||||
// CONTRACT: cs.privValidator is not nil.
|
||||
func (cs *State) createProposalBlock() (block *types.Block, blockParts *types.PartSet) {
|
||||
var commit *types.Commit
|
||||
switch {
|
||||
@@ -1019,13 +1036,23 @@ func (cs *State) createProposalBlock() (block *types.Block, blockParts *types.Pa
|
||||
case cs.LastCommit.HasTwoThirdsMajority():
|
||||
// Make the commit from LastCommit
|
||||
commit = cs.LastCommit.MakeCommit()
|
||||
default:
|
||||
// This shouldn't happen.
|
||||
cs.Logger.Error("enterPropose: Cannot propose anything: No commit for the previous block.")
|
||||
default: // This shouldn't happen.
|
||||
cs.Logger.Error("enterPropose: Cannot propose anything: No commit for the previous block")
|
||||
return
|
||||
}
|
||||
|
||||
proposerAddr := cs.privValidator.GetPubKey().Address()
|
||||
if cs.privValidator == nil {
|
||||
panic("entered createProposalBlock with privValidator being nil")
|
||||
}
|
||||
pubKey, err := cs.privValidator.GetPubKey()
|
||||
if err != nil {
|
||||
// If this node is a validator & proposer in the current round, it will
|
||||
// miss the opportunity to create a block.
|
||||
cs.Logger.Error("Error on retrival of pubkey", "err", err)
|
||||
return
|
||||
}
|
||||
proposerAddr := pubKey.Address()
|
||||
|
||||
return cs.blockExec.CreateProposalBlock(cs.Height, cs.state, commit, proposerAddr)
|
||||
}
|
||||
|
||||
@@ -1428,7 +1455,8 @@ func (cs *State) finalizeCommit(height int64) {
|
||||
// Execute and commit the block, update and save the state, and update the mempool.
|
||||
// NOTE The block.AppHash wont reflect these txs until the next block.
|
||||
var err error
|
||||
stateCopy, err = cs.blockExec.ApplyBlock(
|
||||
var retainHeight int64
|
||||
stateCopy, retainHeight, err = cs.blockExec.ApplyBlock(
|
||||
stateCopy,
|
||||
types.BlockID{Hash: block.Hash(), PartsHeader: blockParts.Header()},
|
||||
block)
|
||||
@@ -1443,6 +1471,16 @@ func (cs *State) finalizeCommit(height int64) {
|
||||
|
||||
fail.Fail() // XXX
|
||||
|
||||
// Prune old heights, if requested by ABCI app.
|
||||
if retainHeight > 0 {
|
||||
pruned, err := cs.pruneBlocks(retainHeight)
|
||||
if err != nil {
|
||||
cs.Logger.Error("Failed to prune blocks", "retainHeight", retainHeight, "err", err)
|
||||
} else {
|
||||
cs.Logger.Info("Pruned blocks", "pruned", pruned, "retainHeight", retainHeight)
|
||||
}
|
||||
}
|
||||
|
||||
// must be called before we update state
|
||||
cs.recordMetrics(height, block)
|
||||
|
||||
@@ -1461,6 +1499,22 @@ func (cs *State) finalizeCommit(height int64) {
|
||||
// * cs.StartTime is set to when we will start round0.
|
||||
}
|
||||
|
||||
func (cs *State) pruneBlocks(retainHeight int64) (uint64, error) {
|
||||
base := cs.blockStore.Base()
|
||||
if retainHeight <= base {
|
||||
return 0, nil
|
||||
}
|
||||
pruned, err := cs.blockStore.PruneBlocks(retainHeight)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to prune block store: %w", err)
|
||||
}
|
||||
err = sm.PruneStates(cs.blockExec.DB(), base, retainHeight)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to prune state database: %w", err)
|
||||
}
|
||||
return pruned, nil
|
||||
}
|
||||
|
||||
func (cs *State) recordMetrics(height int64, block *types.Block) {
|
||||
cs.metrics.Validators.Set(float64(cs.Validators.Size()))
|
||||
cs.metrics.ValidatorsPower.Set(float64(cs.Validators.TotalVotingPower()))
|
||||
@@ -1491,15 +1545,24 @@ func (cs *State) recordMetrics(height int64, block *types.Block) {
|
||||
missingValidatorsPower += val.VotingPower
|
||||
}
|
||||
|
||||
if cs.privValidator != nil && bytes.Equal(val.Address, cs.privValidator.GetPubKey().Address()) {
|
||||
label := []string{
|
||||
"validator_address", val.Address.String(),
|
||||
if cs.privValidator != nil {
|
||||
pubKey, err := cs.privValidator.GetPubKey()
|
||||
if err != nil {
|
||||
// Metrics won't be updated, but it's not critical.
|
||||
cs.Logger.Error("Error on retrival of pubkey", "err", err)
|
||||
continue
|
||||
}
|
||||
cs.metrics.ValidatorPower.With(label...).Set(float64(val.VotingPower))
|
||||
if commitSig.ForBlock() {
|
||||
cs.metrics.ValidatorLastSignedHeight.With(label...).Set(float64(height))
|
||||
} else {
|
||||
cs.metrics.ValidatorMissedBlocks.With(label...).Add(float64(1))
|
||||
|
||||
if bytes.Equal(val.Address, pubKey.Address()) {
|
||||
label := []string{
|
||||
"validator_address", val.Address.String(),
|
||||
}
|
||||
cs.metrics.ValidatorPower.With(label...).Set(float64(val.VotingPower))
|
||||
if commitSig.ForBlock() {
|
||||
cs.metrics.ValidatorLastSignedHeight.With(label...).Set(float64(height))
|
||||
} else {
|
||||
cs.metrics.ValidatorMissedBlocks.With(label...).Add(float64(1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1518,9 +1581,11 @@ func (cs *State) recordMetrics(height int64, block *types.Block) {
|
||||
|
||||
if height > 1 {
|
||||
lastBlockMeta := cs.blockStore.LoadBlockMeta(height - 1)
|
||||
cs.metrics.BlockIntervalSeconds.Set(
|
||||
block.Time.Sub(lastBlockMeta.Header.Time).Seconds(),
|
||||
)
|
||||
if lastBlockMeta != nil {
|
||||
cs.metrics.BlockIntervalSeconds.Set(
|
||||
block.Time.Sub(lastBlockMeta.Header.Time).Seconds(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
cs.metrics.NumTxs.Set(float64(len(block.Data.Txs)))
|
||||
@@ -1648,8 +1713,12 @@ func (cs *State) tryAddVote(vote *types.Vote, peerID p2p.ID) (bool, error) {
|
||||
if err == ErrVoteHeightMismatch {
|
||||
return added, err
|
||||
} else if voteErr, ok := err.(*types.ErrVoteConflictingVotes); ok {
|
||||
addr := cs.privValidator.GetPubKey().Address()
|
||||
if bytes.Equal(vote.ValidatorAddress, addr) {
|
||||
pubKey, err := cs.privValidator.GetPubKey()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "can't get pubkey")
|
||||
}
|
||||
|
||||
if bytes.Equal(vote.ValidatorAddress, pubKey.Address()) {
|
||||
cs.Logger.Error(
|
||||
"Found conflicting vote from ourselves. Did you unsafe_reset a validator?",
|
||||
"height",
|
||||
@@ -1838,6 +1907,7 @@ func (cs *State) addVote(
|
||||
return added, err
|
||||
}
|
||||
|
||||
// CONTRACT: cs.privValidator is not nil.
|
||||
func (cs *State) signVote(
|
||||
msgType types.SignedMsgType,
|
||||
hash []byte,
|
||||
@@ -1847,19 +1917,24 @@ func (cs *State) signVote(
|
||||
// and the privValidator will refuse to sign anything.
|
||||
cs.wal.FlushAndSync()
|
||||
|
||||
addr := cs.privValidator.GetPubKey().Address()
|
||||
valIndex, _ := cs.Validators.GetByAddress(addr)
|
||||
pubKey, err := cs.privValidator.GetPubKey()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can't get pubkey")
|
||||
}
|
||||
addr := pubKey.Address()
|
||||
valIdx, _ := cs.Validators.GetByAddress(addr)
|
||||
|
||||
vote := &types.Vote{
|
||||
ValidatorAddress: addr,
|
||||
ValidatorIndex: valIndex,
|
||||
ValidatorIndex: valIdx,
|
||||
Height: cs.Height,
|
||||
Round: cs.Round,
|
||||
Timestamp: cs.voteTime(),
|
||||
Type: msgType,
|
||||
BlockID: types.BlockID{Hash: hash, PartsHeader: header},
|
||||
}
|
||||
err := cs.privValidator.SignVote(cs.state.ChainID, vote)
|
||||
|
||||
err = cs.privValidator.SignVote(cs.state.ChainID, vote)
|
||||
return vote, err
|
||||
}
|
||||
|
||||
@@ -1884,10 +1959,23 @@ func (cs *State) voteTime() time.Time {
|
||||
|
||||
// sign the vote and publish on internalMsgQueue
|
||||
func (cs *State) signAddVote(msgType types.SignedMsgType, hash []byte, header types.PartSetHeader) *types.Vote {
|
||||
// if we don't have a key or we're not in the validator set, do nothing
|
||||
if cs.privValidator == nil || !cs.Validators.HasAddress(cs.privValidator.GetPubKey().Address()) {
|
||||
if cs.privValidator == nil { // the node does not have a key
|
||||
return nil
|
||||
}
|
||||
|
||||
pubKey, err := cs.privValidator.GetPubKey()
|
||||
if err != nil {
|
||||
// Vote won't be signed, but it's not critical.
|
||||
cs.Logger.Error("Error on retrival of pubkey", "err", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the node not in the validator set, do nothing.
|
||||
if !cs.Validators.HasAddress(pubKey.Address()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: pass pubKey to signVote
|
||||
vote, err := cs.signVote(msgType, hash, header)
|
||||
if err == nil {
|
||||
cs.sendInternalMessage(msgInfo{&VoteMessage{vote}, ""})
|
||||
|
||||
+48
-16
@@ -65,7 +65,9 @@ func TestStateProposerSelection0(t *testing.T) {
|
||||
|
||||
// Commit a block and ensure proposer for the next height is correct.
|
||||
prop := cs1.GetRoundState().Validators.GetProposer()
|
||||
address := cs1.privValidator.GetPubKey().Address()
|
||||
pv, err := cs1.privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
address := pv.Address()
|
||||
if !bytes.Equal(prop.Address, address) {
|
||||
t.Fatalf("expected proposer to be validator %d. Got %X", 0, prop.Address)
|
||||
}
|
||||
@@ -80,7 +82,9 @@ func TestStateProposerSelection0(t *testing.T) {
|
||||
ensureNewRound(newRoundCh, height+1, 0)
|
||||
|
||||
prop = cs1.GetRoundState().Validators.GetProposer()
|
||||
addr := vss[1].GetPubKey().Address()
|
||||
pv1, err := vss[1].GetPubKey()
|
||||
require.NoError(t, err)
|
||||
addr := pv1.Address()
|
||||
if !bytes.Equal(prop.Address, addr) {
|
||||
panic(fmt.Sprintf("expected proposer to be validator %d. Got %X", 1, prop.Address))
|
||||
}
|
||||
@@ -104,7 +108,9 @@ func TestStateProposerSelection2(t *testing.T) {
|
||||
// everyone just votes nil. we get a new proposer each round
|
||||
for i := 0; i < len(vss); i++ {
|
||||
prop := cs1.GetRoundState().Validators.GetProposer()
|
||||
addr := vss[(i+round)%len(vss)].GetPubKey().Address()
|
||||
pvk, err := vss[(i+round)%len(vss)].GetPubKey()
|
||||
require.NoError(t, err)
|
||||
addr := pvk.Address()
|
||||
correctProposer := addr
|
||||
if !bytes.Equal(prop.Address, correctProposer) {
|
||||
panic(fmt.Sprintf(
|
||||
@@ -516,7 +522,9 @@ func TestStateLockPOLRelock(t *testing.T) {
|
||||
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||
proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal)
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
pv1, err := cs1.privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
addr := pv1.Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
newBlockCh := subscribe(cs1.eventBus, types.EventQueryNewBlockHeader)
|
||||
@@ -608,7 +616,9 @@ func TestStateLockPOLUnlock(t *testing.T) {
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
unlockCh := subscribe(cs1.eventBus, types.EventQueryUnlock)
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
pv1, err := cs1.privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
addr := pv1.Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
|
||||
// everything done from perspective of cs1
|
||||
@@ -700,7 +710,9 @@ func TestStateLockPOLSafety1(t *testing.T) {
|
||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
pv1, err := cs1.privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
addr := pv1.Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
|
||||
// start round and wait for propose and prevote
|
||||
@@ -817,7 +829,9 @@ func TestStateLockPOLSafety2(t *testing.T) {
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
unlockCh := subscribe(cs1.eventBus, types.EventQueryUnlock)
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
pv1, err := cs1.privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
addr := pv1.Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
|
||||
// the block for R0: gets polkad but we miss it
|
||||
@@ -909,7 +923,9 @@ func TestProposeValidBlock(t *testing.T) {
|
||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
unlockCh := subscribe(cs1.eventBus, types.EventQueryUnlock)
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
pv1, err := cs1.privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
addr := pv1.Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
|
||||
// start round and wait for propose and prevote
|
||||
@@ -996,7 +1012,9 @@ func TestSetValidBlockOnDelayedPrevote(t *testing.T) {
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
validBlockCh := subscribe(cs1.eventBus, types.EventQueryValidBlock)
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
pv1, err := cs1.privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
addr := pv1.Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
|
||||
// start round and wait for propose and prevote
|
||||
@@ -1056,7 +1074,9 @@ func TestSetValidBlockOnDelayedProposal(t *testing.T) {
|
||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
validBlockCh := subscribe(cs1.eventBus, types.EventQueryValidBlock)
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
pv1, err := cs1.privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
addr := pv1.Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal)
|
||||
|
||||
@@ -1127,7 +1147,9 @@ func TestWaitingTimeoutProposeOnNewRound(t *testing.T) {
|
||||
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
pv1, err := cs1.privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
addr := pv1.Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
|
||||
// start round
|
||||
@@ -1161,7 +1183,9 @@ func TestRoundSkipOnNilPolkaFromHigherRound(t *testing.T) {
|
||||
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
pv1, err := cs1.privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
addr := pv1.Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
|
||||
// start round
|
||||
@@ -1195,7 +1219,9 @@ func TestWaitTimeoutProposeOnNilPolkaForTheCurrentRound(t *testing.T) {
|
||||
|
||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
pv1, err := cs1.privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
addr := pv1.Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
|
||||
// start round in which PO is not proposer
|
||||
@@ -1310,7 +1336,9 @@ func TestStartNextHeightCorrectly(t *testing.T) {
|
||||
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
newBlockHeader := subscribe(cs1.eventBus, types.EventQueryNewBlockHeader)
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
pv1, err := cs1.privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
addr := pv1.Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
|
||||
// start round and wait for propose and prevote
|
||||
@@ -1365,7 +1393,9 @@ func TestResetTimeoutPrecommitUponNewHeight(t *testing.T) {
|
||||
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
newBlockHeader := subscribe(cs1.eventBus, types.EventQueryNewBlockHeader)
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
pv1, err := cs1.privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
addr := pv1.Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
|
||||
// start round and wait for propose and prevote
|
||||
@@ -1502,7 +1532,9 @@ func TestStateHalt1(t *testing.T) {
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
newBlockCh := subscribe(cs1.eventBus, types.EventQueryNewBlock)
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
pv1, err := cs1.privValidator.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
addr := pv1.Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
|
||||
// start round and wait for propose and prevote
|
||||
|
||||
@@ -2,6 +2,7 @@ package types
|
||||
|
||||
import (
|
||||
amino "github.com/tendermint/go-amino"
|
||||
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
|
||||
@@ -54,9 +54,13 @@ func TestPeerCatchupRounds(t *testing.T) {
|
||||
|
||||
func makeVoteHR(t *testing.T, height int64, round int, privVals []types.PrivValidator, valIndex int) *types.Vote {
|
||||
privVal := privVals[valIndex]
|
||||
addr := privVal.GetPubKey().Address()
|
||||
pubKey, err := privVal.GetPubKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
vote := &types.Vote{
|
||||
ValidatorAddress: addr,
|
||||
ValidatorAddress: pubKey.Address(),
|
||||
ValidatorIndex: valIndex,
|
||||
Height: height,
|
||||
Round: round,
|
||||
@@ -65,7 +69,7 @@ func makeVoteHR(t *testing.T, height int64, round int, privVals []types.PrivVali
|
||||
BlockID: types.BlockID{Hash: []byte("fakehash"), PartsHeader: types.PartSetHeader{}},
|
||||
}
|
||||
chainID := config.ChainID()
|
||||
err := privVal.SignVote(chainID, vote)
|
||||
err = privVal.SignVote(chainID, vote)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Error signing vote: %v", err))
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
amino "github.com/tendermint/go-amino"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||
tmrand "github.com/tendermint/tendermint/libs/rand"
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
amino "github.com/tendermint/go-amino"
|
||||
|
||||
auto "github.com/tendermint/tendermint/libs/autofile"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmos "github.com/tendermint/tendermint/libs/os"
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
db "github.com/tendermint/tm-db"
|
||||
|
||||
"github.com/tendermint/tendermint/abci/example/kvstore"
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
@@ -21,7 +23,6 @@ import (
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/store"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
db "github.com/tendermint/tm-db"
|
||||
)
|
||||
|
||||
// WALGenerateNBlocks generates a consensus WAL. It does this by spinning up a
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"reflect"
|
||||
|
||||
amino "github.com/tendermint/go-amino"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
"github.com/tendermint/tendermint/crypto/multisig"
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
amino "github.com/tendermint/go-amino"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
"github.com/tendermint/tendermint/crypto/multisig"
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
|
||||
amino "github.com/tendermint/go-amino"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||
"github.com/tendermint/tendermint/libs/kv"
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||
)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
tmrand "github.com/tendermint/tendermint/libs/rand"
|
||||
)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package multisig
|
||||
|
||||
import (
|
||||
amino "github.com/tendermint/go-amino"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"golang.org/x/crypto/ripemd160" // nolint: staticcheck // necessary for Bitcoin address format
|
||||
|
||||
amino "github.com/tendermint/go-amino"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
package secp256k1
|
||||
|
||||
import (
|
||||
"github.com/magiconair/properties/assert"
|
||||
"testing"
|
||||
|
||||
"github.com/magiconair/properties/assert"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package sr25519
|
||||
|
||||
import (
|
||||
amino "github.com/tendermint/go-amino"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
:root
|
||||
--accent-color #00BB00
|
||||
--accent-color #018A01
|
||||
--background #222222
|
||||
+6
-3
@@ -2,8 +2,7 @@
|
||||
|
||||
The documentation for Tendermint Core is hosted at:
|
||||
|
||||
- https://docs.tendermint.com/master/ and
|
||||
- https://tendermint-staging.interblock.io/docs/
|
||||
- https://docs.tendermint.com/master/
|
||||
|
||||
built from the files in this (`/docs`) directory for
|
||||
[master](https://github.com/tendermint/tendermint/tree/master/docs) respectively.
|
||||
@@ -78,9 +77,13 @@ Install the theme and all dependencies.
|
||||
npm run serve
|
||||
```
|
||||
|
||||
<!-- markdown-link-check-disable -->
|
||||
|
||||
Run `pre` and `post` hooks and start a hot-reloading web-server. See output of this command for the URL (it is often https://localhost:8080).
|
||||
|
||||
To build documentation as a static website run `npm run build`. You will find the website in `.vuepress/dist` directory.
|
||||
<!-- markdown-link-check-enable -->
|
||||
|
||||
To build documentation as a static website run `npm run build`. You will find the website in `.vuepress/dist` directory.
|
||||
|
||||
## Search
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ Tendermint?](introduction/what-is-tendermint.md).
|
||||
|
||||
To get started quickly with an example application, see the [quick start guide](introduction/quick-start.md).
|
||||
|
||||
To learn about application development on Tendermint, see the [Application Blockchain Interface](spec/abci/).
|
||||
To learn about application development on Tendermint, see the [Application Blockchain Interface](https://github.com/tendermint/spec/tree/master/spec/abci).
|
||||
|
||||
For more details on using Tendermint, see the respective documentation for
|
||||
[Tendermint Core](tendermint-core/), [benchmarking and monitoring](tools/), and [network deployments](networks/).
|
||||
|
||||
@@ -142,7 +142,7 @@ response.
|
||||
The server may be generic for a particular language, and we provide a
|
||||
[reference implementation in
|
||||
Golang](https://github.com/tendermint/tendermint/tree/master/abci/server). See the
|
||||
[list of other ABCI implementations](./ecosystem.md) for servers in
|
||||
[list of other ABCI implementations](https://github.com/tendermint/awesome#ecosystem) for servers in
|
||||
other languages.
|
||||
|
||||
The handler is specific to the application, and may be arbitrary, so
|
||||
@@ -192,7 +192,7 @@ Try running these commands:
|
||||
> query "abc"
|
||||
-> code: OK
|
||||
-> log: exists
|
||||
-> height: 0
|
||||
-> height: 2
|
||||
-> value: abc
|
||||
-> value.hex: 616263
|
||||
|
||||
@@ -206,7 +206,7 @@ Try running these commands:
|
||||
> query "def"
|
||||
-> code: OK
|
||||
-> log: exists
|
||||
-> height: 0
|
||||
-> height: 3
|
||||
-> value: xyz
|
||||
-> value.hex: 78797A
|
||||
```
|
||||
|
||||
@@ -7,7 +7,7 @@ order: 4
|
||||
## XXX
|
||||
|
||||
This page is undergoing deprecation. All content is being moved to the new [home
|
||||
of the ABCI specification](../spec/abci/README.md).
|
||||
of the ABCI specification](https://github.com/tendermint/spec/tree/master/spec/abci).
|
||||
|
||||
## ABCI Design
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ type, only the key-value pairs defined in `EndBlock` are used.
|
||||
|
||||
Each event contains a type and a list of attributes, which are key-value pairs
|
||||
denoting something about what happened during the method's execution. For more
|
||||
details on `Events`, see the [ABCI](../spec/abci/abci.md) documentation.
|
||||
details on `Events`, see the [ABCI]https://github.com/tendermint/spec/blob/master/spec/abci/abci.md#events) documentation.
|
||||
|
||||
An Event has a composite key associated with it. A `compositeKey` is constructed by its type and key separated by a dot.
|
||||
For example:
|
||||
|
||||
@@ -36,7 +36,7 @@ transactions](./indexing-transactions.md) for details.
|
||||
When validator set changes, ValidatorSetUpdates event is published. The
|
||||
event carries a list of pubkey/power pairs. The list is the same
|
||||
Tendermint receives from ABCI application (see [EndBlock
|
||||
section](../spec/abci/abci.md#endblock) in
|
||||
section](https://github.com/tendermint/spec/blob/master/spec/abci/abci.md#endblock) in
|
||||
the ABCI spec).
|
||||
|
||||
Response:
|
||||
|
||||
@@ -37,7 +37,7 @@ Note the context/background should be written in the present tense.
|
||||
- [ADR-006-Trust-Metric](./adr-006-trust-metric.md)
|
||||
- [ADR-007-Trust-Metric-Usage](./adr-007-trust-metric-usage.md)
|
||||
- [ADR-008-Priv-Validator](./adr-008-priv-validator.md)
|
||||
- [ADR-009-ABCI-Design](./adr-009-abci-design.md)
|
||||
- [ADR-009-ABCI-Design](./adr-009-ABCI-design.md)
|
||||
- [ADR-010-Crypto-Changes](./adr-010-crypto-changes.md)
|
||||
- [ADR-011-Monitoring](./adr-011-monitoring.md)
|
||||
- [ADR-012-Peer-Transport](./adr-012-peer-transport.md)
|
||||
@@ -46,7 +46,7 @@ Note the context/background should be written in the present tense.
|
||||
- [ADR-015-Crypto-Encoding](./adr-015-crypto-encoding.md)
|
||||
- [ADR-016-Protocol-Versions](./adr-016-protocol-versions.md)
|
||||
- [ADR-017-Chain-Versions](./adr-017-chain-versions.md)
|
||||
- [ADR-018-ABCI-Validators](./adr-018-abci-validators.md)
|
||||
- [ADR-018-ABCI-Validators](./adr-018-ABCI-Validators.md)
|
||||
- [ADR-019-Multisigs](./adr-019-multisigs.md)
|
||||
- [ADR-020-Block-Size](./adr-020-block-size.md)
|
||||
- [ADR-021-ABCI-Events](./adr-021-abci-events.md)
|
||||
@@ -67,5 +67,8 @@ Note the context/background should be written in the present tense.
|
||||
- [ADR-044-Lite-Client-With-Weak-Subjectivity](./adr-044-lite-client-with-weak-subjectivity.md)
|
||||
- [ADR-045-ABCI-Evidence](./adr-045-abci-evidence.md)
|
||||
- [ADR-046-Light-Client-Implementation](./adr-046-light-client-implementation.md)
|
||||
- [ADR-047-Handling-Evidence-From-Light-Client](./adr-047-handling-evidence-from-light-client.md)
|
||||
- [ADR-051-Double-Signing-Risk-Reduction](./adr-051-double-signing-risk-reduction.md)
|
||||
- [ADR-052-Tendermint-Mode](./adr-052-tendermint-mode.md)
|
||||
- [ADR-053-State-Sync-Prototype](./adr-053-state-sync-prototype.md)
|
||||
- [ADR-054-crypto-encoding-2](./adr-054-crypto-encoding-2.md)
|
||||
|
||||
@@ -65,7 +65,6 @@ For historic queries we will need a indexing storage (Postgres, SQLite, ...).
|
||||
|
||||
### Issues
|
||||
|
||||
- https://github.com/tendermint/basecoin/issues/91
|
||||
- https://github.com/tendermint/tendermint/issues/376
|
||||
- https://github.com/tendermint/tendermint/issues/287
|
||||
- https://github.com/tendermint/tendermint/issues/525 (related)
|
||||
|
||||
@@ -19,7 +19,7 @@ A few solutions were considered:
|
||||
b) [go-kit metrics package](https://github.com/go-kit/kit/tree/master/metrics) as an interface plus Prometheus
|
||||
c) [telegraf](https://github.com/influxdata/telegraf)
|
||||
d) new service, which will listen to events emitted by pubsub and report metrics
|
||||
2. [OpenCensus](https://opencensus.io/go/index.html)
|
||||
2. [OpenCensus](https://opencensus.io/introduction/)
|
||||
|
||||
### 1. Prometheus
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ message ValidatorUpdate {
|
||||
}
|
||||
```
|
||||
|
||||
As noted in ADR-009[https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-009-ABCI-design.md],
|
||||
As noted in [ADR-009](adr-009-ABCI-design.md),
|
||||
the `Validator` does not contain a pubkey because quantum public keys are
|
||||
quite large and it would be wasteful to send them all over ABCI with every block.
|
||||
Thus, applications that want to take advantage of the information in BeginBlock
|
||||
|
||||
@@ -34,7 +34,7 @@ across different criteria:
|
||||
|
||||
### Implementation Question
|
||||
* What is the format of a snapshot
|
||||
* Complete snapshot
|
||||
* Complete snapshot
|
||||
* Ordered IAVL key ranges
|
||||
* Compressed individually chunks which can be validated
|
||||
* How is data validated
|
||||
@@ -58,7 +58,7 @@ request time. This solution would create an auxiliary data structure
|
||||
optimized for batch read/writes.
|
||||
|
||||
Additionally the propsosals tend to vary on how they provide safety
|
||||
properties.
|
||||
properties.
|
||||
|
||||
**LightClient** Where a client can aquire the merkle root from the block
|
||||
headers synchronized from a trusted validator set. Subsets of the application state,
|
||||
@@ -70,7 +70,7 @@ downloaded and compared against versions provided by a majority of
|
||||
peers.
|
||||
|
||||
#### Lazy StateSync
|
||||
An [initial specification](https://docs.google.com/document/d/15MFsQtNA0MGBv7F096FFWRDzQ1vR6_dics5Y49vF8JU/edit?ts=5a0f3629) was published by Alexis Sellier.
|
||||
An initial specification was published by Alexis Sellier.
|
||||
In this design, the state has a given `size` of primitive elements (like
|
||||
keys or nodes), each element is assigned a number from 0 to `size-1`,
|
||||
and chunks consists of a range of such elements. Ackratos raised
|
||||
@@ -104,7 +104,7 @@ chunks and snappy compressed. Hashes of snappy compressed chunks are stored in a
|
||||
manifest file which co-ordinates the state-sync. Obtaining a correct manifest
|
||||
file seems to require an honest majority of peers. This means you may not find
|
||||
out the state is incorrect until you download the whole thing and compare it
|
||||
with a verified block header.
|
||||
with a verified block header.
|
||||
|
||||
A similar solution was implemented by Binance in
|
||||
[#3594](https://github.com/tendermint/tendermint/pull/3594)
|
||||
@@ -229,7 +229,6 @@ Proposed
|
||||
|
||||
## References
|
||||
[sync: Sync current state without full replay for Applications](https://github.com/tendermint/tendermint/issues/828) - original issue
|
||||
[tendermint state sync proposal](https://docs.google.com/document/d/15MFsQtNA0MGBv7F096FFWRDzQ1vR6_dics5Y49vF8JU/edit?ts=5a0f3629) - Cloudhead proposal
|
||||
[tendermint state sync proposal 2](https://docs.google.com/document/d/1npGTAa1qxe8EQZ1wG0a0Sip9t5oX2vYZNUDwr_LVRR4/edit) - ackratos proposal
|
||||
[proposal 2 implementation](https://github.com/tendermint/tendermint/pull/3243) - ackratos implementation
|
||||
[WIP General/Lazy State-Sync pseudo-spec](https://github.com/tendermint/tendermint/issues/3639) - Jae Proposal
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
# ADR 043: Blockhchain Reactor Riri-Org
|
||||
|
||||
## Changelog
|
||||
* 18-06-2019: Initial draft
|
||||
* 08-07-2019: Reviewed
|
||||
* 29-11-2019: Implemented
|
||||
* 14-02-2020: Updated with the implementation details
|
||||
|
||||
- 18-06-2019: Initial draft
|
||||
- 08-07-2019: Reviewed
|
||||
- 29-11-2019: Implemented
|
||||
- 14-02-2020: Updated with the implementation details
|
||||
|
||||
## Context
|
||||
|
||||
The blockchain reactor is responsible for two high level processes:sending/receiving blocks from peers and FastSync-ing blocks to catch upnode who is far behind. The goal of [ADR-40](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-040-blockchain-reactor-refactor.md) was to refactor these two processes by separating business logic currently wrapped up in go-channels into pure `handle*` functions. While the ADR specified what the final form of the reactor might look like it lacked guidance on intermediary steps to get there.
|
||||
The following diagram illustrates the state of the [blockchain-reorg](https://github.com/tendermint/tendermint/pull/35610) reactor which will be referred to as `v1`.
|
||||
The blockchain reactor is responsible for two high level processes:sending/receiving blocks from peers and FastSync-ing blocks to catch upnode who is far behind. The goal of [ADR-40](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-040-blockchain-reactor-refactor.md) was to refactor these two processes by separating business logic currently wrapped up in go-channels into pure `handle*` functions. While the ADR specified what the final form of the reactor might look like it lacked guidance on intermediary steps to get there.
|
||||
The following diagram illustrates the state of the [blockchain-reorg](https://github.com/tendermint/tendermint/pull/3561) reactor which will be referred to as `v1`.
|
||||
|
||||

|
||||
|
||||
While `v1` of the blockchain reactor has shown significant improvements in terms of simplifying the concurrency model, the current PR has run into few roadblocks.
|
||||
|
||||
* The current PR large and difficult to review.
|
||||
* Block gossiping and fast sync processes are highly coupled to the shared `Pool` data structure.
|
||||
* Peer communication is spread over multiple components creating complex dependency graph which must be mocked out during testing.
|
||||
* Timeouts modeled as stateful tickers introduce non-determinism in tests
|
||||
- The current PR large and difficult to review.
|
||||
- Block gossiping and fast sync processes are highly coupled to the shared `Pool` data structure.
|
||||
- Peer communication is spread over multiple components creating complex dependency graph which must be mocked out during testing.
|
||||
- Timeouts modeled as stateful tickers introduce non-determinism in tests
|
||||
|
||||
This ADR is meant to specify the missing components and control necessary to achieve [ADR-40](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-040-blockchain-reactor-refactor.md).
|
||||
|
||||
@@ -32,7 +33,7 @@ Diagram](https://github.com/tendermint/tendermint/blob/584e67ac3fac220c5c3e0652e
|
||||
|
||||
### Fast Sync Related Communication Channels
|
||||
|
||||
The diagram below shows the fast sync routines and the types of channels and queues used to communicate with each other.
|
||||
The diagram below shows the fast sync routines and the types of channels and queues used to communicate with each other.
|
||||
In addition the per reactor channels used by the sendRoutine to send messages over the Peer MConnection are shown.
|
||||
|
||||
. The demuxRoutine acts as "pacemaker" setting the time in which events are expected to be handled.
|
||||
|
||||
|
||||
```go
|
||||
func demuxRoutine(msgs, scheduleMsgs, processorMsgs, ioMsgs) {
|
||||
timer := time.NewTicker(interval)
|
||||
@@ -143,6 +143,7 @@ func (r *BlockchainReactor) AddPeer(peer p2p.Peer) {
|
||||
```
|
||||
|
||||
## IO handling
|
||||
|
||||
An io handling routine within the reactor will isolate peer communication. Message going through the ioRoutine will usually be one way, using `p2p` APIs. In the case in which the `p2p` API such as `trySend` return errors, the ioRoutine can funnel those message back to the demuxRoutine for distribution to the other routines. For instance errors from the ioRoutine can be consumed by the scheduler to inform better peer selection implementations.
|
||||
|
||||
```go
|
||||
@@ -169,6 +170,7 @@ func (r *BlockchainReacor) ioRoutine(ioMesgs chan Message, outMsgs chan Message)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Processor Internals
|
||||
|
||||
The processor is responsible for ordering, verifying and executing blocks. The Processor will maintain an internal cursor `height` refering to the last processed block. As a set of blocks arrive unordered, the Processor will check if it has `height+1` necessary to process the next block. The processor also maintains the map `blockPeers` of peers to height, to keep track of which peer provided the block at `height`. `blockPeers` can be used in`handleRemovePeer(...)` to reschedule all unprocessed blocks provided by a peer who has errored.
|
||||
@@ -232,10 +234,10 @@ func handleTimeCheckEv(time) {
|
||||
|
||||
The Schedule maintains the internal state used for scheduling blockRequestMessages based on some scheduling algorithm. The schedule needs to maintain state on:
|
||||
|
||||
* The state `blockState` of every block seem up to height of maxHeight
|
||||
* The set of peers and their peer state `peerState`
|
||||
* which peers have which blocks
|
||||
* which blocks have been requested from which peers
|
||||
- The state `blockState` of every block seem up to height of maxHeight
|
||||
- The set of peers and their peer state `peerState`
|
||||
- which peers have which blocks
|
||||
- which blocks have been requested from which peers
|
||||
|
||||
```go
|
||||
type blockState int
|
||||
@@ -256,7 +258,7 @@ type schedule {
|
||||
|
||||
// a map of peerID to schedule specific peer struct `scPeer`
|
||||
peers map[p2p.ID]scPeer
|
||||
|
||||
|
||||
// a map of heights to the peer we are waiting for a response from
|
||||
pending map[height]scPeer
|
||||
|
||||
@@ -309,6 +311,7 @@ type scPeer struct {
|
||||
```
|
||||
|
||||
# Scheduler
|
||||
|
||||
The scheduler is configured to maintain a target `n` of in flight
|
||||
messages and will use feedback from `_blockResponseMessage`,
|
||||
`_statusResponseMessage` and `_peerError` produce an optimal assignment
|
||||
@@ -342,7 +345,7 @@ func handleTimeCheckEv(time) {
|
||||
|
||||
events = []
|
||||
for peerID := range schedule.peersNotTouchedSince(time) {
|
||||
pending = schedule.pendingFrom(peerID)
|
||||
pending = schedule.pendingFrom(peerID)
|
||||
schedule.setPeerState(peerID, timedout)
|
||||
schedule.resetBlocks(pending)
|
||||
events = append(events, peerTimeout{peerID})
|
||||
@@ -355,6 +358,7 @@ func handleTimeCheckEv(time) {
|
||||
```
|
||||
|
||||
## Peer
|
||||
|
||||
The Peer Stores per peer state based on messages received by the scheduler.
|
||||
|
||||
```go
|
||||
@@ -376,19 +380,19 @@ type Peer struct {
|
||||
This design is under active development. The Implementation has been
|
||||
staged in the following PRs:
|
||||
|
||||
* [Routine](https://github.com/tendermint/tendermint/pull/3878)
|
||||
* [Processor](https://github.com/tendermint/tendermint/pull/4012)
|
||||
* [Scheduler](https://github.com/tendermint/tendermint/pull/4043)
|
||||
* [Reactor](https://github.com/tendermint/tendermint/pull/4067)
|
||||
- [Routine](https://github.com/tendermint/tendermint/pull/3878)
|
||||
- [Processor](https://github.com/tendermint/tendermint/pull/4012)
|
||||
- [Scheduler](https://github.com/tendermint/tendermint/pull/4043)
|
||||
- [Reactor](https://github.com/tendermint/tendermint/pull/4067)
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
* Test become deterministic
|
||||
* Simulation becomes a-termporal: no need wait for a wall-time timeout
|
||||
* Peer Selection can be independently tested/simulated
|
||||
* Develop a general approach to refactoring reactors
|
||||
- Test become deterministic
|
||||
- Simulation becomes a-termporal: no need wait for a wall-time timeout
|
||||
- Peer Selection can be independently tested/simulated
|
||||
- Develop a general approach to refactoring reactors
|
||||
|
||||
### Negative
|
||||
|
||||
@@ -396,11 +400,11 @@ staged in the following PRs:
|
||||
|
||||
### Implementation Path
|
||||
|
||||
* Implement the scheduler, test the scheduler, review the rescheduler
|
||||
* Implement the processor, test the processor, review the processor
|
||||
* Implement the demuxer, write integration test, review integration tests
|
||||
- Implement the scheduler, test the scheduler, review the rescheduler
|
||||
- Implement the processor, test the processor, review the processor
|
||||
- Implement the demuxer, write integration test, review integration tests
|
||||
|
||||
## References
|
||||
|
||||
* [ADR-40](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-040-blockchain-reactor-refactor.md): The original blockchain reactor re-org proposal
|
||||
* [Blockchain re-org](https://github.com/tendermint/tendermint/pull/3561): The current blockchain reactor re-org implementation (v1)
|
||||
- [ADR-40](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-040-blockchain-reactor-refactor.md): The original blockchain reactor re-org proposal
|
||||
- [Blockchain re-org](https://github.com/tendermint/tendermint/pull/3561): The current blockchain reactor re-org implementation (v1)
|
||||
|
||||
@@ -4,28 +4,22 @@
|
||||
* 13-02-2020: Initial draft
|
||||
* 26-02-2020: Cross-checking the first header
|
||||
* 28-02-2020: Bisection algorithm details
|
||||
* 31-03-2020: Verify signature got changed
|
||||
|
||||
## Context
|
||||
|
||||
A `Client` struct represents a light client, connected to a single blockchain.
|
||||
As soon as it's started (via `Start`), it tries to update to the latest header
|
||||
(using bisection algorithm by default).
|
||||
|
||||
Cleaning routine is also started to remove headers outside of trusting period.
|
||||
NOTE: since it's periodic, we still need to check header is not expired in
|
||||
`TrustedHeader`, `TrustedValidatorSet` methods (and others which are using the
|
||||
latest trusted header).
|
||||
|
||||
The user has an option to manually verify headers using `VerifyHeader` and
|
||||
`VerifyHeaderAtHeight` methods. To avoid races, `UpdatePeriod(0)` needs to be
|
||||
passed when initializing the light client (it turns off the auto update).
|
||||
The user has an option to verify headers using `VerifyHeader` or
|
||||
`VerifyHeaderAtHeight` or `Update` methods. The latter method downloads the
|
||||
latest header from primary and compares it with the currently trusted one.
|
||||
|
||||
```go
|
||||
type Client interface {
|
||||
// start and stop updating & cleaning goroutines
|
||||
Start() error
|
||||
Stop()
|
||||
Cleanup() error
|
||||
// verify new headers
|
||||
VerifyHeaderAtHeight(height int64, now time.Time) (*types.SignedHeader, error)
|
||||
VerifyHeader(newHeader *types.SignedHeader, newVals *types.ValidatorSet, now time.Time) error
|
||||
Update(now time.Time) (*types.SignedHeader, error)
|
||||
|
||||
// get trusted headers & validators
|
||||
TrustedHeader(height int64) (*types.SignedHeader, error)
|
||||
@@ -38,9 +32,7 @@ type Client interface {
|
||||
Primary() provider.Provider
|
||||
Witnesses() []provider.Provider
|
||||
|
||||
// verify new headers
|
||||
VerifyHeaderAtHeight(height int64, now time.Time) (*types.SignedHeader, error)
|
||||
VerifyHeader(newHeader *types.SignedHeader, newVals *types.ValidatorSet, now time.Time) error
|
||||
Cleanup() error
|
||||
}
|
||||
```
|
||||
|
||||
@@ -69,8 +61,9 @@ also cross-checked with witnesses for additional security.
|
||||
|
||||
Due to bisection algorithm nature, some headers might be skipped. If the light
|
||||
client does not have a header for height `X` and `VerifyHeaderAtHeight(X)` or
|
||||
`VerifyHeader(H#X)` methods are called, it will perform a backwards
|
||||
verification from the latest header back to the header at height `X`.
|
||||
`VerifyHeader(H#X)` methods are called, these will perform either a) backwards
|
||||
verification from the latest header back to the header at height `X` or b)
|
||||
bisection verification from the first stored header to the header at height `X`.
|
||||
|
||||
`TrustedHeader`, `TrustedValidatorSet` only communicate with the trusted store.
|
||||
If some header is not there, an error will be returned indicating that
|
||||
@@ -108,6 +101,10 @@ type Store interface {
|
||||
FirstSignedHeaderHeight() (int64, error)
|
||||
|
||||
SignedHeaderAfter(height int64) (*types.SignedHeader, error)
|
||||
|
||||
Prune(size uint16) error
|
||||
|
||||
Size() uint16
|
||||
}
|
||||
```
|
||||
|
||||
@@ -118,12 +115,13 @@ database, used in Tendermint). In the future, remote adapters are possible
|
||||
```go
|
||||
func Verify(
|
||||
chainID string,
|
||||
h1 *types.SignedHeader,
|
||||
h1NextVals *types.ValidatorSet,
|
||||
h2 *types.SignedHeader,
|
||||
h2Vals *types.ValidatorSet,
|
||||
trustedHeader *types.SignedHeader, // height=X
|
||||
trustedVals *types.ValidatorSet, // height=X or height=X+1
|
||||
untrustedHeader *types.SignedHeader, // height=Y
|
||||
untrustedVals *types.ValidatorSet, // height=Y
|
||||
trustingPeriod time.Duration,
|
||||
now time.Time,
|
||||
maxClockDrift time.Duration,
|
||||
trustLevel tmmath.Fraction) error {
|
||||
```
|
||||
|
||||
@@ -132,6 +130,9 @@ cases of adjacent and non-adjacent headers. In the former case, it compares the
|
||||
hashes directly (2/3+ signed transition). Otherwise, it verifies 1/3+
|
||||
(`trustLevel`) of trusted validators are still present in new validators.
|
||||
|
||||
While `Verify` function is certainly handy, `VerifyAdjacent` and
|
||||
`VerifyNonAdjacent` should be used most often to avoid logic errors.
|
||||
|
||||
### Bisection algorithm details
|
||||
|
||||
Non-recursive bisection algorithm was implemented despite the spec containing
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
# ADR 047: Handling evidence from light client
|
||||
|
||||
## Changelog
|
||||
* 18-02-2020: Initial draft
|
||||
* 24-02-2020: Second version
|
||||
|
||||
## Context
|
||||
|
||||
If the light client is under attack, either directly -> lunatic/phantom
|
||||
validators (light fork) or indirectly -> full fork, it's supposed to halt and
|
||||
send evidence of misbehavior to a correct full node. Upon receiving an
|
||||
evidence, the full node should punish malicious validators (if possible).
|
||||
|
||||
## Decision
|
||||
|
||||
When a light client sees two conflicting headers (`H1.Hash() != H2.Hash()`,
|
||||
`H1.Height == H2.Height`), both having 1/3+ of the voting power of the
|
||||
currently trusted validator set, it will submit a `ConflictingHeadersEvidence`
|
||||
to all full nodes it's connected to. Evidence needs to be submitted to all full
|
||||
nodes since there's no way to determine which full node is correct (honest).
|
||||
|
||||
```go
|
||||
type ConflictingHeadersEvidence struct {
|
||||
H1 types.SignedHeader
|
||||
H2 types.SignedHeader
|
||||
}
|
||||
```
|
||||
|
||||
When a full node receives the `ConflictingHeadersEvidence` evidence, it should
|
||||
a) validate it b) figure out if malicious behaviour is obvious (immediately
|
||||
slashable) or the fork accountability protocol needs to be started.
|
||||
|
||||
### Validating headers
|
||||
|
||||
Check both headers are valid (`ValidateBasic`), have the same height, and
|
||||
signed by 1/3+ of the validator set that the full node had at height
|
||||
`H1.Height-1`.
|
||||
|
||||
- Q: What if light client validator set is not equal to full node's validator
|
||||
set (i.e. from full node's point of view both headers are not properly signed;
|
||||
this includes the case where none of the two headers were committed on the
|
||||
main chain)
|
||||
|
||||
Reject the evidence. It means light client is following a fork, but, hey, at
|
||||
least it will halt.
|
||||
|
||||
- Q: Don't we want to punish validators who signed something else even if they
|
||||
have less or equal than 1/3?
|
||||
|
||||
No consensus so far. Ethan said no, Zarko said yes.
|
||||
https://github.com/tendermint/spec/pull/71#discussion_r374210533
|
||||
|
||||
### Figuring out if malicious behaviour is immediately slashable
|
||||
|
||||
Let's say H1 was committed from this full node's perspective (see Appendix A).
|
||||
Intersect validator sets of H1 and H2.
|
||||
|
||||
* if there are signers(H2) that are not part of validators(H1), they misbehaved as
|
||||
they are signing protocol messages in heights they are not validators =>
|
||||
immediately slashable (#F4).
|
||||
|
||||
* if `H1.Round == H2.Round`, and some signers signed different precommit
|
||||
messages in both commits, then it is an equivocation misbehavior => immediately
|
||||
slashable (#F1).
|
||||
|
||||
* if `H1.Round != H2.Round` we need to run full detection procedure => not
|
||||
immediately slashable.
|
||||
|
||||
* if `ValidatorsHash`, `NextValidatorsHash`, `ConsensusHash`,
|
||||
`AppHash`, and `LastResultsHash` in H2 are different (incorrect application
|
||||
state transition), then it is a lunatic misbehavior => immediately slashable (#F5).
|
||||
|
||||
If evidence is not immediately slashable, fork accountability needs to invoked
|
||||
(ADR does not yet exist).
|
||||
|
||||
It's unclear if we should further break up `ConflictingHeadersEvidence` or
|
||||
gossip and commit it directly. See
|
||||
https://github.com/tendermint/tendermint/issues/4182#issuecomment-590339233
|
||||
|
||||
If we'd go without breaking evidence, all we'll need to do is to strip the
|
||||
committed header from `ConflictingHeadersEvidence` (H1) and leave only the
|
||||
uncommitted header (H2):
|
||||
|
||||
```go
|
||||
type ConflictingHeaderEvidence struct {
|
||||
H types.SignedHeader
|
||||
}
|
||||
```
|
||||
|
||||
If we'd go with breaking evidence, here are the types we'll need:
|
||||
|
||||
### F1. Equivocation
|
||||
|
||||
Existing `DuplicateVoteEvidence` needs to be created and gossiped.
|
||||
|
||||
### F4. Phantom validators
|
||||
|
||||
A new type of evidence needs to be created:
|
||||
|
||||
```go
|
||||
type PhantomValidatorEvidence struct {
|
||||
PubKey crypto.PubKey
|
||||
Vote types.Vote
|
||||
}
|
||||
```
|
||||
|
||||
It contains a validator's public key and a vote for a block, where this
|
||||
validator is not part of the validator set.
|
||||
|
||||
### F5. Lunatic validator
|
||||
|
||||
```go
|
||||
type LunaticValidatorEvidence struct {
|
||||
Header types.Header
|
||||
Vote types.Vote
|
||||
}
|
||||
```
|
||||
|
||||
To punish this attack, we need support for a new Evidence type -
|
||||
`LunaticValidatorEvidence`. This type includes a vote and a header. The header
|
||||
must contain fields that are invalid with respect to the previous block, and a
|
||||
vote for that header by a validator that was in a validator set within the
|
||||
unbonding period. While the attack is only possible if +1/3 of some validator
|
||||
set colludes, the evidence should be verifiable independently for each
|
||||
individual validator. This means the total evidence can be split into one piece
|
||||
of evidence per attacking validator and gossipped to nodes to be verified one
|
||||
piece at a time, reducing the DoS attack surface at the peer layer.
|
||||
|
||||
Note it is not sufficient to simply compare this header with that committed for
|
||||
the corresponding height, as an honest node may vote for a header that is not
|
||||
ultimately committed. Certain fields may also be variable, for instance the
|
||||
`LastCommitHash` and the `Time` may depend on which votes the proposer includes.
|
||||
Thus, the header must be explicitly checked for invalid data.
|
||||
|
||||
For the attack to succeed, VC must sign a header that changes the validator set
|
||||
to consist of something they control. Without doing this, they can not
|
||||
otherwise attack the light client, since the client verifies commits according
|
||||
to validator sets. Thus, it should be sufficient to check only that
|
||||
`ValidatorsHash` and `NextValidatorsHash` are correct with respect to the
|
||||
header that was committed at the corresponding height.
|
||||
|
||||
That said, if the attack is conducted by +2/3 of the validator set, they don't
|
||||
need to make an invalid change to the validator set, since they already control
|
||||
it. Instead they would make invalid changes to the `AppHash`, or possibly other
|
||||
fields. In order to punish them, then, we would have to check all header
|
||||
fields.
|
||||
|
||||
Note some header fields require the block itself to verify, which the light
|
||||
client, by definition, does not possess, so it may not be possible to check
|
||||
these fields. For now, then, `LunaticValidatorEvidence` must be checked against
|
||||
all header fields which are a function of the application at previous blocks.
|
||||
This includes `ValidatorsHash`, `NextValidatorsHash`, `ConsensusHash`,
|
||||
`AppHash`, and `LastResultsHash`. These should all match what's in the header
|
||||
for the block that was actually committed at the corresponding height, and
|
||||
should thus be easy to check.
|
||||
|
||||
## Status
|
||||
|
||||
Proposed.
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
* Tendermint will be able to detect & punish new types of misbehavior
|
||||
* light clients connected to multiple full nodes can help full nodes notice a
|
||||
fork faster
|
||||
|
||||
### Negative
|
||||
|
||||
* Accepting `ConflictingHeadersEvidence` from light clients opens up a DDOS
|
||||
attack vector (same is fair for any RPC endpoint open to public; remember that
|
||||
RPC is not open by default).
|
||||
|
||||
### Neutral
|
||||
|
||||
## References
|
||||
|
||||
* [Fork accountability spec](https://github.com/tendermint/spec/blob/master/spec/consensus/light-client/accountability.md)
|
||||
|
||||
## Appendix A
|
||||
|
||||
If there is an actual fork (full fork), a full node may follow either one or
|
||||
another branch. So both H1 or H2 can be considered committed depending on which
|
||||
branch the full node is following. It's supposed to halt if it notices an
|
||||
actual fork, but there's a small chance it doesn't.
|
||||
@@ -14,6 +14,14 @@ This ADR outlines the plan for an initial state sync prototype, and is subject t
|
||||
* Added experimental prototype info.
|
||||
* Added open questions and implementation plan.
|
||||
|
||||
* 2020-03-29: Strengthened and simplified ABCI interface (Erik Grinaker)
|
||||
* ABCI: replaced `chunks` with `chunk_hashes` in `Snapshot`.
|
||||
* ABCI: removed `SnapshotChunk` message.
|
||||
* ABCI: renamed `GetSnapshotChunk` to `LoadSnapshotChunk`.
|
||||
* ABCI: chunks are now exchanged simply as `bytes`.
|
||||
* ABCI: chunks are now 0-indexed, for parity with `chunk_hashes` array.
|
||||
* Reduced maximum chunk size to 16 MB, and increased snapshot message size to 4 MB.
|
||||
|
||||
## Context
|
||||
|
||||
State sync will allow a new node to receive a snapshot of the application state without downloading blocks or going through consensus. This bootstraps the node significantly faster than the current fast sync system, which replays all historical blocks.
|
||||
@@ -36,28 +44,18 @@ This describes the snapshot/restore process seen from Tendermint. The interface
|
||||
|
||||
### Snapshot Data Structure
|
||||
|
||||
A node can have multiple snapshots taken at various heights. Snapshots can be taken in different application-specified formats (e.g. MessagePack as format `1` and Protobuf as format `2`, or similarly for schema versioning). Each snapshot consists of multiple chunks containing the actual state data, allowing parallel downloads and reduced memory usage.
|
||||
A node can have multiple snapshots taken at various heights. Snapshots can be taken in different application-specified formats (e.g. MessagePack as format `1` and Protobuf as format `2`, or similarly for schema versioning). Each snapshot consists of multiple chunks containing the actual state data, for parallel downloads and reduced memory usage.
|
||||
|
||||
```proto
|
||||
message Snapshot {
|
||||
uint64 height = 1; // The height at which the snapshot was taken
|
||||
uint32 format = 2; // The application-specific snapshot format
|
||||
uint32 chunks = 3; // The number of chunks in the snapshot
|
||||
bytes metadata = 4; // Arbitrary application metadata
|
||||
}
|
||||
|
||||
message SnapshotChunk {
|
||||
uint64 height = 1; // The height of the corresponding snapshot
|
||||
uint32 format = 2; // The application-specific snapshot format
|
||||
uint32 chunk = 3; // The chunk index (one-based)
|
||||
bytes data = 4; // Serialized application state in an arbitrary format
|
||||
bytes checksum = 5; // SHA-1 checksum of data
|
||||
uint64 height = 1; // The height at which the snapshot was taken
|
||||
uint32 format = 2; // The application-specific snapshot format
|
||||
repeated bytes chunk_hashes = 3; // SHA-256 checksums of all chunks, in order
|
||||
bytes metadata = 4; // Arbitrary application metadata
|
||||
}
|
||||
```
|
||||
|
||||
Chunk verification data must be encoded along with the state data in the `data` field.
|
||||
|
||||
Chunk `data` cannot be larger than 64 MB, and snapshot `metadata` cannot be larger than 64 KB.
|
||||
Chunks are exchanged simply as `bytes`, and cannot be larger than 16 MB. `Snapshot` messages should be less than 4 MB.
|
||||
|
||||
### ABCI Interface
|
||||
|
||||
@@ -72,41 +70,43 @@ message ResponseListSnapshots {
|
||||
// Offers a snapshot to the application
|
||||
message RequestOfferSnapshot {
|
||||
Snapshot snapshot = 1;
|
||||
bytes app_hash = 2;
|
||||
bytes app_hash = 2;
|
||||
}
|
||||
|
||||
message ResponseOfferSnapshot {
|
||||
bool accepted = 1;
|
||||
Reason reason = 2; // Reason why snapshot was rejected
|
||||
enum Reason {
|
||||
unknown = 0; // Unknown or generic reason
|
||||
invalid_height = 1; // Height is rejected: avoid this height
|
||||
invalid_format = 2; // Format is rejected: avoid this format
|
||||
bool accepted = 1;
|
||||
Reason reason = 2;
|
||||
|
||||
enum Reason { // Reason why snapshot was rejected
|
||||
unknown = 0; // Unknown or generic reason
|
||||
invalid_height = 1; // Height is rejected: avoid this height
|
||||
invalid_format = 2; // Format is rejected: avoid this format
|
||||
}
|
||||
}
|
||||
|
||||
// Fetches a snapshot chunk
|
||||
message RequestGetSnapshotChunk {
|
||||
// Loads a snapshot chunk
|
||||
message RequestLoadSnapshotChunk {
|
||||
uint64 height = 1;
|
||||
uint32 format = 2;
|
||||
uint32 chunk = 3;
|
||||
uint32 chunk = 3; // Zero-indexed
|
||||
}
|
||||
|
||||
message ResponseGetSnapshotChunk {
|
||||
SnapshotChunk chunk = 1;
|
||||
message ResponseLoadSnapshotChunk {
|
||||
bytes chunk = 1;
|
||||
}
|
||||
|
||||
// Applies a snapshot chunk
|
||||
message RequestApplySnapshotChunk {
|
||||
SnapshotChunk chunk = 1;
|
||||
bytes chunk = 1;
|
||||
}
|
||||
|
||||
message ResponseApplySnapshotChunk {
|
||||
bool applied = 1;
|
||||
Reason reason = 2; // Reason why chunk failed
|
||||
enum Reason {
|
||||
unknown = 0; // Unknown or generic reason
|
||||
verify_failed = 1; // Chunk verification failed
|
||||
bool applied = 1;
|
||||
Reason reason = 2; // Reason why chunk failed
|
||||
|
||||
enum Reason { // Reason why chunk failed
|
||||
unknown = 0; // Unknown or generic reason
|
||||
verify_failed = 1; // Snapshot verification failed
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -139,19 +139,19 @@ When starting an empty node with state sync and fast sync enabled, snapshots are
|
||||
|
||||
3. The node contacts a set of full nodes, and verifies the trusted block header using the given hash via the light client.
|
||||
|
||||
4. The node requests available snapshots via `RequestListSnapshots`. Snapshots with `metadata` greater than 64 KB are rejected.
|
||||
4. The node requests available snapshots via P2P from peers, via `RequestListSnapshots`. Peers will return the 10 most recent snapshots, one message per snapshot.
|
||||
|
||||
5. The node iterates over all snapshots in reverse order by height and format until it finds one that satisfies all of the following conditions:
|
||||
5. The node aggregates snapshots from multiple peers, ordered by height and format (in reverse). If there are `chunk_hashes` mismatches between different snapshots, the one hosted by the largest amount of peers is chosen. The node iterates over all snapshots in reverse order by height and format until it finds one that satisfies all of the following conditions:
|
||||
|
||||
* The snapshot height's block is considered trustworthy by the light client (i.e. snapshot height is greater than trusted header and within unbonding period of the latest trustworthy block).
|
||||
|
||||
* The snapshot's height or format hasn't been explicitly rejected by an earlier `RequestOffsetSnapshot` call (via `invalid_height` or `invalid_format`).
|
||||
* The snapshot's height or format hasn't been explicitly rejected by an earlier `RequestOfferSnapshot` call (via `invalid_height` or `invalid_format`).
|
||||
|
||||
* The application accepts the `RequestOfferSnapshot` call.
|
||||
|
||||
6. The node downloads chunks in parallel from multiple peers via `RequestGetSnapshotChunk`, and both the sender and receiver verifies their checksums. Chunks with `data` greater than 64 MB are rejected.
|
||||
6. The node downloads chunks in parallel from multiple peers, via `RequestLoadSnapshotChunk`, and both the sender and receiver verifies their checksums. Chunk messages cannot exceed 16 MB.
|
||||
|
||||
7. The node passes chunks sequentially to the app via `RequestApplySnapshotChunk`, along with the chain's app hash at the snapshot height for verification. If the chunk is rejected the node should retry it. If it was rejected with `verify_failed`, it should be refetched from a different source. If an internal error occurred, `ResponseException` should be returned and state sync should be aborted.
|
||||
7. The node passes chunks sequentially to the app via `RequestApplySnapshotChunk`.
|
||||
|
||||
8. Once all chunks have been applied, the node compares the app hash to the chain app hash, and if they do not match it either errors or discards the state and starts over.
|
||||
|
||||
@@ -167,7 +167,7 @@ This describes the snapshot process seen from Gaia, using format version `1`. Th
|
||||
|
||||
In the initial version there is no snapshot metadata, so it is set to an empty byte buffer.
|
||||
|
||||
Once all chunks have been successfully built, snapshot metadata should be serialized and stored in the file system as e.g. `snapshots/<height>/<format>/metadata`, and served via `RequestListSnapshots`.
|
||||
Once all chunks have been successfully built, snapshot metadata should be stored in a database and served via `RequestListSnapshots`.
|
||||
|
||||
### Snapshot Chunk Format
|
||||
|
||||
@@ -181,7 +181,7 @@ For the initial prototype, each chunk consists of a complete dump of all node da
|
||||
|
||||
For a production version, it should be sufficient to store key/value/version for all nodes (leaf and inner) in insertion order, chunked in some appropriate way. If per-chunk verification is required, the chunk must also contain enough information to reconstruct the Merkle proofs all the way up to the root of the multistore, e.g. by storing a complete subtree's key/value/version data plus Merkle hashes of all other branches up to the multistore root. The exact approach will depend on tradeoffs between size, time, and verification. IAVL RangeProofs are not recommended, since these include redundant data such as proofs for intermediate and leaf nodes that can be derived from the above data.
|
||||
|
||||
Chunks should be built greedily by collecting node data up to some size limit (e.g. 32 MB) and serializing it. Chunk data is stored in the file system as `snapshots/<height>/<format>/<chunk>/data`, along with a SHA-1 checksum in `snapshots/<height>/<format>/<chunk>/checksum`, and served via `RequestGetSnapshotChunk`.
|
||||
Chunks should be built greedily by collecting node data up to some size limit (e.g. 10 MB) and serializing it. Chunk data is stored in the file system as `snapshots/<height>/<format>/<chunk>`, and a SHA-256 checksum is stored along with the snapshot metadata.
|
||||
|
||||
### Snapshot Scheduling
|
||||
|
||||
@@ -223,12 +223,6 @@ To stop the testnet, run:
|
||||
$ ./tools/stop.sh
|
||||
```
|
||||
|
||||
## Open Questions
|
||||
|
||||
* Should we have a simpler scheme for discovering snapshots? E.g. announce supported formats, and have peer supply latest available snapshot.
|
||||
|
||||
Downsides: app has to announce supported formats, having a single snapshot per peer may make fewer peers available for chosen snapshot.
|
||||
|
||||
## Resolved Questions
|
||||
|
||||
* Is it OK for state-synced nodes to not have historical blocks nor historical IAVL versions?
|
||||
@@ -309,6 +303,8 @@ $ ./tools/stop.sh
|
||||
|
||||
* **Tendermint:** node should go back to fast-syncing when lagging significantly [#129](https://github.com/tendermint/tendermint/issues/129)
|
||||
|
||||
* **Tendermint:** backfill historical blocks [#4629](https://github.com/tendermint/tendermint/issues/4629)
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# ADR 054: Crypto encoding (part 2)
|
||||
|
||||
## Changelog
|
||||
|
||||
\*2020-2-27: Created
|
||||
|
||||
## Context
|
||||
|
||||
Amino has been a pain point of many users in the ecosystem. While Tendermint does not suffer greatly from the performance degradation introduced by amino, we are making an effort in moving the encoding format to a widely adopted format, [Protocol Buffers](https://developers.google.com/protocol-buffers). With this migration a new standard is needed for the encoding of keys. This will cause ecosystem wide breaking changes.
|
||||
|
||||
Currently amino encodes keys as `<PrefixBytes> <Length> <ByteArray>`.
|
||||
|
||||
## Decision
|
||||
|
||||
When using the `oneof` protobuf type there are many times where one will have to manually switch over the possible messages and then pass them to the interface which is needed. By transitioning from a fixed size byte array (`[size]byte`) to byte slice's (`[]byte`) then this would enable the usage of the [cosmos-proto's](hhttps://github.com/regen-network/cosmos-proto#interface_type) interface type, which will generate these switch statements.
|
||||
|
||||
The approach that will be taken to minimize headaches for users is one where all encoding of keys will shift to protobuf and where amino encoding is relied on, there will be custom marshal and unmarshal functions.
|
||||
|
||||
Protobuf messages:
|
||||
|
||||
```proto
|
||||
message PubKey {
|
||||
option (cosmos_proto.interface_type) = "*github.com/tendermint/tendermint/crypto.PubKey";
|
||||
oneof key {
|
||||
bytes ed25519 = 1
|
||||
[(gogoproto.casttype) = "github.com/tendermint/tendermint/crypto/ed25519.PubKey"];
|
||||
bytes secp256k1 = 2
|
||||
[(gogoproto.casttype) = "github.com/tendermint/tendermint/crypto/secp256k1.PubKey"];
|
||||
bytes sr25519 = 3
|
||||
[(gogoproto.casttype) = "github.com/tendermint/tendermint/crypto/sr25519.PubKey"];
|
||||
PubKeyMultiSigThreshold multisig = 4
|
||||
[(gogoproto.casttype) = "github.com/tendermint/tendermint/crypto/multisig.PubKeyMultisigThreshold"];;
|
||||
}
|
||||
|
||||
message PrivKey {
|
||||
option (cosmos_proto.interface_type) = "github.com/tendermint/tendermint/crypto.PrivKey";
|
||||
oneof sum {
|
||||
bytes ed25519 = 1
|
||||
[(gogoproto.casttype) = "github.com/tendermint/tendermint/crypto/ed25519.PrivKey"];
|
||||
bytes secp256k1 = 2
|
||||
[(gogoproto.casttype) = "github.com/tendermint/tendermint/crypto/secp256k1.PrivKey"];
|
||||
bytes sr25519 = 3
|
||||
[(gogoproto.casttype) = "github.com/tendermint/tendermint/crypto/sr25519.PrivKey"];;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Note: The places where backwards compatibility is needed is still unclear.
|
||||
|
||||
All modules currently do not rely on amino encoded bytes and keys are not amino encoded for genesis, therefore a hardfork upgrade is what will be needed to adopt these changes.
|
||||
|
||||
This work will be broken out into a few PRs, this work will be merged into a proto-breakage branch, all PRs will be reviewed prior to being merged:
|
||||
|
||||
1. Encoding of keys to protobuf and protobuf messages
|
||||
2. Move Tendermint types to protobuf, mainly the ones that are being encoded.
|
||||
3. Go one by one through the reactors and transition amino encoded messages to protobuf.
|
||||
4. Test with cosmos-sdk and/or testnets repo.
|
||||
|
||||
## Status
|
||||
|
||||
Proposed
|
||||
|
||||
## Consequences
|
||||
|
||||
- Move keys to protobuf encoding, where backwards compatibility is needed, amino marshal and unmarshal functions will be used.
|
||||
|
||||
### Positive
|
||||
|
||||
- Protocol Buffer encoding will not change going forward.
|
||||
- Removing amino overhead from keys will help with the KSM.
|
||||
- Have a large ecosystem of supported languages.
|
||||
|
||||
### Negative
|
||||
|
||||
- Hardfork is required to integrate this into running chains.
|
||||
|
||||
### 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}
|
||||
@@ -1,17 +1,19 @@
|
||||
# ADR {ADR-NUMBER}: {TITLE}
|
||||
|
||||
## Changelog
|
||||
* {date}: {changelog}
|
||||
|
||||
- {date}: {changelog}
|
||||
|
||||
## 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.
|
||||
> 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.
|
||||
|
||||
## Decision
|
||||
|
||||
> This section explains all of the details of the proposed solution, including implementation details.
|
||||
It should also describe affects / corollary items that may need to be changed as a part of this.
|
||||
If the proposed change will be large, please also indicate a way to do the change to maximize ease of review.
|
||||
(e.g. the optimal split of things to do between separate PR's)
|
||||
> It should also describe affects / corollary items that may need to be changed as a part of this.
|
||||
> If the proposed change will be large, please also indicate a way to do the change to maximize ease of review.
|
||||
> (e.g. the optimal split of things to do between separate PR's)
|
||||
|
||||
## Status
|
||||
|
||||
@@ -31,6 +33,6 @@ If the proposed change will be large, please also indicate a way to do the chang
|
||||
|
||||
## References
|
||||
|
||||
> Are there any relevant PR comments, issues that led up to this, or articles referrenced for why we made the given design choice? If so link them here!
|
||||
> 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}
|
||||
- {reference link}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
Generated
+582
-573
File diff suppressed because it is too large
Load Diff
+3
-2
@@ -4,7 +4,8 @@
|
||||
"description": "Welcome to the Tendermint Core documentation!",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"vuepress-theme-cosmos": "^1.0.150"
|
||||
"@vuepress/plugin-google-analytics": "^1.3.1",
|
||||
"vuepress-theme-cosmos": "^1.0.156"
|
||||
},
|
||||
"scripts": {
|
||||
"preserve": "./pre.sh",
|
||||
@@ -16,4 +17,4 @@
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,9 +66,8 @@ I[10-04|13:54:30.392] Started node module=main n
|
||||
Next follows a standard block creation cycle, where we enter a new
|
||||
round, propose a block, receive more than 2/3 of prevotes, then
|
||||
precommits and finally have a chance to commit a block. For details,
|
||||
please refer to [Consensus
|
||||
Overview](../introduction/introduction.md#consensus-overview) or [Byzantine Consensus
|
||||
Algorithm](../spec/consensus/consensus.md).
|
||||
please refer to [Byzantine Consensus
|
||||
Algorithm](https://github.com/tendermint/spec/blob/master/spec/consensus/consensus.md).
|
||||
|
||||
```
|
||||
I[10-04|13:54:30.393] enterNewRound(91/0). Current: 91/0/RoundStepNewHeight module=consensus
|
||||
|
||||
@@ -55,9 +55,9 @@ passing them back to the caller. Other than that, it will present the same
|
||||
interface as a full Tendermint node.
|
||||
|
||||
```sh
|
||||
$ tendermint lite --chain-id=supernova --primary=tcp://233.123.0.140:26657 \
|
||||
--witnesses=tcp://179.63.29.15:26657,tcp://144.165.223.135:26657 \
|
||||
--trusted-height=10 --trusted-hash=37E9A6DD3FA25E83B22C18835401E8E56088D0D7ABC6FD99FCDC920DD76C1C57
|
||||
$ tendermint lite supernova -p tcp://233.123.0.140:26657 \
|
||||
-w tcp://179.63.29.15:26657,tcp://144.165.223.135:26657 \
|
||||
--height=10 --hash=37E9A6DD3FA25E83B22C18835401E8E56088D0D7ABC6FD99FCDC920DD76C1C57
|
||||
```
|
||||
|
||||
For additional options, run `tendermint lite --help`.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user