mirror of
https://github.com/tendermint/tendermint.git
synced 2026-04-22 00:30:31 +00:00
222
.circleci/config.yml
Normal file
222
.circleci/config.yml
Normal file
@@ -0,0 +1,222 @@
|
||||
version: 2
|
||||
|
||||
defaults: &defaults
|
||||
working_directory: /go/src/github.com/tendermint/tendermint
|
||||
docker:
|
||||
- image: circleci/golang:1.10.0
|
||||
environment:
|
||||
GOBIN: /tmp/workspace/bin
|
||||
|
||||
jobs:
|
||||
setup_dependencies:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- run: mkdir -p /tmp/workspace/bin
|
||||
- run: mkdir -p /tmp/workspace/profiles
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-pkg-cache
|
||||
- run:
|
||||
name: tools
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make get_tools
|
||||
- run:
|
||||
name: dependencies
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make get_vendor_deps
|
||||
- run:
|
||||
name: binaries
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make install
|
||||
- persist_to_workspace:
|
||||
root: /tmp/workspace
|
||||
paths:
|
||||
- bin
|
||||
- profiles
|
||||
- save_cache:
|
||||
key: v1-pkg-cache
|
||||
paths:
|
||||
- /go/pkg
|
||||
- save_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
paths:
|
||||
- /go/src/github.com/tendermint/tendermint
|
||||
|
||||
setup_abci:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- restore_cache:
|
||||
key: v1-pkg-cache
|
||||
- restore_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run:
|
||||
name: Checkout abci
|
||||
command: |
|
||||
commit=$(bash scripts/dep_utils/parse.sh abci)
|
||||
go get -v -u -d github.com/tendermint/abci/...
|
||||
cd /go/src/github.com/tendermint/abci
|
||||
git checkout "$commit"
|
||||
- run:
|
||||
working_directory: /go/src/github.com/tendermint/abci
|
||||
name: Install abci
|
||||
command: |
|
||||
set -ex
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make get_tools
|
||||
make get_vendor_deps
|
||||
make install
|
||||
- run: ls -lah /tmp/workspace/bin
|
||||
- persist_to_workspace:
|
||||
root: /tmp/workspace
|
||||
paths:
|
||||
- "bin/abci*"
|
||||
|
||||
lint:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- restore_cache:
|
||||
key: v1-pkg-cache
|
||||
- restore_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run:
|
||||
name: metalinter
|
||||
command: |
|
||||
set -ex
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make metalinter
|
||||
|
||||
test_apps:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- restore_cache:
|
||||
key: v1-pkg-cache
|
||||
- restore_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run: sudo apt-get update && sudo apt-get install -y --no-install-recommends bsdmainutils
|
||||
- run:
|
||||
name: Run tests
|
||||
command: bash test/app/test.sh
|
||||
|
||||
test_cover:
|
||||
<<: *defaults
|
||||
parallelism: 4
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- restore_cache:
|
||||
key: v1-pkg-cache
|
||||
- restore_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run:
|
||||
name: Run tests
|
||||
command: |
|
||||
for pkg in $(go list github.com/tendermint/tendermint/... | grep -v /vendor/ | circleci tests split --split-by=timings); do
|
||||
id=$(basename "$pkg")
|
||||
|
||||
go test -timeout 5m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg"
|
||||
done
|
||||
- persist_to_workspace:
|
||||
root: /tmp/workspace
|
||||
paths:
|
||||
- "profiles/*"
|
||||
|
||||
test_libs:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- restore_cache:
|
||||
key: v1-pkg-cache
|
||||
- restore_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run:
|
||||
name: Run tests
|
||||
command: bash test/test_libs.sh
|
||||
|
||||
test_persistence:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- restore_cache:
|
||||
key: v1-pkg-cache
|
||||
- restore_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run:
|
||||
name: Run tests
|
||||
command: bash test/persist/test_failure_indices.sh
|
||||
|
||||
test_p2p:
|
||||
environment:
|
||||
GOBIN: /home/circleci/.go_workspace/bin
|
||||
GOPATH: /home/circleci/.go_workspace
|
||||
machine:
|
||||
image: circleci/classic:latest
|
||||
steps:
|
||||
- checkout
|
||||
- run: mkdir -p $GOPATH/src/github.com/tendermint
|
||||
- run: ln -sf /home/circleci/project $GOPATH/src/github.com/tendermint/tendermint
|
||||
- run: bash test/circleci/p2p.sh
|
||||
|
||||
upload_coverage:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- restore_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run:
|
||||
name: gather
|
||||
command: |
|
||||
set -ex
|
||||
|
||||
echo "mode: atomic" > coverage.txt
|
||||
for prof in $(ls /tmp/workspace/profiles/); do
|
||||
tail -n +2 /tmp/workspace/profiles/"$prof" >> coverage.txt
|
||||
done
|
||||
- run:
|
||||
name: upload
|
||||
command: bash <(curl -s https://codecov.io/bash) -f coverage.txt
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
test-suite:
|
||||
jobs:
|
||||
- setup_dependencies
|
||||
- setup_abci:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
- lint:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
- test_apps:
|
||||
requires:
|
||||
- setup_abci
|
||||
- test_cover:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
- test_libs:
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
- master
|
||||
requires:
|
||||
- setup_dependencies
|
||||
- test_persistence:
|
||||
requires:
|
||||
- setup_abci
|
||||
- test_p2p
|
||||
- upload_coverage:
|
||||
requires:
|
||||
- test_cover
|
||||
26
.codecov.yml
26
.codecov.yml
@@ -1,26 +0,0 @@
|
||||
|
||||
#
|
||||
# This codecov.yml is the default configuration for
|
||||
# all repositories on Codecov. You may adjust the settings
|
||||
# below in your own codecov.yml in your repository.
|
||||
#
|
||||
coverage:
|
||||
precision: 2
|
||||
round: down
|
||||
range: 70...100
|
||||
|
||||
status:
|
||||
# Learn more at https://codecov.io/docs#yaml_default_commit_status
|
||||
project:
|
||||
default:
|
||||
threshold: 1% # allow this much decrease on project
|
||||
changes: false
|
||||
|
||||
comment:
|
||||
layout: "header, diff"
|
||||
behavior: default # update if exists else create new
|
||||
|
||||
ignore:
|
||||
- "docs"
|
||||
- "*.md"
|
||||
- "*.rst"
|
||||
3
.github/ISSUE_TEMPLATE
vendored
3
.github/ISSUE_TEMPLATE
vendored
@@ -37,5 +37,8 @@ in a case of bug.
|
||||
|
||||
**How to reproduce it** (as minimally and precisely as possible):
|
||||
|
||||
**Logs (you can paste a part showing an error or attach the whole file)**:
|
||||
|
||||
**`/dump_consensus_state` output for consensus bugs**
|
||||
|
||||
**Anything else do we need to know**:
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -21,3 +21,6 @@ docs/tools
|
||||
|
||||
scripts/wal2json/wal2json
|
||||
scripts/cutWALUntil/cutWALUntil
|
||||
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -25,7 +25,31 @@ BUG FIXES:
|
||||
- Graceful handling/recovery for apps that have non-determinism or fail to halt
|
||||
- Graceful handling/recovery for violations of safety, or liveness
|
||||
|
||||
## 0.16.0 (February 20th, 2017)
|
||||
## 0.17.0 (March 27th, 2018)
|
||||
|
||||
BREAKING:
|
||||
- [types] WriteSignBytes -> SignBytes
|
||||
|
||||
IMPROVEMENTS:
|
||||
- [all] renamed `dummy` (`persistent_dummy`) to `kvstore` (`persistent_kvstore`) (name "dummy" is deprecated and will not work in the next breaking release)
|
||||
- [config] exposed `auth_enc` flag to enable/disable encryption
|
||||
- [docs] note on determinism (docs/determinism.rst)
|
||||
- [genesis] `app_options` field is deprecated. please rename it to `app_state` in your genesis file(s). `app_options` will not work in the next breaking release
|
||||
- [p2p] dial seeds directly without potential peers
|
||||
- [p2p] exponential backoff for addrs in the address book
|
||||
- [p2p] mark peer as good if it contributed enough votes or block parts
|
||||
- [p2p] stop peer if it sends incorrect data, msg to unknown channel, msg we did not expect
|
||||
- [p2p] when `auth_enc` is true, all dialed peers must have a node ID in their address
|
||||
- [spec] various improvements
|
||||
- switched from glide to dep internally for package management
|
||||
- [wire] prep work for upgrading to new go-wire (which is now called go-amino)
|
||||
- [types/priv_validator] new format and socket client, allowing for remote signing
|
||||
|
||||
FEATURES:
|
||||
- [config] added the `--p2p.private_peer_ids` flag and `PrivatePeerIDs` config variable (see config for description)
|
||||
- [rpc] added `/health` endpoint, which returns empty result for now
|
||||
|
||||
## 0.16.0 (February 20th, 2018)
|
||||
|
||||
BREAKING CHANGES:
|
||||
- [config] use $TMHOME/config for all config and json files
|
||||
|
||||
@@ -34,15 +34,26 @@ Please don't make Pull Requests to `master`.
|
||||
|
||||
## Dependencies
|
||||
|
||||
We use [glide](https://github.com/masterminds/glide) to manage dependencies.
|
||||
That said, the master branch of every Tendermint repository should just build with `go get`, which means they should be kept up-to-date with their dependencies so we can get away with telling people they can just `go get` our software.
|
||||
Since some dependencies are not under our control, a third party may break our build, in which case we can fall back on `glide install`. Even for dependencies under our control, glide helps us keeps multiple repos in sync as they evolve. Anything with an executable, such as apps, tools, and the core, should use glide.
|
||||
We use [dep](https://github.com/golang/dep) to manage dependencies.
|
||||
|
||||
Run `bash scripts/glide/status.sh` to get a list of vendored dependencies that may not be up-to-date.
|
||||
That said, the master branch of every Tendermint repository should just build
|
||||
with `go get`, which means they should be kept up-to-date with their
|
||||
dependencies so we can get away with telling people they can just `go get` our
|
||||
software.
|
||||
|
||||
Since some dependencies are not under our control, a third party may break our
|
||||
build, in which case we can fall back on `dep ensure` (or `make
|
||||
get_vendor_deps`). Even for dependencies under our control, dep helps us to
|
||||
keep multiple repos in sync as they evolve. Anything with an executable, such
|
||||
as apps, tools, and the core, should use dep.
|
||||
|
||||
Run `dep status` to get a list of vendored dependencies that may not be
|
||||
up-to-date.
|
||||
|
||||
## Vagrant
|
||||
|
||||
If you are a [Vagrant](https://www.vagrantup.com/) user, you can get started hacking Tendermint with the commands below.
|
||||
If you are a [Vagrant](https://www.vagrantup.com/) user, you can get started
|
||||
hacking Tendermint with the commands below.
|
||||
|
||||
NOTE: In case you installed Vagrant in 2017, you might need to run
|
||||
`vagrant box update` to upgrade to the latest `ubuntu/xenial64`.
|
||||
@@ -53,11 +64,14 @@ vagrant ssh
|
||||
make test
|
||||
```
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
All repos should be hooked up to circle.
|
||||
If they have `.go` files in the root directory, they will be automatically tested by circle using `go test -v -race ./...`. If not, they will need a `circle.yml`. Ideally, every repo has a `Makefile` that defines `make test` and includes its continuous integration status using a badge in the `README.md`.
|
||||
All repos should be hooked up to [CircleCI](https://circleci.com/).
|
||||
|
||||
If they have `.go` files in the root directory, they will be automatically
|
||||
tested by circle using `go test -v -race ./...`. If not, they will need a
|
||||
`circle.yml`. Ideally, every repo has a `Makefile` that defines `make test` and
|
||||
includes its continuous integration status using a badge in the `README.md`.
|
||||
|
||||
## Branching Model and Release
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@ RUN mkdir -p /go/src/github.com/tendermint/tendermint && \
|
||||
cd /go/src/github.com/tendermint/tendermint && \
|
||||
git clone https://github.com/tendermint/tendermint . && \
|
||||
git checkout develop && \
|
||||
make get_tools && \
|
||||
make get_vendor_deps && \
|
||||
make install && \
|
||||
glide cc && \
|
||||
cd - && \
|
||||
rm -rf /go/src/github.com/tendermint/tendermint && \
|
||||
apk del go build-base git
|
||||
@@ -32,4 +32,4 @@ EXPOSE 46657
|
||||
|
||||
ENTRYPOINT ["tendermint"]
|
||||
|
||||
CMD ["node", "--moniker=`hostname`", "--proxy_app=dummy"]
|
||||
CMD ["node", "--moniker=`hostname`", "--proxy_app=kvstore"]
|
||||
|
||||
@@ -34,13 +34,13 @@ To get started developing applications, see the [application developers guide](h
|
||||
|
||||
# How to use this image
|
||||
|
||||
## Start one instance of the Tendermint core with the `dummy` app
|
||||
## Start one instance of the Tendermint core with the `kvstore` app
|
||||
|
||||
A very simple example of a built-in app and Tendermint core in one container.
|
||||
|
||||
```
|
||||
docker run -it --rm -v "/tmp:/tendermint" tendermint/tendermint init
|
||||
docker run -it --rm -v "/tmp:/tendermint" tendermint/tendermint node --proxy_app=dummy
|
||||
docker run -it --rm -v "/tmp:/tendermint" tendermint/tendermint node --proxy_app=kvstore
|
||||
```
|
||||
|
||||
## mintnet-kubernetes
|
||||
|
||||
386
Gopkg.lock
generated
Normal file
386
Gopkg.lock
generated
Normal file
@@ -0,0 +1,386 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/btcsuite/btcd"
|
||||
packages = ["btcec"]
|
||||
revision = "2be2f12b358dc57d70b8f501b00be450192efbc3"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/ebuchman/fail-test"
|
||||
packages = ["."]
|
||||
revision = "95f809107225be108efcf10a3509e4ea6ceef3c4"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/fortytw2/leaktest"
|
||||
packages = ["."]
|
||||
revision = "a5ef70473c97b71626b9abeda80ee92ba2a7de9e"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/fsnotify/fsnotify"
|
||||
packages = ["."]
|
||||
revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
|
||||
version = "v1.4.7"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-kit/kit"
|
||||
packages = [
|
||||
"log",
|
||||
"log/level",
|
||||
"log/term"
|
||||
]
|
||||
revision = "4dc7be5d2d12881735283bcab7352178e190fc71"
|
||||
version = "v0.6.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-logfmt/logfmt"
|
||||
packages = ["."]
|
||||
revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-stack/stack"
|
||||
packages = ["."]
|
||||
revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc"
|
||||
version = "v1.7.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gogo/protobuf"
|
||||
packages = [
|
||||
"gogoproto",
|
||||
"jsonpb",
|
||||
"proto",
|
||||
"protoc-gen-gogo/descriptor",
|
||||
"sortkeys",
|
||||
"types"
|
||||
]
|
||||
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = [
|
||||
"proto",
|
||||
"ptypes",
|
||||
"ptypes/any",
|
||||
"ptypes/duration",
|
||||
"ptypes/timestamp"
|
||||
]
|
||||
revision = "925541529c1fa6821df4e44ce2723319eb2be768"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/golang/snappy"
|
||||
packages = ["."]
|
||||
revision = "553a641470496b2327abcac10b36396bd98e45c9"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gorilla/websocket"
|
||||
packages = ["."]
|
||||
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/hashicorp/hcl"
|
||||
packages = [
|
||||
".",
|
||||
"hcl/ast",
|
||||
"hcl/parser",
|
||||
"hcl/printer",
|
||||
"hcl/scanner",
|
||||
"hcl/strconv",
|
||||
"hcl/token",
|
||||
"json/parser",
|
||||
"json/scanner",
|
||||
"json/token"
|
||||
]
|
||||
revision = "f40e974e75af4e271d97ce0fc917af5898ae7bda"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/inconshreveable/mousetrap"
|
||||
packages = ["."]
|
||||
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/jmhodges/levigo"
|
||||
packages = ["."]
|
||||
revision = "c42d9e0ca023e2198120196f842701bb4c55d7b9"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/kr/logfmt"
|
||||
packages = ["."]
|
||||
revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/magiconair/properties"
|
||||
packages = ["."]
|
||||
revision = "c3beff4c2358b44d0493c7dda585e7db7ff28ae6"
|
||||
version = "v1.7.6"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mitchellh/mapstructure"
|
||||
packages = ["."]
|
||||
revision = "00c29f56e2386353d58c599509e8dc3801b0d716"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pelletier/go-toml"
|
||||
packages = ["."]
|
||||
revision = "acdc4509485b587f5e675510c4f2c63e90ff68a8"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
packages = ["difflib"]
|
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/rcrowley/go-metrics"
|
||||
packages = ["."]
|
||||
revision = "8732c616f52954686704c8645fe1a9d59e9df7c1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/spf13/afero"
|
||||
packages = [
|
||||
".",
|
||||
"mem"
|
||||
]
|
||||
revision = "bb8f1927f2a9d3ab41c9340aa034f6b803f4359c"
|
||||
version = "v1.0.2"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/spf13/cast"
|
||||
packages = ["."]
|
||||
revision = "8965335b8c7107321228e3e3702cab9832751bac"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/spf13/cobra"
|
||||
packages = ["."]
|
||||
revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b"
|
||||
version = "v0.0.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/spf13/jwalterweatherman"
|
||||
packages = ["."]
|
||||
revision = "7c0cea34c8ece3fbeb2b27ab9b59511d360fb394"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/spf13/pflag"
|
||||
packages = ["."]
|
||||
revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/spf13/viper"
|
||||
packages = ["."]
|
||||
revision = "b5e8006cbee93ec955a89ab31e0e3ce3204f3736"
|
||||
version = "v1.0.2"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = [
|
||||
"assert",
|
||||
"require"
|
||||
]
|
||||
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
|
||||
version = "v1.2.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/syndtr/goleveldb"
|
||||
packages = [
|
||||
"leveldb",
|
||||
"leveldb/cache",
|
||||
"leveldb/comparer",
|
||||
"leveldb/errors",
|
||||
"leveldb/filter",
|
||||
"leveldb/iterator",
|
||||
"leveldb/journal",
|
||||
"leveldb/memdb",
|
||||
"leveldb/opt",
|
||||
"leveldb/storage",
|
||||
"leveldb/table",
|
||||
"leveldb/util"
|
||||
]
|
||||
revision = "169b1b37be738edb2813dab48c97a549bcf99bb5"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tendermint/abci"
|
||||
packages = [
|
||||
"client",
|
||||
"example/code",
|
||||
"example/counter",
|
||||
"example/kvstore",
|
||||
"server",
|
||||
"types"
|
||||
]
|
||||
revision = "46686763ba8ea595ede16530ed4a40fb38f49f94"
|
||||
version = "v0.10.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/tendermint/ed25519"
|
||||
packages = [
|
||||
".",
|
||||
"edwards25519",
|
||||
"extra25519"
|
||||
]
|
||||
revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tendermint/go-crypto"
|
||||
packages = ["."]
|
||||
revision = "c3e19f3ea26f5c3357e0bcbb799b0761ef923755"
|
||||
version = "v0.5.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tendermint/go-wire"
|
||||
packages = [
|
||||
".",
|
||||
"data"
|
||||
]
|
||||
revision = "fa721242b042ecd4c6ed1a934ee740db4f74e45c"
|
||||
source = "github.com/tendermint/go-amino"
|
||||
version = "v0.7.3"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tendermint/tmlibs"
|
||||
packages = [
|
||||
"autofile",
|
||||
"cli",
|
||||
"cli/flags",
|
||||
"clist",
|
||||
"common",
|
||||
"db",
|
||||
"flowrate",
|
||||
"log",
|
||||
"merkle",
|
||||
"pubsub",
|
||||
"pubsub/query",
|
||||
"test"
|
||||
]
|
||||
revision = "24da7009c3d8c019b40ba4287495749e3160caca"
|
||||
version = "v0.7.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"curve25519",
|
||||
"nacl/box",
|
||||
"nacl/secretbox",
|
||||
"openpgp/armor",
|
||||
"openpgp/errors",
|
||||
"poly1305",
|
||||
"ripemd160",
|
||||
"salsa20/salsa"
|
||||
]
|
||||
revision = "88942b9c40a4c9d203b82b3731787b672d6e809b"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
"http2",
|
||||
"http2/hpack",
|
||||
"idna",
|
||||
"internal/timeseries",
|
||||
"lex/httplex",
|
||||
"trace"
|
||||
]
|
||||
revision = "6078986fec03a1dcc236c34816c71b0e05018fda"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix"]
|
||||
revision = "91ee8cde435411ca3f1cd365e8f20131aed4d0a1"
|
||||
|
||||
[[projects]]
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"collate",
|
||||
"collate/build",
|
||||
"internal/colltab",
|
||||
"internal/gen",
|
||||
"internal/tag",
|
||||
"internal/triegen",
|
||||
"internal/ucd",
|
||||
"language",
|
||||
"secure/bidirule",
|
||||
"transform",
|
||||
"unicode/bidi",
|
||||
"unicode/cldr",
|
||||
"unicode/norm",
|
||||
"unicode/rangetable"
|
||||
]
|
||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "google.golang.org/genproto"
|
||||
packages = ["googleapis/rpc/status"]
|
||||
revision = "f8c8703595236ae70fdf8789ecb656ea0bcdcf46"
|
||||
|
||||
[[projects]]
|
||||
name = "google.golang.org/grpc"
|
||||
packages = [
|
||||
".",
|
||||
"balancer",
|
||||
"codes",
|
||||
"connectivity",
|
||||
"credentials",
|
||||
"grpclb/grpc_lb_v1/messages",
|
||||
"grpclog",
|
||||
"internal",
|
||||
"keepalive",
|
||||
"metadata",
|
||||
"naming",
|
||||
"peer",
|
||||
"resolver",
|
||||
"stats",
|
||||
"status",
|
||||
"tap",
|
||||
"transport"
|
||||
]
|
||||
revision = "5b3c4e850e90a4cf6a20ebd46c8b32a0a3afcb9e"
|
||||
version = "v1.7.5"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
revision = "7f97868eec74b32b0982dd158a51a446d1da7eb5"
|
||||
version = "v2.1.1"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "4dca5dbd2d280d093d7c8fc423606ab86d6ad1b241b076a7716c2093b5a09231"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
95
Gopkg.toml
Normal file
95
Gopkg.toml
Normal file
@@ -0,0 +1,95 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/ebuchman/fail-test"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/fortytw2/leaktest"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-kit/kit"
|
||||
version = "~0.6.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gogo/protobuf"
|
||||
version = "~1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/golang/protobuf"
|
||||
version = "~1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gorilla/websocket"
|
||||
version = "~1.2.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/pkg/errors"
|
||||
version = "~0.8.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/rcrowley/go-metrics"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/spf13/cobra"
|
||||
version = "~0.0.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/spf13/viper"
|
||||
version = "~1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/stretchr/testify"
|
||||
version = "~1.2.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tendermint/abci"
|
||||
version = "~0.10.2"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tendermint/go-crypto"
|
||||
version = "~0.5.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tendermint/go-wire"
|
||||
source = "github.com/tendermint/go-amino"
|
||||
version = "~0.7.3"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tendermint/tmlibs"
|
||||
version = "~0.7.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "google.golang.org/grpc"
|
||||
version = "~1.7.3"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
95
Makefile
95
Makefile
@@ -1,13 +1,13 @@
|
||||
GOTOOLS = \
|
||||
github.com/tendermint/glide \
|
||||
# gopkg.in/alecthomas/gometalinter.v2
|
||||
github.com/golang/dep/cmd/dep \
|
||||
gopkg.in/alecthomas/gometalinter.v2
|
||||
PACKAGES=$(shell go list ./... | grep -v '/vendor/')
|
||||
BUILD_TAGS?=tendermint
|
||||
BUILD_FLAGS = -ldflags "-X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse --short=8 HEAD`"
|
||||
|
||||
all: check build test install
|
||||
|
||||
check: check_tools get_vendor_deps
|
||||
check: check_tools ensure_deps
|
||||
|
||||
|
||||
########################################
|
||||
@@ -40,36 +40,89 @@ check_tools:
|
||||
get_tools:
|
||||
@echo "--> Installing tools"
|
||||
go get -u -v $(GOTOOLS)
|
||||
# @gometalinter.v2 --install
|
||||
@gometalinter.v2 --install
|
||||
|
||||
update_tools:
|
||||
@echo "--> Updating tools"
|
||||
@go get -u $(GOTOOLS)
|
||||
|
||||
#Run this from CI
|
||||
get_vendor_deps:
|
||||
@rm -rf vendor/
|
||||
@echo "--> Running glide install"
|
||||
@glide install
|
||||
@echo "--> Running dep"
|
||||
@dep ensure -vendor-only
|
||||
|
||||
|
||||
#Run this locally.
|
||||
ensure_deps:
|
||||
@rm -rf vendor/
|
||||
@echo "--> Running dep"
|
||||
@dep ensure
|
||||
|
||||
draw_deps:
|
||||
@# requires brew install graphviz or apt-get install graphviz
|
||||
go get github.com/RobotsAndPencils/goviz
|
||||
@goviz -i github.com/tendermint/tendermint/cmd/tendermint -d 3 | dot -Tpng -o dependency-graph.png
|
||||
|
||||
get_deps_bin_size:
|
||||
@# Copy of build recipe with additional flags to perform binary size analysis
|
||||
$(eval $(shell go build -work -a $(BUILD_FLAGS) -tags '$(BUILD_TAGS)' -o build/tendermint ./cmd/tendermint/ 2>&1))
|
||||
@find $(WORK) -type f -name "*.a" | xargs -I{} du -hxs "{}" | sort -rh | sed -e s:${WORK}/::g > deps_bin_size.log
|
||||
@echo "Results can be found here: $(CURDIR)/deps_bin_size.log"
|
||||
|
||||
########################################
|
||||
### Testing
|
||||
|
||||
test:
|
||||
@echo "--> Running go test"
|
||||
@go test $(PACKAGES)
|
||||
## required to be run first by most tests
|
||||
build_docker_test_image:
|
||||
docker build -t tester -f ./test/docker/Dockerfile .
|
||||
|
||||
test_race:
|
||||
@echo "--> Running go test --race"
|
||||
@go test -v -race $(PACKAGES)
|
||||
### coverage, app, persistence, and libs tests
|
||||
test_cover:
|
||||
# run the go unit tests with coverage
|
||||
bash test/test_cover.sh
|
||||
|
||||
test_apps:
|
||||
# run the app tests using bash
|
||||
# requires `abci-cli` and `tendermint` binaries installed
|
||||
bash test/app/test.sh
|
||||
|
||||
test_persistence:
|
||||
# run the persistence tests using bash
|
||||
# requires `abci-cli` installed
|
||||
docker run --name run_persistence -t tester bash test/persist/test_failure_indices.sh
|
||||
|
||||
# TODO undockerize
|
||||
# bash test/persist/test_failure_indices.sh
|
||||
|
||||
test_p2p:
|
||||
docker rm -f rsyslog || true
|
||||
rm -rf test/logs || true
|
||||
mkdir test/logs
|
||||
cd test/
|
||||
docker run -d -v "logs:/var/log/" -p 127.0.0.1:5514:514/udp --name rsyslog voxxit/rsyslog
|
||||
cd ..
|
||||
# requires 'tester' the image from above
|
||||
bash test/p2p/test.sh tester
|
||||
|
||||
need_abci:
|
||||
bash scripts/install_abci_apps.sh
|
||||
|
||||
test_integrations:
|
||||
@bash ./test/test.sh
|
||||
make build_docker_test_image
|
||||
make get_tools
|
||||
make get_vendor_deps
|
||||
make install
|
||||
make need_abci
|
||||
make test_cover
|
||||
make test_apps
|
||||
make test_persistence
|
||||
make test_p2p
|
||||
|
||||
test_libs:
|
||||
# checkout every github.com/tendermint dir and run its tests
|
||||
# NOTE: on release-* or master branches only (set by Jenkins)
|
||||
docker run --name run_libs -t tester bash test/test_libs.sh
|
||||
|
||||
test_release:
|
||||
@go test -tags release $(PACKAGES)
|
||||
@@ -79,10 +132,17 @@ test100:
|
||||
|
||||
vagrant_test:
|
||||
vagrant up
|
||||
vagrant ssh -c 'make install'
|
||||
vagrant ssh -c 'make test_race'
|
||||
vagrant ssh -c 'make test_integrations'
|
||||
|
||||
### go tests
|
||||
test:
|
||||
@echo "--> Running go test"
|
||||
@go test $(PACKAGES)
|
||||
|
||||
test_race:
|
||||
@echo "--> Running go test --race"
|
||||
@go test -v -race $(PACKAGES)
|
||||
|
||||
|
||||
########################################
|
||||
### Formatting, linting, and vetting
|
||||
@@ -92,7 +152,7 @@ fmt:
|
||||
|
||||
metalinter:
|
||||
@echo "--> Running linter"
|
||||
gometalinter.v2 --vendor --deadline=600s --disable-all \
|
||||
@gometalinter.v2 --vendor --deadline=600s --disable-all \
|
||||
--enable=deadcode \
|
||||
--enable=gosimple \
|
||||
--enable=misspell \
|
||||
@@ -123,8 +183,7 @@ metalinter_all:
|
||||
@echo "--> Running linter (all)"
|
||||
gometalinter.v2 --vendor --deadline=600s --enable-all --disable=lll ./...
|
||||
|
||||
|
||||
# To avoid unintended conflicts with file names, always add to .PHONY
|
||||
# unless there is a reason not to.
|
||||
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
|
||||
.PHONY: check build build_race dist install check_tools get_tools update_tools get_vendor_deps draw_deps test test_race test_integrations test_release test100 vagrant_test fmt metalinter metalinter_all
|
||||
.PHONY: check build build_race dist install check_tools get_tools update_tools get_vendor_deps draw_deps test_cover test_apps test_persistence test_p2p test test_race test_libs test_integrations test_release test100 vagrant_test fmt
|
||||
|
||||
@@ -14,7 +14,7 @@ if [ ! -d $DATA ]; then
|
||||
echo "starting node"
|
||||
tendermint node \
|
||||
--home $DATA \
|
||||
--proxy_app dummy \
|
||||
--proxy_app kvstore \
|
||||
--p2p.laddr tcp://127.0.0.1:56656 \
|
||||
--rpc.laddr tcp://127.0.0.1:56657 \
|
||||
--log_level error &
|
||||
@@ -35,7 +35,7 @@ cp -R $DATA $HOME1
|
||||
echo "starting validator node"
|
||||
tendermint node \
|
||||
--home $HOME1 \
|
||||
--proxy_app dummy \
|
||||
--proxy_app kvstore \
|
||||
--p2p.laddr tcp://127.0.0.1:56656 \
|
||||
--rpc.laddr tcp://127.0.0.1:56657 \
|
||||
--log_level error &
|
||||
@@ -48,7 +48,7 @@ cp $HOME1/genesis.json $HOME2
|
||||
printf "starting downloader node"
|
||||
tendermint node \
|
||||
--home $HOME2 \
|
||||
--proxy_app dummy \
|
||||
--proxy_app kvstore \
|
||||
--p2p.laddr tcp://127.0.0.1:56666 \
|
||||
--rpc.laddr tcp://127.0.0.1:56667 \
|
||||
--p2p.persistent_peers 127.0.0.1:56656 \
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
@@ -39,9 +40,12 @@ const (
|
||||
// Assuming a DSL connection (not a good choice) 128 Kbps (upload) ~ 15 KB/s,
|
||||
// sending data across atlantic ~ 7.5 KB/s.
|
||||
minRecvRate = 7680
|
||||
|
||||
// Maximum difference between current and new block's height.
|
||||
maxDiffBetweenCurrentAndReceivedBlockHeight = 100
|
||||
)
|
||||
|
||||
var peerTimeoutSeconds = time.Duration(15) // not const so we can override with tests
|
||||
var peerTimeout = 15 * time.Second // not const so we can override with tests
|
||||
|
||||
/*
|
||||
Peers self report their heights when we join the block pool.
|
||||
@@ -68,10 +72,10 @@ type BlockPool struct {
|
||||
maxPeerHeight int64
|
||||
|
||||
requestsCh chan<- BlockRequest
|
||||
timeoutsCh chan<- p2p.ID
|
||||
errorsCh chan<- peerError
|
||||
}
|
||||
|
||||
func NewBlockPool(start int64, requestsCh chan<- BlockRequest, timeoutsCh chan<- p2p.ID) *BlockPool {
|
||||
func NewBlockPool(start int64, requestsCh chan<- BlockRequest, errorsCh chan<- peerError) *BlockPool {
|
||||
bp := &BlockPool{
|
||||
peers: make(map[p2p.ID]*bpPeer),
|
||||
|
||||
@@ -80,7 +84,7 @@ func NewBlockPool(start int64, requestsCh chan<- BlockRequest, timeoutsCh chan<-
|
||||
numPending: 0,
|
||||
|
||||
requestsCh: requestsCh,
|
||||
timeoutsCh: timeoutsCh,
|
||||
errorsCh: errorsCh,
|
||||
}
|
||||
bp.BaseService = *cmn.NewBaseService(nil, "BlockPool", bp)
|
||||
return bp
|
||||
@@ -128,9 +132,10 @@ func (pool *BlockPool) removeTimedoutPeers() {
|
||||
curRate := peer.recvMonitor.Status().CurRate
|
||||
// curRate can be 0 on start
|
||||
if curRate != 0 && curRate < minRecvRate {
|
||||
pool.sendTimeout(peer.id)
|
||||
err := errors.New("peer is not sending us data fast enough")
|
||||
pool.sendError(err, peer.id)
|
||||
pool.Logger.Error("SendTimeout", "peer", peer.id,
|
||||
"reason", "peer is not sending us data fast enough",
|
||||
"reason", err,
|
||||
"curRate", fmt.Sprintf("%d KB/s", curRate/1024),
|
||||
"minRate", fmt.Sprintf("%d KB/s", minRecvRate/1024))
|
||||
peer.didTimeout = true
|
||||
@@ -199,7 +204,7 @@ func (pool *BlockPool) PopRequest() {
|
||||
delete(pool.requesters, pool.height)
|
||||
pool.height++
|
||||
} else {
|
||||
cmn.PanicSanity(cmn.Fmt("Expected requester to pop, got nothing at height %v", pool.height))
|
||||
panic(fmt.Sprintf("Expected requester to pop, got nothing at height %v", pool.height))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,8 +218,9 @@ func (pool *BlockPool) RedoRequest(height int64) p2p.ID {
|
||||
request := pool.requesters[height]
|
||||
|
||||
if request.block == nil {
|
||||
cmn.PanicSanity("Expected block to be non-nil")
|
||||
panic("Expected block to be non-nil")
|
||||
}
|
||||
|
||||
// RemovePeer will redo all requesters associated with this peer.
|
||||
pool.removePeer(request.peerID)
|
||||
return request.peerID
|
||||
@@ -227,8 +233,14 @@ func (pool *BlockPool) AddBlock(peerID p2p.ID, block *types.Block, blockSize int
|
||||
|
||||
requester := pool.requesters[block.Height]
|
||||
if requester == nil {
|
||||
// a block we didn't expect.
|
||||
// TODO:if height is too far ahead, punish peer
|
||||
pool.Logger.Info("peer sent us a block we didn't expect", "peer", peerID, "curHeight", pool.height, "blockHeight", block.Height)
|
||||
diff := pool.height - block.Height
|
||||
if diff < 0 {
|
||||
diff *= -1
|
||||
}
|
||||
if diff > maxDiffBetweenCurrentAndReceivedBlockHeight {
|
||||
pool.sendError(errors.New("peer sent us a block we didn't expect with a height too far ahead/behind"), peerID)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -339,11 +351,11 @@ func (pool *BlockPool) sendRequest(height int64, peerID p2p.ID) {
|
||||
pool.requestsCh <- BlockRequest{height, peerID}
|
||||
}
|
||||
|
||||
func (pool *BlockPool) sendTimeout(peerID p2p.ID) {
|
||||
func (pool *BlockPool) sendError(err error, peerID p2p.ID) {
|
||||
if !pool.IsRunning() {
|
||||
return
|
||||
}
|
||||
pool.timeoutsCh <- peerID
|
||||
pool.errorsCh <- peerError{err, peerID}
|
||||
}
|
||||
|
||||
// unused by tendermint; left for debugging purposes
|
||||
@@ -402,9 +414,9 @@ func (peer *bpPeer) resetMonitor() {
|
||||
|
||||
func (peer *bpPeer) resetTimeout() {
|
||||
if peer.timeout == nil {
|
||||
peer.timeout = time.AfterFunc(time.Second*peerTimeoutSeconds, peer.onTimeout)
|
||||
peer.timeout = time.AfterFunc(peerTimeout, peer.onTimeout)
|
||||
} else {
|
||||
peer.timeout.Reset(time.Second * peerTimeoutSeconds)
|
||||
peer.timeout.Reset(peerTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,8 +442,9 @@ func (peer *bpPeer) onTimeout() {
|
||||
peer.pool.mtx.Lock()
|
||||
defer peer.pool.mtx.Unlock()
|
||||
|
||||
peer.pool.sendTimeout(peer.id)
|
||||
peer.logger.Error("SendTimeout", "reason", "onTimeout")
|
||||
err := errors.New("peer did not send us anything")
|
||||
peer.pool.sendError(err, peer.id)
|
||||
peer.logger.Error("SendTimeout", "reason", err, "timeout", peerTimeout)
|
||||
peer.didTimeout = true
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
peerTimeoutSeconds = time.Duration(2)
|
||||
peerTimeout = 2 * time.Second
|
||||
}
|
||||
|
||||
type testPeer struct {
|
||||
@@ -34,9 +34,9 @@ func makePeers(numPeers int, minHeight, maxHeight int64) map[p2p.ID]testPeer {
|
||||
func TestBasic(t *testing.T) {
|
||||
start := int64(42)
|
||||
peers := makePeers(10, start+1, 1000)
|
||||
timeoutsCh := make(chan p2p.ID, 100)
|
||||
requestsCh := make(chan BlockRequest, 100)
|
||||
pool := NewBlockPool(start, requestsCh, timeoutsCh)
|
||||
errorsCh := make(chan peerError, 1000)
|
||||
requestsCh := make(chan BlockRequest, 1000)
|
||||
pool := NewBlockPool(start, requestsCh, errorsCh)
|
||||
pool.SetLogger(log.TestingLogger())
|
||||
|
||||
err := pool.Start()
|
||||
@@ -71,8 +71,8 @@ func TestBasic(t *testing.T) {
|
||||
// Pull from channels
|
||||
for {
|
||||
select {
|
||||
case peerID := <-timeoutsCh:
|
||||
t.Errorf("timeout: %v", peerID)
|
||||
case err := <-errorsCh:
|
||||
t.Error(err)
|
||||
case request := <-requestsCh:
|
||||
t.Logf("Pulled new BlockRequest %v", request)
|
||||
if request.Height == 300 {
|
||||
@@ -91,9 +91,9 @@ func TestBasic(t *testing.T) {
|
||||
func TestTimeout(t *testing.T) {
|
||||
start := int64(42)
|
||||
peers := makePeers(10, start+1, 1000)
|
||||
timeoutsCh := make(chan p2p.ID, 100)
|
||||
requestsCh := make(chan BlockRequest, 100)
|
||||
pool := NewBlockPool(start, requestsCh, timeoutsCh)
|
||||
errorsCh := make(chan peerError, 1000)
|
||||
requestsCh := make(chan BlockRequest, 1000)
|
||||
pool := NewBlockPool(start, requestsCh, errorsCh)
|
||||
pool.SetLogger(log.TestingLogger())
|
||||
err := pool.Start()
|
||||
if err != nil {
|
||||
@@ -132,9 +132,10 @@ func TestTimeout(t *testing.T) {
|
||||
timedOut := map[p2p.ID]struct{}{}
|
||||
for {
|
||||
select {
|
||||
case peerID := <-timeoutsCh:
|
||||
t.Logf("Peer %v timeouted", peerID)
|
||||
if _, ok := timedOut[peerID]; !ok {
|
||||
case err := <-errorsCh:
|
||||
t.Log(err)
|
||||
// consider error to be always timeout here
|
||||
if _, ok := timedOut[err.peerID]; !ok {
|
||||
counter++
|
||||
if counter == len(peers) {
|
||||
return // Done!
|
||||
|
||||
@@ -22,8 +22,7 @@ const (
|
||||
// BlockchainChannel is a channel for blocks and status updates (`BlockStore` height)
|
||||
BlockchainChannel = byte(0x40)
|
||||
|
||||
defaultChannelCapacity = 1000
|
||||
trySyncIntervalMS = 50
|
||||
trySyncIntervalMS = 50
|
||||
// stop syncing when last block's time is
|
||||
// within this much of the system time.
|
||||
// stopSyncingDurationMinutes = 10
|
||||
@@ -40,6 +39,15 @@ type consensusReactor interface {
|
||||
SwitchToConsensus(sm.State, int)
|
||||
}
|
||||
|
||||
type peerError struct {
|
||||
err error
|
||||
peerID p2p.ID
|
||||
}
|
||||
|
||||
func (e peerError) Error() string {
|
||||
return fmt.Sprintf("error with peer %v: %s", e.peerID, e.err.Error())
|
||||
}
|
||||
|
||||
// BlockchainReactor handles long-term catchup syncing.
|
||||
type BlockchainReactor struct {
|
||||
p2p.BaseReactor
|
||||
@@ -56,7 +64,7 @@ type BlockchainReactor struct {
|
||||
fastSync bool
|
||||
|
||||
requestsCh <-chan BlockRequest
|
||||
timeoutsCh <-chan p2p.ID
|
||||
errorsCh <-chan peerError
|
||||
}
|
||||
|
||||
// NewBlockchainReactor returns new reactor instance.
|
||||
@@ -64,17 +72,20 @@ func NewBlockchainReactor(state sm.State, blockExec *sm.BlockExecutor, store *Bl
|
||||
fastSync bool) *BlockchainReactor {
|
||||
|
||||
if state.LastBlockHeight != store.Height() {
|
||||
cmn.PanicSanity(cmn.Fmt("state (%v) and store (%v) height mismatch", state.LastBlockHeight,
|
||||
panic(fmt.Sprintf("state (%v) and store (%v) height mismatch", state.LastBlockHeight,
|
||||
store.Height()))
|
||||
}
|
||||
|
||||
requestsCh := make(chan BlockRequest, defaultChannelCapacity)
|
||||
timeoutsCh := make(chan p2p.ID, defaultChannelCapacity)
|
||||
const capacity = 1000 // must be bigger than peers count
|
||||
requestsCh := make(chan BlockRequest, capacity)
|
||||
errorsCh := make(chan peerError, capacity) // so we don't block in #Receive#pool.AddBlock
|
||||
|
||||
pool := NewBlockPool(
|
||||
store.Height()+1,
|
||||
requestsCh,
|
||||
timeoutsCh,
|
||||
errorsCh,
|
||||
)
|
||||
|
||||
bcR := &BlockchainReactor{
|
||||
params: state.ConsensusParams,
|
||||
initialState: state,
|
||||
@@ -83,7 +94,7 @@ func NewBlockchainReactor(state sm.State, blockExec *sm.BlockExecutor, store *Bl
|
||||
pool: pool,
|
||||
fastSync: fastSync,
|
||||
requestsCh: requestsCh,
|
||||
timeoutsCh: timeoutsCh,
|
||||
errorsCh: errorsCh,
|
||||
}
|
||||
bcR.BaseReactor = *p2p.NewBaseReactor("BlockchainReactor", bcR)
|
||||
return bcR
|
||||
@@ -166,7 +177,8 @@ func (bcR *BlockchainReactor) respondToPeer(msg *bcBlockRequestMessage,
|
||||
func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) {
|
||||
_, msg, err := DecodeMessage(msgBytes, bcR.maxMsgSize())
|
||||
if err != nil {
|
||||
bcR.Logger.Error("Error decoding message", "err", err)
|
||||
bcR.Logger.Error("Error decoding message", "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes)
|
||||
bcR.Switch.StopPeerForError(src, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -230,7 +242,7 @@ func (bcR *BlockchainReactor) poolRoutine() {
|
||||
FOR_LOOP:
|
||||
for {
|
||||
select {
|
||||
case request := <-bcR.requestsCh: // chan BlockRequest
|
||||
case request := <-bcR.requestsCh:
|
||||
peer := bcR.Switch.Peers().Get(request.PeerID)
|
||||
if peer == nil {
|
||||
continue FOR_LOOP // Peer has since been disconnected.
|
||||
@@ -242,11 +254,10 @@ FOR_LOOP:
|
||||
// The pool handles timeouts, just let it go.
|
||||
continue FOR_LOOP
|
||||
}
|
||||
case peerID := <-bcR.timeoutsCh: // chan string
|
||||
// Peer timed out.
|
||||
peer := bcR.Switch.Peers().Get(peerID)
|
||||
case err := <-bcR.errorsCh:
|
||||
peer := bcR.Switch.Peers().Get(err.peerID)
|
||||
if peer != nil {
|
||||
bcR.Switch.StopPeerForError(peer, errors.New("BlockchainReactor Timeout"))
|
||||
bcR.Switch.StopPeerForError(peer, err)
|
||||
}
|
||||
case <-statusUpdateTicker.C:
|
||||
// ask for status updates
|
||||
|
||||
@@ -76,7 +76,7 @@ func (bs *BlockStore) LoadBlock(height int64) *types.Block {
|
||||
}
|
||||
blockMeta := wire.ReadBinary(&types.BlockMeta{}, r, 0, &n, &err).(*types.BlockMeta)
|
||||
if err != nil {
|
||||
cmn.PanicCrisis(cmn.Fmt("Error reading block meta: %v", err))
|
||||
panic(fmt.Sprintf("Error reading block meta: %v", err))
|
||||
}
|
||||
bytez := []byte{}
|
||||
for i := 0; i < blockMeta.BlockID.PartsHeader.Total; i++ {
|
||||
@@ -85,7 +85,7 @@ func (bs *BlockStore) LoadBlock(height int64) *types.Block {
|
||||
}
|
||||
block := wire.ReadBinary(&types.Block{}, bytes.NewReader(bytez), 0, &n, &err).(*types.Block)
|
||||
if err != nil {
|
||||
cmn.PanicCrisis(cmn.Fmt("Error reading block: %v", err))
|
||||
panic(fmt.Sprintf("Error reading block: %v", err))
|
||||
}
|
||||
return block
|
||||
}
|
||||
@@ -102,7 +102,7 @@ func (bs *BlockStore) LoadBlockPart(height int64, index int) *types.Part {
|
||||
}
|
||||
part := wire.ReadBinary(&types.Part{}, r, 0, &n, &err).(*types.Part)
|
||||
if err != nil {
|
||||
cmn.PanicCrisis(cmn.Fmt("Error reading block part: %v", err))
|
||||
panic(fmt.Sprintf("Error reading block part: %v", err))
|
||||
}
|
||||
return part
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func (bs *BlockStore) LoadBlockMeta(height int64) *types.BlockMeta {
|
||||
}
|
||||
blockMeta := wire.ReadBinary(&types.BlockMeta{}, r, 0, &n, &err).(*types.BlockMeta)
|
||||
if err != nil {
|
||||
cmn.PanicCrisis(cmn.Fmt("Error reading block meta: %v", err))
|
||||
panic(fmt.Sprintf("Error reading block meta: %v", err))
|
||||
}
|
||||
return blockMeta
|
||||
}
|
||||
@@ -136,7 +136,7 @@ func (bs *BlockStore) LoadBlockCommit(height int64) *types.Commit {
|
||||
}
|
||||
commit := wire.ReadBinary(&types.Commit{}, r, 0, &n, &err).(*types.Commit)
|
||||
if err != nil {
|
||||
cmn.PanicCrisis(cmn.Fmt("Error reading commit: %v", err))
|
||||
panic(fmt.Sprintf("Error reading commit: %v", err))
|
||||
}
|
||||
return commit
|
||||
}
|
||||
@@ -153,7 +153,7 @@ func (bs *BlockStore) LoadSeenCommit(height int64) *types.Commit {
|
||||
}
|
||||
commit := wire.ReadBinary(&types.Commit{}, r, 0, &n, &err).(*types.Commit)
|
||||
if err != nil {
|
||||
cmn.PanicCrisis(cmn.Fmt("Error reading commit: %v", err))
|
||||
panic(fmt.Sprintf("Error reading commit: %v", err))
|
||||
}
|
||||
return commit
|
||||
}
|
||||
@@ -262,7 +262,7 @@ func LoadBlockStoreStateJSON(db dbm.DB) BlockStoreStateJSON {
|
||||
bsj := BlockStoreStateJSON{}
|
||||
err := json.Unmarshal(bytes, &bsj)
|
||||
if err != nil {
|
||||
cmn.PanicCrisis(cmn.Fmt("Could not unmarshal bytes: %X", bytes))
|
||||
panic(fmt.Sprintf("Could not unmarshal bytes: %X", bytes))
|
||||
}
|
||||
return bsj
|
||||
}
|
||||
|
||||
35
circle.yml
35
circle.yml
@@ -1,35 +0,0 @@
|
||||
---
|
||||
machine:
|
||||
environment:
|
||||
MACH_PREFIX: tendermint-test-mach
|
||||
DOCKER_VERSION: 1.10.0
|
||||
DOCKER_MACHINE_VERSION: 0.9.0
|
||||
GOPATH: "$HOME/.go_project"
|
||||
PROJECT_PARENT_PATH: "$GOPATH/src/github.com/$CIRCLE_PROJECT_USERNAME"
|
||||
PROJECT_PATH: "$PROJECT_PARENT_PATH/$CIRCLE_PROJECT_REPONAME"
|
||||
PATH: "$HOME/.go_project/bin:${PATH}"
|
||||
hosts:
|
||||
localhost: 127.0.0.1
|
||||
|
||||
dependencies:
|
||||
override:
|
||||
- curl -sSL https://s3.amazonaws.com/circle-downloads/install-circleci-docker.sh | sudo bash -s -- $DOCKER_VERSION
|
||||
- sudo start docker
|
||||
- sudo curl -sSL -o /usr/bin/docker-machine "https://github.com/docker/machine/releases/download/v$DOCKER_MACHINE_VERSION/docker-machine-`uname -s`-`uname -m`"; sudo chmod 0755 /usr/bin/docker-machine
|
||||
- mkdir -p "$PROJECT_PARENT_PATH"
|
||||
- ln -sf "$HOME/$CIRCLE_PROJECT_REPONAME/" "$PROJECT_PATH"
|
||||
post:
|
||||
- go version
|
||||
- docker version
|
||||
- docker-machine version
|
||||
|
||||
test:
|
||||
override:
|
||||
- cd "$PROJECT_PATH" && set -o pipefail && make test_integrations 2>&1 | tee test_integrations.log:
|
||||
timeout: 1800
|
||||
post:
|
||||
- cd "$PROJECT_PATH" && mv test_integrations.log "${CIRCLE_ARTIFACTS}"
|
||||
- cd "$PROJECT_PATH" && bash <(curl -s https://codecov.io/bash) -f coverage.txt
|
||||
- cd "$PROJECT_PATH" && mv coverage.txt "${CIRCLE_ARTIFACTS}"
|
||||
- cd "$PROJECT_PATH" && cp test/logs/messages "${CIRCLE_ARTIFACTS}/docker.log"
|
||||
- cd "${CIRCLE_ARTIFACTS}" && tar czf logs.tar.gz *.log
|
||||
53
cmd/priv_val_server/main.go
Normal file
53
cmd/priv_val_server/main.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
priv_val "github.com/tendermint/tendermint/types/priv_validator"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
addr = flag.String("addr", ":46659", "Address of client to connect to")
|
||||
chainID = flag.String("chain-id", "mychain", "chain id")
|
||||
privValPath = flag.String("priv", "", "priv val file path")
|
||||
|
||||
logger = log.NewTMLogger(
|
||||
log.NewSyncWriter(os.Stdout),
|
||||
).With("module", "priv_val")
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
logger.Info(
|
||||
"Starting private validator",
|
||||
"addr", *addr,
|
||||
"chainID", *chainID,
|
||||
"privPath", *privValPath,
|
||||
)
|
||||
|
||||
privVal := priv_val.LoadPrivValidatorJSON(*privValPath)
|
||||
|
||||
rs := priv_val.NewRemoteSigner(
|
||||
logger,
|
||||
*chainID,
|
||||
*addr,
|
||||
privVal,
|
||||
crypto.GenPrivKeyEd25519(),
|
||||
)
|
||||
err := rs.Start()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cmn.TrapSignal(func() {
|
||||
err := rs.Stop()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -24,7 +24,7 @@ func initFiles(cmd *cobra.Command, args []string) {
|
||||
} else {
|
||||
privValidator = types.GenPrivValidatorFS(privValFile)
|
||||
privValidator.Save()
|
||||
logger.Info("Genetated private validator", "path", privValFile)
|
||||
logger.Info("Generated private validator", "path", privValFile)
|
||||
}
|
||||
|
||||
// genesis file
|
||||
@@ -43,6 +43,6 @@ func initFiles(cmd *cobra.Command, args []string) {
|
||||
if err := genDoc.SaveAs(genFile); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logger.Info("Genetated genesis file", "path", genFile)
|
||||
logger.Info("Generated genesis file", "path", genFile)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
@@ -32,12 +35,36 @@ var (
|
||||
|
||||
func init() {
|
||||
LiteCmd.Flags().StringVar(&listenAddr, "laddr", ":8888", "Serve the proxy on the given port")
|
||||
LiteCmd.Flags().StringVar(&nodeAddr, "node", "localhost:46657", "Connect to a Tendermint node at this address")
|
||||
LiteCmd.Flags().StringVar(&nodeAddr, "node", "tcp://localhost:46657", "Connect to a Tendermint node at this address")
|
||||
LiteCmd.Flags().StringVar(&chainID, "chain-id", "tendermint", "Specify the Tendermint chain ID")
|
||||
LiteCmd.Flags().StringVar(&home, "home-dir", ".tendermint-lite", "Specify the home directory")
|
||||
}
|
||||
|
||||
func ensureAddrHasSchemeOrDefaultToTCP(addr string) (string, error) {
|
||||
u, err := url.Parse(nodeAddr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch u.Scheme {
|
||||
case "tcp", "unix":
|
||||
case "":
|
||||
u.Scheme = "tcp"
|
||||
default:
|
||||
return "", fmt.Errorf("unknown scheme %q, use either tcp or unix", u.Scheme)
|
||||
}
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
func runProxy(cmd *cobra.Command, args []string) error {
|
||||
nodeAddr, err := ensureAddrHasSchemeOrDefaultToTCP(nodeAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
listenAddr, err := ensureAddrHasSchemeOrDefaultToTCP(listenAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// First, connect a client
|
||||
node := rpcclient.NewHTTP(nodeAddr, "/websocket")
|
||||
|
||||
|
||||
@@ -14,11 +14,14 @@ func AddNodeFlags(cmd *cobra.Command) {
|
||||
// bind flags
|
||||
cmd.Flags().String("moniker", config.Moniker, "Node Name")
|
||||
|
||||
// priv val flags
|
||||
cmd.Flags().String("priv_validator_laddr", config.PrivValidatorListenAddr, "Socket address to listen on for connections from external priv_validator process")
|
||||
|
||||
// node flags
|
||||
cmd.Flags().Bool("fast_sync", config.FastSync, "Fast blockchain syncing")
|
||||
|
||||
// abci flags
|
||||
cmd.Flags().String("proxy_app", config.ProxyApp, "Proxy app address, or 'nilapp' or 'dummy' for local testing.")
|
||||
cmd.Flags().String("proxy_app", config.ProxyApp, "Proxy app address, or 'nilapp' or 'kvstore' for local testing.")
|
||||
cmd.Flags().String("abci", config.ABCI, "Specify abci transport (socket | grpc)")
|
||||
|
||||
// rpc flags
|
||||
@@ -28,18 +31,19 @@ func AddNodeFlags(cmd *cobra.Command) {
|
||||
|
||||
// p2p flags
|
||||
cmd.Flags().String("p2p.laddr", config.P2P.ListenAddress, "Node listen address. (0.0.0.0:0 means any interface, any port)")
|
||||
cmd.Flags().String("p2p.seeds", config.P2P.Seeds, "Comma delimited host:port seed nodes")
|
||||
cmd.Flags().String("p2p.persistent_peers", config.P2P.PersistentPeers, "Comma delimited host:port persistent peers")
|
||||
cmd.Flags().String("p2p.seeds", config.P2P.Seeds, "Comma-delimited ID@host:port seed nodes")
|
||||
cmd.Flags().String("p2p.persistent_peers", config.P2P.PersistentPeers, "Comma-delimited ID@host:port persistent peers")
|
||||
cmd.Flags().Bool("p2p.skip_upnp", config.P2P.SkipUPNP, "Skip UPNP configuration")
|
||||
cmd.Flags().Bool("p2p.pex", config.P2P.PexReactor, "Enable/disable Peer-Exchange")
|
||||
cmd.Flags().Bool("p2p.seed_mode", config.P2P.SeedMode, "Enable/disable seed mode")
|
||||
cmd.Flags().String("p2p.private_peer_ids", config.P2P.PrivatePeerIDs, "Comma-delimited private peer IDs")
|
||||
|
||||
// consensus flags
|
||||
cmd.Flags().Bool("consensus.create_empty_blocks", config.Consensus.CreateEmptyBlocks, "Set this to false to only produce blocks when there are txs or when the AppHash changes")
|
||||
}
|
||||
|
||||
// NewRunNodeCmd returns the command that allows the CLI to start a
|
||||
// node. It can be used with a custom PrivValidator and in-process ABCI application.
|
||||
// NewRunNodeCmd returns the command that allows the CLI to start a node.
|
||||
// It can be used with a custom PrivValidator and in-process ABCI application.
|
||||
func NewRunNodeCmd(nodeProvider nm.NodeProvider) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "node",
|
||||
|
||||
25
cmd/tendermint/commands/show_node_id.go
Normal file
25
cmd/tendermint/commands/show_node_id.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
)
|
||||
|
||||
// ShowNodeIDCmd dumps node's ID to the standard output.
|
||||
var ShowNodeIDCmd = &cobra.Command{
|
||||
Use: "show_node_id",
|
||||
Short: "Show this node's ID",
|
||||
RunE: showNodeID,
|
||||
}
|
||||
|
||||
func showNodeID(cmd *cobra.Command, args []string) error {
|
||||
nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(nodeKey.ID())
|
||||
return nil
|
||||
}
|
||||
@@ -75,15 +75,16 @@ func testnetFiles(cmd *cobra.Command, args []string) {
|
||||
|
||||
// Initialize per-machine core directory
|
||||
func initMachCoreDirectory(base, mach string) error {
|
||||
// Create priv_validator.json file if not present
|
||||
defaultConfig := cfg.DefaultBaseConfig()
|
||||
dir := filepath.Join(base, mach)
|
||||
err := cmn.EnsureDir(dir, 0777)
|
||||
privValPath := filepath.Join(dir, defaultConfig.PrivValidator)
|
||||
dir = filepath.Dir(privValPath)
|
||||
err := cmn.EnsureDir(dir, 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create priv_validator.json file if not present
|
||||
defaultConfig := cfg.DefaultBaseConfig()
|
||||
ensurePrivValidator(filepath.Join(dir, defaultConfig.PrivValidator))
|
||||
ensurePrivValidator(privValPath)
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ func main() {
|
||||
cmd.ResetPrivValidatorCmd,
|
||||
cmd.ShowValidatorCmd,
|
||||
cmd.TestnetFilesCmd,
|
||||
cmd.ShowNodeIDCmd,
|
||||
cmd.VersionCmd)
|
||||
|
||||
// NOTE:
|
||||
|
||||
18
codecov.yml
Normal file
18
codecov.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
coverage:
|
||||
precision: 2
|
||||
round: down
|
||||
range: "70...100"
|
||||
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
threshold: 1%
|
||||
patch: on
|
||||
changes: off
|
||||
|
||||
comment:
|
||||
layout: "diff, files"
|
||||
behavior: default
|
||||
require_changes: no
|
||||
require_base: no
|
||||
require_head: yes
|
||||
@@ -20,9 +20,10 @@ var (
|
||||
|
||||
defaultConfigFileName = "config.toml"
|
||||
defaultGenesisJSONName = "genesis.json"
|
||||
defaultPrivValName = "priv_validator.json"
|
||||
defaultNodeKeyName = "node_key.json"
|
||||
defaultAddrBookName = "addrbook.json"
|
||||
|
||||
defaultPrivValName = "priv_validator.json"
|
||||
defaultNodeKeyName = "node_key.json"
|
||||
defaultAddrBookName = "addrbook.json"
|
||||
|
||||
defaultConfigFilePath = filepath.Join(defaultConfigDir, defaultConfigFileName)
|
||||
defaultGenesisJSONPath = filepath.Join(defaultConfigDir, defaultGenesisJSONName)
|
||||
@@ -103,6 +104,10 @@ type BaseConfig struct {
|
||||
// A custom human readable name for this node
|
||||
Moniker string `mapstructure:"moniker"`
|
||||
|
||||
// TCP or UNIX socket address for Tendermint to listen on for
|
||||
// connections from an external PrivValidator process
|
||||
PrivValidatorListenAddr string `mapstructure:"priv_validator_laddr"`
|
||||
|
||||
// TCP or UNIX socket address of the ABCI application,
|
||||
// or the name of an ABCI application compiled in with the Tendermint binary
|
||||
ProxyApp string `mapstructure:"proxy_app"`
|
||||
@@ -158,7 +163,7 @@ func DefaultBaseConfig() BaseConfig {
|
||||
func TestBaseConfig() BaseConfig {
|
||||
conf := DefaultBaseConfig()
|
||||
conf.chainID = "tendermint_test"
|
||||
conf.ProxyApp = "dummy"
|
||||
conf.ProxyApp = "kvstore"
|
||||
conf.FastSync = false
|
||||
conf.DBBackend = "memdb"
|
||||
return conf
|
||||
@@ -245,8 +250,8 @@ type P2PConfig struct {
|
||||
// We only use these if we can’t connect to peers in the addrbook
|
||||
Seeds string `mapstructure:"seeds"`
|
||||
|
||||
// Comma separated list of persistent peers to connect to
|
||||
// We always connect to these
|
||||
// Comma separated list of nodes to keep persistent connections to
|
||||
// Do not add private peers to this list if you don't want them advertised
|
||||
PersistentPeers string `mapstructure:"persistent_peers"`
|
||||
|
||||
// Skip UPNP port forwarding
|
||||
@@ -281,6 +286,12 @@ type P2PConfig struct {
|
||||
//
|
||||
// Does not work if the peer-exchange reactor is disabled.
|
||||
SeedMode bool `mapstructure:"seed_mode"`
|
||||
|
||||
// Authenticated encryption
|
||||
AuthEnc bool `mapstructure:"auth_enc"`
|
||||
|
||||
// Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
||||
PrivatePeerIDs string `mapstructure:"private_peer_ids"`
|
||||
}
|
||||
|
||||
// DefaultP2PConfig returns a default configuration for the peer-to-peer layer
|
||||
@@ -296,6 +307,7 @@ func DefaultP2PConfig() *P2PConfig {
|
||||
RecvRate: 512000, // 500 kB/s
|
||||
PexReactor: true,
|
||||
SeedMode: false,
|
||||
AuthEnc: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@ laddr = "{{ .P2P.ListenAddress }}"
|
||||
seeds = ""
|
||||
|
||||
# Comma separated list of nodes to keep persistent connections to
|
||||
# Do not add private peers to this list if you don't want them advertised
|
||||
persistent_peers = ""
|
||||
|
||||
# Path to address book
|
||||
@@ -159,6 +160,12 @@ pex = {{ .P2P.PexReactor }}
|
||||
# Does not work if the peer-exchange reactor is disabled.
|
||||
seed_mode = {{ .P2P.SeedMode }}
|
||||
|
||||
# Authenticated encryption
|
||||
auth_enc = {{ .P2P.AuthEnc }}
|
||||
|
||||
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
||||
private_peer_ids = "{{ .P2P.PrivatePeerIDs }}"
|
||||
|
||||
##### mempool configuration options #####
|
||||
[mempool]
|
||||
|
||||
|
||||
@@ -46,9 +46,9 @@ func TestByzantine(t *testing.T) {
|
||||
eventChans := make([]chan interface{}, N)
|
||||
reactors := make([]p2p.Reactor, N)
|
||||
for i := 0; i < N; i++ {
|
||||
// make first val byzantine
|
||||
if i == 0 {
|
||||
css[i].privValidator = NewByzantinePrivValidator(css[i].privValidator)
|
||||
// make byzantine
|
||||
css[i].decideProposal = func(j int) func(int64, int) {
|
||||
return func(height int64, round int) {
|
||||
byzantineDecideProposalFunc(t, height, round, css[j], switches[j])
|
||||
@@ -74,9 +74,11 @@ func TestByzantine(t *testing.T) {
|
||||
var conRI p2p.Reactor // nolint: gotype, gosimple
|
||||
conRI = conR
|
||||
|
||||
// make first val byzantine
|
||||
if i == 0 {
|
||||
conRI = NewByzantineReactor(conR)
|
||||
}
|
||||
|
||||
reactors[i] = conRI
|
||||
}
|
||||
|
||||
@@ -115,19 +117,19 @@ func TestByzantine(t *testing.T) {
|
||||
// and the other block to peers[1] and peers[2].
|
||||
// note peers and switches order don't match.
|
||||
peers := switches[0].Peers().List()
|
||||
|
||||
// partition A
|
||||
ind0 := getSwitchIndex(switches, peers[0])
|
||||
|
||||
// partition B
|
||||
ind1 := getSwitchIndex(switches, peers[1])
|
||||
ind2 := getSwitchIndex(switches, peers[2])
|
||||
|
||||
// connect the 2 peers in the larger partition
|
||||
p2p.Connect2Switches(switches, ind1, ind2)
|
||||
|
||||
// wait for someone in the big partition to make a block
|
||||
// wait for someone in the big partition (B) to make a block
|
||||
<-eventChans[ind2]
|
||||
|
||||
t.Log("A block has been committed. Healing partition")
|
||||
|
||||
// connect the partitions
|
||||
p2p.Connect2Switches(switches, ind0, ind1)
|
||||
p2p.Connect2Switches(switches, ind0, ind2)
|
||||
|
||||
@@ -289,17 +291,17 @@ func (privVal *ByzantinePrivValidator) GetPubKey() crypto.PubKey {
|
||||
}
|
||||
|
||||
func (privVal *ByzantinePrivValidator) SignVote(chainID string, vote *types.Vote) (err error) {
|
||||
vote.Signature, err = privVal.Sign(types.SignBytes(chainID, vote))
|
||||
vote.Signature, err = privVal.Sign(vote.SignBytes(chainID))
|
||||
return err
|
||||
}
|
||||
|
||||
func (privVal *ByzantinePrivValidator) SignProposal(chainID string, proposal *types.Proposal) (err error) {
|
||||
proposal.Signature, _ = privVal.Sign(types.SignBytes(chainID, proposal))
|
||||
proposal.Signature, _ = privVal.Sign(proposal.SignBytes(chainID))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (privVal *ByzantinePrivValidator) SignHeartbeat(chainID string, heartbeat *types.Heartbeat) (err error) {
|
||||
heartbeat.Signature, _ = privVal.Sign(types.SignBytes(chainID, heartbeat))
|
||||
heartbeat.Signature, _ = privVal.Sign(heartbeat.SignBytes(chainID))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
"github.com/tendermint/abci/example/counter"
|
||||
"github.com/tendermint/abci/example/dummy"
|
||||
"github.com/tendermint/abci/example/kvstore"
|
||||
|
||||
"github.com/go-kit/kit/log/term"
|
||||
)
|
||||
@@ -50,7 +50,7 @@ func ResetConfig(name string) *cfg.Config {
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// validator stub (a dummy consensus peer we control)
|
||||
// validator stub (a kvstore consensus peer we control)
|
||||
|
||||
type validatorStub struct {
|
||||
Index int // Validator index. NOTE: we don't assume validator set changes.
|
||||
@@ -488,7 +488,7 @@ func newCounter() abci.Application {
|
||||
return counter.NewCounterApplication(true)
|
||||
}
|
||||
|
||||
func newPersistentDummy() abci.Application {
|
||||
dir, _ := ioutil.TempDir("/tmp", "persistent-dummy")
|
||||
return dummy.NewPersistentDummyApplication(dir)
|
||||
func newPersistentKVStore() abci.Application {
|
||||
dir, _ := ioutil.TempDir("/tmp", "persistent-kvstore")
|
||||
return kvstore.NewPersistentKVStoreApplication(dir)
|
||||
}
|
||||
|
||||
@@ -152,6 +152,7 @@ func TestMempoolRmBadTx(t *testing.T) {
|
||||
txs := cs.mempool.Reap(1)
|
||||
if len(txs) == 0 {
|
||||
emptyMempoolCh <- struct{}{}
|
||||
return
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ const (
|
||||
VoteSetBitsChannel = byte(0x23)
|
||||
|
||||
maxConsensusMessageSize = 1048576 // 1MB; NOTE/TODO: keep in sync with types.PartSet sizes.
|
||||
|
||||
blocksToContributeToBecomeGoodPeer = 10000
|
||||
)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
@@ -179,7 +181,7 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte)
|
||||
_, msg, err := DecodeMessage(msgBytes)
|
||||
if err != nil {
|
||||
conR.Logger.Error("Error decoding message", "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes)
|
||||
// TODO punish peer?
|
||||
conR.Switch.StopPeerForError(src, err)
|
||||
return
|
||||
}
|
||||
conR.Logger.Debug("Receive", "src", src, "chId", chID, "msg", msg)
|
||||
@@ -251,6 +253,9 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte)
|
||||
ps.ApplyProposalPOLMessage(msg)
|
||||
case *BlockPartMessage:
|
||||
ps.SetHasProposalBlockPart(msg.Height, msg.Round, msg.Part.Index)
|
||||
if numBlocks := ps.RecordBlockPart(msg); numBlocks%blocksToContributeToBecomeGoodPeer == 0 {
|
||||
conR.Switch.MarkPeerAsGood(src)
|
||||
}
|
||||
conR.conS.peerMsgQueue <- msgInfo{msg, src.ID()}
|
||||
default:
|
||||
conR.Logger.Error(cmn.Fmt("Unknown message type %v", reflect.TypeOf(msg)))
|
||||
@@ -270,6 +275,9 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte)
|
||||
ps.EnsureVoteBitArrays(height, valSize)
|
||||
ps.EnsureVoteBitArrays(height-1, lastCommitSize)
|
||||
ps.SetHasVote(msg.Vote)
|
||||
if blocks := ps.RecordVote(msg.Vote); blocks%blocksToContributeToBecomeGoodPeer == 0 {
|
||||
conR.Switch.MarkPeerAsGood(src)
|
||||
}
|
||||
|
||||
cs.peerMsgQueue <- msgInfo{msg, src.ID()}
|
||||
|
||||
@@ -831,6 +839,21 @@ type PeerState struct {
|
||||
|
||||
mtx sync.Mutex
|
||||
cstypes.PeerRoundState
|
||||
|
||||
stats *peerStateStats
|
||||
}
|
||||
|
||||
// peerStateStats holds internal statistics for a peer.
|
||||
type peerStateStats struct {
|
||||
lastVoteHeight int64
|
||||
votes int
|
||||
|
||||
lastBlockPartHeight int64
|
||||
blockParts int
|
||||
}
|
||||
|
||||
func (pss peerStateStats) String() string {
|
||||
return fmt.Sprintf("peerStateStats{votes: %d, blockParts: %d}", pss.votes, pss.blockParts)
|
||||
}
|
||||
|
||||
// NewPeerState returns a new PeerState for the given Peer
|
||||
@@ -844,6 +867,7 @@ func NewPeerState(peer p2p.Peer) *PeerState {
|
||||
LastCommitRound: -1,
|
||||
CatchupCommitRound: -1,
|
||||
},
|
||||
stats: &peerStateStats{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1055,6 +1079,43 @@ func (ps *PeerState) ensureVoteBitArrays(height int64, numValidators int) {
|
||||
}
|
||||
}
|
||||
|
||||
// RecordVote updates internal statistics for this peer by recording the vote.
|
||||
// It returns the total number of votes (1 per block). This essentially means
|
||||
// the number of blocks for which peer has been sending us votes.
|
||||
func (ps *PeerState) RecordVote(vote *types.Vote) int {
|
||||
if ps.stats.lastVoteHeight >= vote.Height {
|
||||
return ps.stats.votes
|
||||
}
|
||||
ps.stats.lastVoteHeight = vote.Height
|
||||
ps.stats.votes += 1
|
||||
return ps.stats.votes
|
||||
}
|
||||
|
||||
// VotesSent returns the number of blocks for which peer has been sending us
|
||||
// votes.
|
||||
func (ps *PeerState) VotesSent() int {
|
||||
return ps.stats.votes
|
||||
}
|
||||
|
||||
// RecordVote updates internal statistics for this peer by recording the block part.
|
||||
// It returns the total number of block parts (1 per block). This essentially means
|
||||
// the number of blocks for which peer has been sending us block parts.
|
||||
func (ps *PeerState) RecordBlockPart(bp *BlockPartMessage) int {
|
||||
if ps.stats.lastBlockPartHeight >= bp.Height {
|
||||
return ps.stats.blockParts
|
||||
}
|
||||
|
||||
ps.stats.lastBlockPartHeight = bp.Height
|
||||
ps.stats.blockParts += 1
|
||||
return ps.stats.blockParts
|
||||
}
|
||||
|
||||
// BlockPartsSent returns the number of blocks for which peer has been sending
|
||||
// us block parts.
|
||||
func (ps *PeerState) BlockPartsSent() int {
|
||||
return ps.stats.blockParts
|
||||
}
|
||||
|
||||
// SetHasVote sets the given vote as known by the peer
|
||||
func (ps *PeerState) SetHasVote(vote *types.Vote) {
|
||||
ps.mtx.Lock()
|
||||
@@ -1201,11 +1262,13 @@ func (ps *PeerState) StringIndented(indent string) string {
|
||||
ps.mtx.Lock()
|
||||
defer ps.mtx.Unlock()
|
||||
return fmt.Sprintf(`PeerState{
|
||||
%s Key %v
|
||||
%s PRS %v
|
||||
%s Key %v
|
||||
%s PRS %v
|
||||
%s Stats %v
|
||||
%s}`,
|
||||
indent, ps.Peer.ID(),
|
||||
indent, ps.PeerRoundState.StringIndented(indent+" "),
|
||||
indent, ps.stats,
|
||||
indent)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,14 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/abci/example/dummy"
|
||||
"github.com/tendermint/abci/example/kvstore"
|
||||
wire "github.com/tendermint/tendermint/wire"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
p2pdummy "github.com/tendermint/tendermint/p2p/dummy"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -121,13 +124,119 @@ func TestReactorProposalHeartbeats(t *testing.T) {
|
||||
}, css)
|
||||
}
|
||||
|
||||
// Test we record block parts from other peers
|
||||
func TestReactorRecordsBlockParts(t *testing.T) {
|
||||
// create dummy peer
|
||||
peer := p2pdummy.NewPeer()
|
||||
ps := NewPeerState(peer).SetLogger(log.TestingLogger())
|
||||
peer.Set(types.PeerStateKey, ps)
|
||||
|
||||
// create reactor
|
||||
css := randConsensusNet(1, "consensus_reactor_records_block_parts_test", newMockTickerFunc(true), newPersistentKVStore)
|
||||
reactor := NewConsensusReactor(css[0], false) // so we dont start the consensus states
|
||||
reactor.SetEventBus(css[0].eventBus)
|
||||
reactor.SetLogger(log.TestingLogger())
|
||||
sw := p2p.MakeSwitch(cfg.DefaultP2PConfig(), 1, "testing", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch { return sw })
|
||||
reactor.SetSwitch(sw)
|
||||
err := reactor.Start()
|
||||
require.NoError(t, err)
|
||||
defer reactor.Stop()
|
||||
|
||||
// 1) new block part
|
||||
parts := types.NewPartSetFromData(cmn.RandBytes(100), 10)
|
||||
msg := &BlockPartMessage{
|
||||
Height: 2,
|
||||
Round: 0,
|
||||
Part: parts.GetPart(0),
|
||||
}
|
||||
bz, err := wire.MarshalBinary(struct{ ConsensusMessage }{msg})
|
||||
require.NoError(t, err)
|
||||
|
||||
reactor.Receive(DataChannel, peer, bz)
|
||||
assert.Equal(t, 1, ps.BlockPartsSent(), "number of block parts sent should have increased by 1")
|
||||
|
||||
// 2) block part with the same height, but different round
|
||||
msg.Round = 1
|
||||
|
||||
bz, err = wire.MarshalBinary(struct{ ConsensusMessage }{msg})
|
||||
require.NoError(t, err)
|
||||
|
||||
reactor.Receive(DataChannel, peer, bz)
|
||||
assert.Equal(t, 1, ps.BlockPartsSent(), "number of block parts sent should stay the same")
|
||||
|
||||
// 3) block part from earlier height
|
||||
msg.Height = 1
|
||||
msg.Round = 0
|
||||
|
||||
bz, err = wire.MarshalBinary(struct{ ConsensusMessage }{msg})
|
||||
require.NoError(t, err)
|
||||
|
||||
reactor.Receive(DataChannel, peer, bz)
|
||||
assert.Equal(t, 1, ps.BlockPartsSent(), "number of block parts sent should stay the same")
|
||||
}
|
||||
|
||||
// Test we record votes from other peers
|
||||
func TestReactorRecordsVotes(t *testing.T) {
|
||||
// create dummy peer
|
||||
peer := p2pdummy.NewPeer()
|
||||
ps := NewPeerState(peer).SetLogger(log.TestingLogger())
|
||||
peer.Set(types.PeerStateKey, ps)
|
||||
|
||||
// create reactor
|
||||
css := randConsensusNet(1, "consensus_reactor_records_votes_test", newMockTickerFunc(true), newPersistentKVStore)
|
||||
reactor := NewConsensusReactor(css[0], false) // so we dont start the consensus states
|
||||
reactor.SetEventBus(css[0].eventBus)
|
||||
reactor.SetLogger(log.TestingLogger())
|
||||
sw := p2p.MakeSwitch(cfg.DefaultP2PConfig(), 1, "testing", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch { return sw })
|
||||
reactor.SetSwitch(sw)
|
||||
err := reactor.Start()
|
||||
require.NoError(t, err)
|
||||
defer reactor.Stop()
|
||||
_, val := css[0].state.Validators.GetByIndex(0)
|
||||
|
||||
// 1) new vote
|
||||
vote := &types.Vote{
|
||||
ValidatorIndex: 0,
|
||||
ValidatorAddress: val.Address,
|
||||
Height: 2,
|
||||
Round: 0,
|
||||
Timestamp: time.Now().UTC(),
|
||||
Type: types.VoteTypePrevote,
|
||||
BlockID: types.BlockID{},
|
||||
}
|
||||
bz, err := wire.MarshalBinary(struct{ ConsensusMessage }{&VoteMessage{vote}})
|
||||
require.NoError(t, err)
|
||||
|
||||
reactor.Receive(VoteChannel, peer, bz)
|
||||
assert.Equal(t, 1, ps.VotesSent(), "number of votes sent should have increased by 1")
|
||||
|
||||
// 2) vote with the same height, but different round
|
||||
vote.Round = 1
|
||||
|
||||
bz, err = wire.MarshalBinary(struct{ ConsensusMessage }{&VoteMessage{vote}})
|
||||
require.NoError(t, err)
|
||||
|
||||
reactor.Receive(VoteChannel, peer, bz)
|
||||
assert.Equal(t, 1, ps.VotesSent(), "number of votes sent should stay the same")
|
||||
|
||||
// 3) vote from earlier height
|
||||
vote.Height = 1
|
||||
vote.Round = 0
|
||||
|
||||
bz, err = wire.MarshalBinary(struct{ ConsensusMessage }{&VoteMessage{vote}})
|
||||
require.NoError(t, err)
|
||||
|
||||
reactor.Receive(VoteChannel, peer, bz)
|
||||
assert.Equal(t, 1, ps.VotesSent(), "number of votes sent should stay the same")
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------
|
||||
// ensure we can make blocks despite cycling a validator set
|
||||
|
||||
func TestReactorVotingPowerChange(t *testing.T) {
|
||||
nVals := 4
|
||||
logger := log.TestingLogger()
|
||||
css := randConsensusNet(nVals, "consensus_voting_power_changes_test", newMockTickerFunc(true), newPersistentDummy)
|
||||
css := randConsensusNet(nVals, "consensus_voting_power_changes_test", newMockTickerFunc(true), newPersistentKVStore)
|
||||
reactors, eventChans, eventBuses := startConsensusNet(t, css, nVals)
|
||||
defer stopConsensusNet(logger, reactors, eventBuses)
|
||||
|
||||
@@ -146,7 +255,7 @@ func TestReactorVotingPowerChange(t *testing.T) {
|
||||
logger.Debug("---------------------------- Testing changing the voting power of one validator a few times")
|
||||
|
||||
val1PubKey := css[0].privValidator.GetPubKey()
|
||||
updateValidatorTx := dummy.MakeValSetChangeTx(val1PubKey.Bytes(), 25)
|
||||
updateValidatorTx := kvstore.MakeValSetChangeTx(val1PubKey.Bytes(), 25)
|
||||
previousTotalVotingPower := css[0].GetRoundState().LastValidators.TotalVotingPower()
|
||||
|
||||
waitForAndValidateBlock(t, nVals, activeVals, eventChans, css, updateValidatorTx)
|
||||
@@ -158,7 +267,7 @@ func TestReactorVotingPowerChange(t *testing.T) {
|
||||
t.Fatalf("expected voting power to change (before: %d, after: %d)", previousTotalVotingPower, css[0].GetRoundState().LastValidators.TotalVotingPower())
|
||||
}
|
||||
|
||||
updateValidatorTx = dummy.MakeValSetChangeTx(val1PubKey.Bytes(), 2)
|
||||
updateValidatorTx = kvstore.MakeValSetChangeTx(val1PubKey.Bytes(), 2)
|
||||
previousTotalVotingPower = css[0].GetRoundState().LastValidators.TotalVotingPower()
|
||||
|
||||
waitForAndValidateBlock(t, nVals, activeVals, eventChans, css, updateValidatorTx)
|
||||
@@ -170,7 +279,7 @@ func TestReactorVotingPowerChange(t *testing.T) {
|
||||
t.Fatalf("expected voting power to change (before: %d, after: %d)", previousTotalVotingPower, css[0].GetRoundState().LastValidators.TotalVotingPower())
|
||||
}
|
||||
|
||||
updateValidatorTx = dummy.MakeValSetChangeTx(val1PubKey.Bytes(), 26)
|
||||
updateValidatorTx = kvstore.MakeValSetChangeTx(val1PubKey.Bytes(), 26)
|
||||
previousTotalVotingPower = css[0].GetRoundState().LastValidators.TotalVotingPower()
|
||||
|
||||
waitForAndValidateBlock(t, nVals, activeVals, eventChans, css, updateValidatorTx)
|
||||
@@ -186,7 +295,7 @@ func TestReactorVotingPowerChange(t *testing.T) {
|
||||
func TestReactorValidatorSetChanges(t *testing.T) {
|
||||
nPeers := 7
|
||||
nVals := 4
|
||||
css := randConsensusNetWithPeers(nVals, nPeers, "consensus_val_set_changes_test", newMockTickerFunc(true), newPersistentDummy)
|
||||
css := randConsensusNetWithPeers(nVals, nPeers, "consensus_val_set_changes_test", newMockTickerFunc(true), newPersistentKVStore)
|
||||
|
||||
logger := log.TestingLogger()
|
||||
|
||||
@@ -208,7 +317,7 @@ func TestReactorValidatorSetChanges(t *testing.T) {
|
||||
logger.Info("---------------------------- Testing adding one validator")
|
||||
|
||||
newValidatorPubKey1 := css[nVals].privValidator.GetPubKey()
|
||||
newValidatorTx1 := dummy.MakeValSetChangeTx(newValidatorPubKey1.Bytes(), testMinPower)
|
||||
newValidatorTx1 := kvstore.MakeValSetChangeTx(newValidatorPubKey1.Bytes(), testMinPower)
|
||||
|
||||
// wait till everyone makes block 2
|
||||
// ensure the commit includes all validators
|
||||
@@ -234,7 +343,7 @@ func TestReactorValidatorSetChanges(t *testing.T) {
|
||||
logger.Info("---------------------------- Testing changing the voting power of one validator")
|
||||
|
||||
updateValidatorPubKey1 := css[nVals].privValidator.GetPubKey()
|
||||
updateValidatorTx1 := dummy.MakeValSetChangeTx(updateValidatorPubKey1.Bytes(), 25)
|
||||
updateValidatorTx1 := kvstore.MakeValSetChangeTx(updateValidatorPubKey1.Bytes(), 25)
|
||||
previousTotalVotingPower := css[nVals].GetRoundState().LastValidators.TotalVotingPower()
|
||||
|
||||
waitForAndValidateBlock(t, nPeers, activeVals, eventChans, css, updateValidatorTx1)
|
||||
@@ -250,10 +359,10 @@ func TestReactorValidatorSetChanges(t *testing.T) {
|
||||
logger.Info("---------------------------- Testing adding two validators at once")
|
||||
|
||||
newValidatorPubKey2 := css[nVals+1].privValidator.GetPubKey()
|
||||
newValidatorTx2 := dummy.MakeValSetChangeTx(newValidatorPubKey2.Bytes(), testMinPower)
|
||||
newValidatorTx2 := kvstore.MakeValSetChangeTx(newValidatorPubKey2.Bytes(), testMinPower)
|
||||
|
||||
newValidatorPubKey3 := css[nVals+2].privValidator.GetPubKey()
|
||||
newValidatorTx3 := dummy.MakeValSetChangeTx(newValidatorPubKey3.Bytes(), testMinPower)
|
||||
newValidatorTx3 := kvstore.MakeValSetChangeTx(newValidatorPubKey3.Bytes(), testMinPower)
|
||||
|
||||
waitForAndValidateBlock(t, nPeers, activeVals, eventChans, css, newValidatorTx2, newValidatorTx3)
|
||||
waitForAndValidateBlockWithTx(t, nPeers, activeVals, eventChans, css, newValidatorTx2, newValidatorTx3)
|
||||
@@ -265,8 +374,8 @@ func TestReactorValidatorSetChanges(t *testing.T) {
|
||||
//---------------------------------------------------------------------------
|
||||
logger.Info("---------------------------- Testing removing two validators at once")
|
||||
|
||||
removeValidatorTx2 := dummy.MakeValSetChangeTx(newValidatorPubKey2.Bytes(), 0)
|
||||
removeValidatorTx3 := dummy.MakeValSetChangeTx(newValidatorPubKey3.Bytes(), 0)
|
||||
removeValidatorTx2 := kvstore.MakeValSetChangeTx(newValidatorPubKey2.Bytes(), 0)
|
||||
removeValidatorTx3 := kvstore.MakeValSetChangeTx(newValidatorPubKey3.Bytes(), 0)
|
||||
|
||||
waitForAndValidateBlock(t, nPeers, activeVals, eventChans, css, removeValidatorTx2, removeValidatorTx3)
|
||||
waitForAndValidateBlockWithTx(t, nPeers, activeVals, eventChans, css, removeValidatorTx2, removeValidatorTx3)
|
||||
|
||||
@@ -2,6 +2,7 @@ package consensus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
@@ -190,13 +191,23 @@ type Handshaker struct {
|
||||
stateDB dbm.DB
|
||||
initialState sm.State
|
||||
store types.BlockStore
|
||||
appState json.RawMessage
|
||||
logger log.Logger
|
||||
|
||||
nBlocks int // number of blocks applied to the state
|
||||
}
|
||||
|
||||
func NewHandshaker(stateDB dbm.DB, state sm.State, store types.BlockStore) *Handshaker {
|
||||
return &Handshaker{stateDB, state, store, log.NewNopLogger(), 0}
|
||||
func NewHandshaker(stateDB dbm.DB, state sm.State,
|
||||
store types.BlockStore, appState json.RawMessage) *Handshaker {
|
||||
|
||||
return &Handshaker{
|
||||
stateDB: stateDB,
|
||||
initialState: state,
|
||||
store: store,
|
||||
appState: appState,
|
||||
logger: log.NewNopLogger(),
|
||||
nBlocks: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handshaker) SetLogger(l log.Logger) {
|
||||
@@ -249,9 +260,12 @@ func (h *Handshaker) ReplayBlocks(state sm.State, appHash []byte, appBlockHeight
|
||||
// If appBlockHeight == 0 it means that we are at genesis and hence should send InitChain
|
||||
if appBlockHeight == 0 {
|
||||
validators := types.TM2PB.Validators(state.Validators)
|
||||
// TODO: get the genesis bytes (https://github.com/tendermint/tendermint/issues/1224)
|
||||
var genesisBytes []byte
|
||||
if _, err := proxyApp.Consensus().InitChainSync(abci.RequestInitChain{validators, genesisBytes}); err != nil {
|
||||
req := abci.RequestInitChain{
|
||||
Validators: validators,
|
||||
AppStateBytes: h.appState,
|
||||
}
|
||||
_, err := proxyApp.Consensus().InitChainSync(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,14 +287,19 @@ func newConsensusStateForReplay(config cfg.BaseConfig, csConfig *cfg.ConsensusCo
|
||||
|
||||
// Get State
|
||||
stateDB := dbm.NewDB("state", dbType, config.DBDir())
|
||||
state, err := sm.MakeGenesisStateFromFile(config.GenesisFile())
|
||||
gdoc, err := sm.MakeGenesisDocFromFile(config.GenesisFile())
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
state, err := sm.MakeGenesisState(gdoc)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
|
||||
// Create proxyAppConn connection (consensus, mempool, query)
|
||||
clientCreator := proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir())
|
||||
proxyApp := proxy.NewAppConns(clientCreator, NewHandshaker(stateDB, state, blockStore))
|
||||
proxyApp := proxy.NewAppConns(clientCreator,
|
||||
NewHandshaker(stateDB, state, blockStore, gdoc.AppState()))
|
||||
err = proxyApp.Start()
|
||||
if err != nil {
|
||||
cmn.Exit(cmn.Fmt("Error starting proxy app conns: %v", err))
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/abci/example/dummy"
|
||||
"github.com/tendermint/abci/example/kvstore"
|
||||
abci "github.com/tendermint/abci/types"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
@@ -55,7 +55,7 @@ func startNewConsensusStateAndWaitForBlock(t *testing.T, lastBlockHeight int64,
|
||||
logger := log.TestingLogger()
|
||||
state, _ := sm.LoadStateFromDBOrGenesisFile(stateDB, consensusReplayConfig.GenesisFile())
|
||||
privValidator := loadPrivValidator(consensusReplayConfig)
|
||||
cs := newConsensusStateWithConfigAndBlockStore(consensusReplayConfig, state, privValidator, dummy.NewDummyApplication(), blockDB)
|
||||
cs := newConsensusStateWithConfigAndBlockStore(consensusReplayConfig, state, privValidator, kvstore.NewKVStoreApplication(), blockDB)
|
||||
cs.SetLogger(logger)
|
||||
|
||||
bytes, _ := ioutil.ReadFile(cs.config.WalFile())
|
||||
@@ -141,7 +141,7 @@ LOOP:
|
||||
state, _ := sm.MakeGenesisStateFromFile(consensusReplayConfig.GenesisFile())
|
||||
privValidator := loadPrivValidator(consensusReplayConfig)
|
||||
blockDB := dbm.NewMemDB()
|
||||
cs := newConsensusStateWithConfigAndBlockStore(consensusReplayConfig, state, privValidator, dummy.NewDummyApplication(), blockDB)
|
||||
cs := newConsensusStateWithConfigAndBlockStore(consensusReplayConfig, state, privValidator, kvstore.NewKVStoreApplication(), blockDB)
|
||||
cs.SetLogger(logger)
|
||||
|
||||
// start sending transactions
|
||||
@@ -351,8 +351,8 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) {
|
||||
latestAppHash := state.AppHash
|
||||
|
||||
// make a new client creator
|
||||
dummyApp := dummy.NewPersistentDummyApplication(path.Join(config.DBDir(), "2"))
|
||||
clientCreator2 := proxy.NewLocalClientCreator(dummyApp)
|
||||
kvstoreApp := kvstore.NewPersistentKVStoreApplication(path.Join(config.DBDir(), "2"))
|
||||
clientCreator2 := proxy.NewLocalClientCreator(kvstoreApp)
|
||||
if nBlocks > 0 {
|
||||
// run nBlocks against a new client to build up the app state.
|
||||
// use a throwaway tendermint state
|
||||
@@ -362,7 +362,7 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) {
|
||||
}
|
||||
|
||||
// now start the app using the handshake - it should sync
|
||||
handshaker := NewHandshaker(stateDB, state, store)
|
||||
handshaker := NewHandshaker(stateDB, state, store, nil)
|
||||
proxyApp := proxy.NewAppConns(clientCreator2, handshaker)
|
||||
if err := proxyApp.Start(); err != nil {
|
||||
t.Fatalf("Error starting proxy app connections: %v", err)
|
||||
@@ -412,9 +412,9 @@ func buildAppStateFromChain(proxyApp proxy.AppConns, stateDB dbm.DB,
|
||||
}
|
||||
defer proxyApp.Stop()
|
||||
|
||||
validators := types.TM2PB.Validators(state.Validators)
|
||||
// TODO: get the genesis bytes (https://github.com/tendermint/tendermint/issues/1224)
|
||||
var genesisBytes []byte
|
||||
validators := types.TM2PB.Validators(state.Validators)
|
||||
if _, err := proxyApp.Consensus().InitChainSync(abci.RequestInitChain{validators, genesisBytes}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -432,7 +432,7 @@ func buildAppStateFromChain(proxyApp proxy.AppConns, stateDB dbm.DB,
|
||||
}
|
||||
|
||||
if mode == 2 {
|
||||
// update the dummy height and apphash
|
||||
// update the kvstore height and apphash
|
||||
// as if we ran commit but not
|
||||
state = applyBlock(stateDB, state, chain[nBlocks-1], proxyApp)
|
||||
}
|
||||
@@ -442,16 +442,16 @@ func buildAppStateFromChain(proxyApp proxy.AppConns, stateDB dbm.DB,
|
||||
|
||||
func buildTMStateFromChain(config *cfg.Config, stateDB dbm.DB, state sm.State, chain []*types.Block, mode uint) sm.State {
|
||||
// run the whole chain against this client to build up the tendermint state
|
||||
clientCreator := proxy.NewLocalClientCreator(dummy.NewPersistentDummyApplication(path.Join(config.DBDir(), "1")))
|
||||
clientCreator := proxy.NewLocalClientCreator(kvstore.NewPersistentKVStoreApplication(path.Join(config.DBDir(), "1")))
|
||||
proxyApp := proxy.NewAppConns(clientCreator, nil) // sm.NewHandshaker(config, state, store, ReplayLastBlock))
|
||||
if err := proxyApp.Start(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer proxyApp.Stop()
|
||||
|
||||
validators := types.TM2PB.Validators(state.Validators)
|
||||
// TODO: get the genesis bytes (https://github.com/tendermint/tendermint/issues/1224)
|
||||
var genesisBytes []byte
|
||||
validators := types.TM2PB.Validators(state.Validators)
|
||||
if _, err := proxyApp.Consensus().InitChainSync(abci.RequestInitChain{validators, genesisBytes}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -477,6 +477,9 @@ func (cs *ConsensusState) updateToState(state sm.State) {
|
||||
cs.LockedRound = 0
|
||||
cs.LockedBlock = nil
|
||||
cs.LockedBlockParts = nil
|
||||
cs.ValidRound = 0
|
||||
cs.ValidBlock = nil
|
||||
cs.ValidBlockParts = nil
|
||||
cs.Votes = cstypes.NewHeightVoteSet(state.ChainID, height, validators)
|
||||
cs.CommitRound = -1
|
||||
cs.LastCommit = lastPrecommits
|
||||
@@ -580,6 +583,10 @@ func (cs *ConsensusState) handleMsg(mi msgInfo) {
|
||||
err := cs.tryAddVote(msg.Vote, peerID)
|
||||
if err == ErrAddingVote {
|
||||
// TODO: punish peer
|
||||
// We probably don't want to stop the peer here. The vote does not
|
||||
// necessarily comes from a malicious peer but can be just broadcasted by
|
||||
// a typical peer.
|
||||
// https://github.com/tendermint/tendermint/issues/1281
|
||||
}
|
||||
|
||||
// NOTE: the vote is broadcast to peers by the reactor listening
|
||||
@@ -798,6 +805,9 @@ func (cs *ConsensusState) defaultDecideProposal(height int64, round int) {
|
||||
if cs.LockedBlock != nil {
|
||||
// If we're locked onto a block, just choose that.
|
||||
block, blockParts = cs.LockedBlock, cs.LockedBlockParts
|
||||
} else if cs.ValidBlock != nil {
|
||||
// If there is valid block, choose that.
|
||||
block, blockParts = cs.ValidBlock, cs.ValidBlockParts
|
||||
} else {
|
||||
// Create a new proposal block from state/txs from the mempool.
|
||||
block, blockParts = cs.createProposalBlock()
|
||||
@@ -1263,7 +1273,7 @@ func (cs *ConsensusState) defaultSetProposal(proposal *types.Proposal) error {
|
||||
}
|
||||
|
||||
// Verify signature
|
||||
if !cs.Validators.GetProposer().PubKey.VerifyBytes(types.SignBytes(cs.state.ChainID, proposal), proposal.Signature) {
|
||||
if !cs.Validators.GetProposer().PubKey.VerifyBytes(proposal.SignBytes(cs.state.ChainID), proposal.Signature) {
|
||||
return ErrInvalidProposalSignature
|
||||
}
|
||||
|
||||
@@ -1349,99 +1359,115 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool,
|
||||
return added, ErrVoteHeightMismatch
|
||||
}
|
||||
added, err = cs.LastCommit.AddVote(vote)
|
||||
if added {
|
||||
cs.Logger.Info(cmn.Fmt("Added to lastPrecommits: %v", cs.LastCommit.StringShort()))
|
||||
cs.eventBus.PublishEventVote(types.EventDataVote{vote})
|
||||
if !added {
|
||||
return added, err
|
||||
}
|
||||
|
||||
// if we can skip timeoutCommit and have all the votes now,
|
||||
if cs.config.SkipTimeoutCommit && cs.LastCommit.HasAll() {
|
||||
// go straight to new round (skip timeout commit)
|
||||
// cs.scheduleTimeout(time.Duration(0), cs.Height, 0, cstypes.RoundStepNewHeight)
|
||||
cs.enterNewRound(cs.Height, 0)
|
||||
}
|
||||
cs.Logger.Info(cmn.Fmt("Added to lastPrecommits: %v", cs.LastCommit.StringShort()))
|
||||
cs.eventBus.PublishEventVote(types.EventDataVote{vote})
|
||||
|
||||
// if we can skip timeoutCommit and have all the votes now,
|
||||
if cs.config.SkipTimeoutCommit && cs.LastCommit.HasAll() {
|
||||
// go straight to new round (skip timeout commit)
|
||||
// cs.scheduleTimeout(time.Duration(0), cs.Height, 0, cstypes.RoundStepNewHeight)
|
||||
cs.enterNewRound(cs.Height, 0)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// A prevote/precommit for this height?
|
||||
if vote.Height == cs.Height {
|
||||
height := cs.Height
|
||||
added, err = cs.Votes.AddVote(vote, peerID)
|
||||
if added {
|
||||
cs.eventBus.PublishEventVote(types.EventDataVote{vote})
|
||||
// Height mismatch is ignored.
|
||||
// Not necessarily a bad peer, but not favourable behaviour.
|
||||
if vote.Height != cs.Height {
|
||||
err = ErrVoteHeightMismatch
|
||||
cs.Logger.Info("Vote ignored and not added", "voteHeight", vote.Height, "csHeight", cs.Height, "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch vote.Type {
|
||||
case types.VoteTypePrevote:
|
||||
prevotes := cs.Votes.Prevotes(vote.Round)
|
||||
cs.Logger.Info("Added to prevote", "vote", vote, "prevotes", prevotes.StringShort())
|
||||
// First, unlock if prevotes is a valid POL.
|
||||
// >> lockRound < POLRound <= unlockOrChangeLockRound (see spec)
|
||||
// NOTE: If (lockRound < POLRound) but !(POLRound <= unlockOrChangeLockRound),
|
||||
// we'll still enterNewRound(H,vote.R) and enterPrecommit(H,vote.R) to process it
|
||||
// there.
|
||||
if (cs.LockedBlock != nil) && (cs.LockedRound < vote.Round) && (vote.Round <= cs.Round) {
|
||||
blockID, ok := prevotes.TwoThirdsMajority()
|
||||
if ok && !cs.LockedBlock.HashesTo(blockID.Hash) {
|
||||
cs.Logger.Info("Unlocking because of POL.", "lockedRound", cs.LockedRound, "POLRound", vote.Round)
|
||||
cs.LockedRound = 0
|
||||
cs.LockedBlock = nil
|
||||
cs.LockedBlockParts = nil
|
||||
cs.eventBus.PublishEventUnlock(cs.RoundStateEvent())
|
||||
}
|
||||
}
|
||||
if cs.Round <= vote.Round && prevotes.HasTwoThirdsAny() {
|
||||
// Round-skip over to PrevoteWait or goto Precommit.
|
||||
cs.enterNewRound(height, vote.Round) // if the vote is ahead of us
|
||||
if prevotes.HasTwoThirdsMajority() {
|
||||
cs.enterPrecommit(height, vote.Round)
|
||||
} else {
|
||||
cs.enterPrevote(height, vote.Round) // if the vote is ahead of us
|
||||
cs.enterPrevoteWait(height, vote.Round)
|
||||
}
|
||||
} else if cs.Proposal != nil && 0 <= cs.Proposal.POLRound && cs.Proposal.POLRound == vote.Round {
|
||||
// If the proposal is now complete, enter prevote of cs.Round.
|
||||
if cs.isProposalComplete() {
|
||||
cs.enterPrevote(height, cs.Round)
|
||||
}
|
||||
}
|
||||
case types.VoteTypePrecommit:
|
||||
precommits := cs.Votes.Precommits(vote.Round)
|
||||
cs.Logger.Info("Added to precommit", "vote", vote, "precommits", precommits.StringShort())
|
||||
blockID, ok := precommits.TwoThirdsMajority()
|
||||
if ok {
|
||||
if len(blockID.Hash) == 0 {
|
||||
cs.enterNewRound(height, vote.Round+1)
|
||||
} else {
|
||||
cs.enterNewRound(height, vote.Round)
|
||||
cs.enterPrecommit(height, vote.Round)
|
||||
cs.enterCommit(height, vote.Round)
|
||||
|
||||
if cs.config.SkipTimeoutCommit && precommits.HasAll() {
|
||||
// if we have all the votes now,
|
||||
// go straight to new round (skip timeout commit)
|
||||
// cs.scheduleTimeout(time.Duration(0), cs.Height, 0, cstypes.RoundStepNewHeight)
|
||||
cs.enterNewRound(cs.Height, 0)
|
||||
}
|
||||
|
||||
}
|
||||
} else if cs.Round <= vote.Round && precommits.HasTwoThirdsAny() {
|
||||
cs.enterNewRound(height, vote.Round)
|
||||
cs.enterPrecommit(height, vote.Round)
|
||||
cs.enterPrecommitWait(height, vote.Round)
|
||||
}
|
||||
default:
|
||||
cmn.PanicSanity(cmn.Fmt("Unexpected vote type %X", vote.Type)) // Should not happen.
|
||||
}
|
||||
}
|
||||
height := cs.Height
|
||||
added, err = cs.Votes.AddVote(vote, peerID)
|
||||
if !added {
|
||||
// Either duplicate, or error upon cs.Votes.AddByIndex()
|
||||
return
|
||||
} else {
|
||||
err = ErrVoteHeightMismatch
|
||||
}
|
||||
|
||||
// Height mismatch, bad peer?
|
||||
cs.Logger.Info("Vote ignored and not added", "voteHeight", vote.Height, "csHeight", cs.Height, "err", err)
|
||||
cs.eventBus.PublishEventVote(types.EventDataVote{vote})
|
||||
|
||||
switch vote.Type {
|
||||
case types.VoteTypePrevote:
|
||||
prevotes := cs.Votes.Prevotes(vote.Round)
|
||||
cs.Logger.Info("Added to prevote", "vote", vote, "prevotes", prevotes.StringShort())
|
||||
blockID, ok := prevotes.TwoThirdsMajority()
|
||||
// First, unlock if prevotes is a valid POL.
|
||||
// >> lockRound < POLRound <= unlockOrChangeLockRound (see spec)
|
||||
// NOTE: If (lockRound < POLRound) but !(POLRound <= unlockOrChangeLockRound),
|
||||
// we'll still enterNewRound(H,vote.R) and enterPrecommit(H,vote.R) to process it
|
||||
// there.
|
||||
if (cs.LockedBlock != nil) && (cs.LockedRound < vote.Round) && (vote.Round <= cs.Round) {
|
||||
if ok && !cs.LockedBlock.HashesTo(blockID.Hash) {
|
||||
cs.Logger.Info("Unlocking because of POL.", "lockedRound", cs.LockedRound, "POLRound", vote.Round)
|
||||
cs.LockedRound = 0
|
||||
cs.LockedBlock = nil
|
||||
cs.LockedBlockParts = nil
|
||||
cs.eventBus.PublishEventUnlock(cs.RoundStateEvent())
|
||||
}
|
||||
}
|
||||
// Update ValidBlock
|
||||
if ok && !blockID.IsZero() && !cs.ValidBlock.HashesTo(blockID.Hash) && vote.Round > cs.ValidRound {
|
||||
// update valid value
|
||||
if cs.ProposalBlock.HashesTo(blockID.Hash) {
|
||||
cs.ValidRound = vote.Round
|
||||
cs.ValidBlock = cs.ProposalBlock
|
||||
cs.ValidBlockParts = cs.ProposalBlockParts
|
||||
}
|
||||
//TODO: We might want to update ValidBlock also in case we don't have that block yet,
|
||||
// and obtain the required block using gossiping
|
||||
}
|
||||
|
||||
if cs.Round <= vote.Round && prevotes.HasTwoThirdsAny() {
|
||||
// Round-skip over to PrevoteWait or goto Precommit.
|
||||
cs.enterNewRound(height, vote.Round) // if the vote is ahead of us
|
||||
if prevotes.HasTwoThirdsMajority() {
|
||||
cs.enterPrecommit(height, vote.Round)
|
||||
} else {
|
||||
cs.enterPrevote(height, vote.Round) // if the vote is ahead of us
|
||||
cs.enterPrevoteWait(height, vote.Round)
|
||||
}
|
||||
} else if cs.Proposal != nil && 0 <= cs.Proposal.POLRound && cs.Proposal.POLRound == vote.Round {
|
||||
// If the proposal is now complete, enter prevote of cs.Round.
|
||||
if cs.isProposalComplete() {
|
||||
cs.enterPrevote(height, cs.Round)
|
||||
}
|
||||
}
|
||||
case types.VoteTypePrecommit:
|
||||
precommits := cs.Votes.Precommits(vote.Round)
|
||||
cs.Logger.Info("Added to precommit", "vote", vote, "precommits", precommits.StringShort())
|
||||
blockID, ok := precommits.TwoThirdsMajority()
|
||||
if ok {
|
||||
if len(blockID.Hash) == 0 {
|
||||
cs.enterNewRound(height, vote.Round+1)
|
||||
} else {
|
||||
cs.enterNewRound(height, vote.Round)
|
||||
cs.enterPrecommit(height, vote.Round)
|
||||
cs.enterCommit(height, vote.Round)
|
||||
|
||||
if cs.config.SkipTimeoutCommit && precommits.HasAll() {
|
||||
// if we have all the votes now,
|
||||
// go straight to new round (skip timeout commit)
|
||||
// cs.scheduleTimeout(time.Duration(0), cs.Height, 0, cstypes.RoundStepNewHeight)
|
||||
cs.enterNewRound(cs.Height, 0)
|
||||
}
|
||||
|
||||
}
|
||||
} else if cs.Round <= vote.Round && precommits.HasTwoThirdsAny() {
|
||||
cs.enterNewRound(height, vote.Round)
|
||||
cs.enterPrecommit(height, vote.Round)
|
||||
cs.enterPrecommitWait(height, vote.Round)
|
||||
}
|
||||
default:
|
||||
panic(cmn.Fmt("Unexpected vote type %X", vote.Type)) // go-wire should prevent this.
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -15,6 +16,10 @@ type RoundVoteSet struct {
|
||||
Precommits *types.VoteSet
|
||||
}
|
||||
|
||||
var (
|
||||
GotVoteFromUnwantedRoundError = errors.New("Peer has sent a vote that does not match our round for more than one round")
|
||||
)
|
||||
|
||||
/*
|
||||
Keeps track of all VoteSets from round 0 to round 'round'.
|
||||
|
||||
@@ -117,10 +122,8 @@ func (hvs *HeightVoteSet) AddVote(vote *types.Vote, peerID p2p.ID) (added bool,
|
||||
voteSet = hvs.getVoteSet(vote.Round, vote.Type)
|
||||
hvs.peerCatchupRounds[peerID] = append(rndz, vote.Round)
|
||||
} else {
|
||||
// Peer has sent a vote that does not match our round,
|
||||
// for more than one round. Bad peer!
|
||||
// TODO punish peer.
|
||||
// log.Warn("Deal with peer giving votes from unwanted rounds")
|
||||
// punish peer
|
||||
err = GotVoteFromUnwantedRoundError
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -218,5 +221,5 @@ func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ byte, peerID p2p.ID, blo
|
||||
if voteSet == nil {
|
||||
return nil // something we don't know about yet
|
||||
}
|
||||
return voteSet.SetPeerMaj23(peerID, blockID)
|
||||
return voteSet.SetPeerMaj23(types.P2PID(peerID), blockID)
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ func TestPeerCatchupRounds(t *testing.T) {
|
||||
|
||||
vote1001_0 := makeVoteHR(t, 1, 1001, privVals, 0)
|
||||
added, err = hvs.AddVote(vote1001_0, "peer1")
|
||||
if err != nil {
|
||||
t.Error("AddVote error", err)
|
||||
if err != GotVoteFromUnwantedRoundError {
|
||||
t.Errorf("Expected GotVoteFromUnwantedRoundError, but got %v", err)
|
||||
}
|
||||
if added {
|
||||
t.Error("Expected to *not* add vote from peer, too many catchup rounds.")
|
||||
|
||||
@@ -70,6 +70,9 @@ type RoundState struct {
|
||||
LockedRound int
|
||||
LockedBlock *types.Block
|
||||
LockedBlockParts *types.PartSet
|
||||
ValidRound int
|
||||
ValidBlock *types.Block
|
||||
ValidBlockParts *types.PartSet
|
||||
Votes *HeightVoteSet
|
||||
CommitRound int //
|
||||
LastCommit *types.VoteSet // Last precommits at Height-1
|
||||
@@ -106,6 +109,8 @@ func (rs *RoundState) StringIndented(indent string) string {
|
||||
%s ProposalBlock: %v %v
|
||||
%s LockedRound: %v
|
||||
%s LockedBlock: %v %v
|
||||
%s ValidRound: %v
|
||||
%s ValidBlock: %v %v
|
||||
%s Votes: %v
|
||||
%s LastCommit: %v
|
||||
%s LastValidators:%v
|
||||
@@ -118,6 +123,8 @@ func (rs *RoundState) StringIndented(indent string) string {
|
||||
indent, rs.ProposalBlockParts.StringShort(), rs.ProposalBlock.StringShort(),
|
||||
indent, rs.LockedRound,
|
||||
indent, rs.LockedBlockParts.StringShort(), rs.LockedBlock.StringShort(),
|
||||
indent, rs.ValidRound,
|
||||
indent, rs.ValidBlockParts.StringShort(), rs.ValidBlock.StringShort(),
|
||||
indent, rs.Votes.StringIndented(indent+" "),
|
||||
indent, rs.LastCommit.StringShort(),
|
||||
indent, rs.LastValidators.StringIndented(indent+" "),
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tendermint/abci/example/dummy"
|
||||
"github.com/tendermint/abci/example/kvstore"
|
||||
bc "github.com/tendermint/tendermint/blockchain"
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/proxy"
|
||||
@@ -25,13 +25,13 @@ import (
|
||||
|
||||
// WALWithNBlocks generates a consensus WAL. It does this by spining up a
|
||||
// stripped down version of node (proxy app, event bus, consensus state) with a
|
||||
// persistent dummy application and special consensus wal instance
|
||||
// persistent kvstore application and special consensus wal instance
|
||||
// (byteBufferWAL) and waits until numBlocks are created. Then it returns a WAL
|
||||
// content.
|
||||
func WALWithNBlocks(numBlocks int) (data []byte, err error) {
|
||||
config := getConfig()
|
||||
|
||||
app := dummy.NewPersistentDummyApplication(filepath.Join(config.DBDir(), "wal_generator"))
|
||||
app := kvstore.NewPersistentKVStoreApplication(filepath.Join(config.DBDir(), "wal_generator"))
|
||||
|
||||
logger := log.TestingLogger().With("wal_generator", "wal_generator")
|
||||
logger.Info("generating WAL (last height msg excluded)", "numBlocks", numBlocks)
|
||||
@@ -52,7 +52,7 @@ func WALWithNBlocks(numBlocks int) (data []byte, err error) {
|
||||
return nil, errors.Wrap(err, "failed to make genesis state")
|
||||
}
|
||||
blockStore := bc.NewBlockStore(blockStoreDB)
|
||||
handshaker := NewHandshaker(stateDB, state, blockStore)
|
||||
handshaker := NewHandshaker(stateDB, state, blockStore, genDoc.AppState())
|
||||
proxyApp := proxy.NewAppConns(proxy.NewLocalClientCreator(app), handshaker)
|
||||
proxyApp.SetLogger(logger.With("module", "proxy"))
|
||||
if err := proxyApp.Start(); err != nil {
|
||||
|
||||
@@ -16,15 +16,15 @@ Next, install the ``abci-cli`` tool and example applications:
|
||||
|
||||
go get -u github.com/tendermint/abci/cmd/abci-cli
|
||||
|
||||
If this fails, you may need to use ``glide`` to get vendored
|
||||
If this fails, you may need to use `dep <https://github.com/golang/dep>`__ to get vendored
|
||||
dependencies:
|
||||
|
||||
::
|
||||
|
||||
go get github.com/Masterminds/glide
|
||||
cd $GOPATH/src/github.com/tendermint/abci
|
||||
glide install
|
||||
go install ./cmd/abci-cli
|
||||
make get_tools
|
||||
make get_vendor_deps
|
||||
make install
|
||||
|
||||
Now run ``abci-cli`` to see the list of commands:
|
||||
|
||||
@@ -40,7 +40,7 @@ Now run ``abci-cli`` to see the list of commands:
|
||||
console Start an interactive abci console for multiple commands
|
||||
counter ABCI demo example
|
||||
deliver_tx Deliver a new tx to the application
|
||||
dummy ABCI demo example
|
||||
kvstore ABCI demo example
|
||||
echo Have the application echo a message
|
||||
help Help about any command
|
||||
info Get some info about the application
|
||||
@@ -56,8 +56,8 @@ Now run ``abci-cli`` to see the list of commands:
|
||||
Use "abci-cli [command] --help" for more information about a command.
|
||||
|
||||
|
||||
Dummy - First Example
|
||||
---------------------
|
||||
KVStore - First Example
|
||||
-----------------------
|
||||
|
||||
The ``abci-cli`` tool lets us send ABCI messages to our application, to
|
||||
help build and debug them.
|
||||
@@ -66,8 +66,8 @@ The most important messages are ``deliver_tx``, ``check_tx``, and
|
||||
``commit``, but there are others for convenience, configuration, and
|
||||
information purposes.
|
||||
|
||||
We'll start a dummy application, which was installed at the same time as
|
||||
``abci-cli`` above. The dummy just stores transactions in a merkle tree.
|
||||
We'll start a kvstore application, which was installed at the same time as
|
||||
``abci-cli`` above. The kvstore just stores transactions in a merkle tree.
|
||||
|
||||
Its code can be found `here <https://github.com/tendermint/abci/blob/master/cmd/abci-cli/abci-cli.go>`__ and looks like:
|
||||
|
||||
@@ -75,20 +75,20 @@ Its code can be found `here <https://github.com/tendermint/abci/blob/master/cmd/
|
||||
|
||||
.. container:: header
|
||||
|
||||
**Show/Hide Dummy Example**
|
||||
**Show/Hide KVStore Example**
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
func cmdDummy(cmd *cobra.Command, args []string) error {
|
||||
func cmdKVStore(cmd *cobra.Command, args []string) error {
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||
|
||||
// Create the application - in memory or persisted to disk
|
||||
var app types.Application
|
||||
if flagPersist == "" {
|
||||
app = dummy.NewDummyApplication()
|
||||
app = kvstore.NewKVStoreApplication()
|
||||
} else {
|
||||
app = dummy.NewPersistentDummyApplication(flagPersist)
|
||||
app.(*dummy.PersistentDummyApplication).SetLogger(logger.With("module", "dummy"))
|
||||
app = kvstore.NewPersistentKVStoreApplication(flagPersist)
|
||||
app.(*kvstore.PersistentKVStoreApplication).SetLogger(logger.With("module", "kvstore"))
|
||||
}
|
||||
|
||||
// Start the listener
|
||||
@@ -113,7 +113,7 @@ Start by running:
|
||||
|
||||
::
|
||||
|
||||
abci-cli dummy
|
||||
abci-cli kvstore
|
||||
|
||||
And in another terminal, run
|
||||
|
||||
@@ -229,7 +229,7 @@ Counter - Another Example
|
||||
Now that we've got the hang of it, let's try another application, the
|
||||
"counter" app.
|
||||
|
||||
Like the dummy app, its code can be found `here <https://github.com/tendermint/abci/blob/master/cmd/abci-cli/abci-cli.go>`__ and looks like:
|
||||
Like the kvstore app, its code can be found `here <https://github.com/tendermint/abci/blob/master/cmd/abci-cli/abci-cli.go>`__ and looks like:
|
||||
|
||||
.. container:: toggle
|
||||
|
||||
@@ -288,7 +288,7 @@ other peers.
|
||||
In this instance of the counter app, ``check_tx`` only allows
|
||||
transactions whose integer is greater than the last committed one.
|
||||
|
||||
Let's kill the console and the dummy application, and start the counter
|
||||
Let's kill the console and the kvstore application, and start the counter
|
||||
app:
|
||||
|
||||
::
|
||||
@@ -328,7 +328,7 @@ In another window, start the ``abci-cli console``:
|
||||
-> data.hex: 0x7B22686173686573223A302C22747873223A327D
|
||||
|
||||
This is a very simple application, but between ``counter`` and
|
||||
``dummy``, its easy to see how you can build out arbitrary application
|
||||
``kvstore``, its easy to see how you can build out arbitrary application
|
||||
states on top of the ABCI. `Hyperledger's
|
||||
Burrow <https://github.com/hyperledger/burrow>`__ also runs atop ABCI,
|
||||
bringing with it Ethereum-like accounts, the Ethereum virtual-machine,
|
||||
|
||||
@@ -142,10 +142,10 @@ It is unlikely that you will need to implement a client. For details of
|
||||
our client, see
|
||||
`here <https://github.com/tendermint/abci/tree/master/client>`__.
|
||||
|
||||
Most of the examples below are from `dummy application
|
||||
<https://github.com/tendermint/abci/blob/master/example/dummy/dummy.go>`__,
|
||||
which is a part of the abci repo. `persistent_dummy application
|
||||
<https://github.com/tendermint/abci/blob/master/example/dummy/persistent_dummy.go>`__
|
||||
Most of the examples below are from `kvstore application
|
||||
<https://github.com/tendermint/abci/blob/master/example/kvstore/kvstore.go>`__,
|
||||
which is a part of the abci repo. `persistent_kvstore application
|
||||
<https://github.com/tendermint/abci/blob/master/example/kvstore/persistent_kvstore.go>`__
|
||||
is used to show ``BeginBlock``, ``EndBlock`` and ``InitChain``
|
||||
example implementations.
|
||||
|
||||
@@ -202,7 +202,7 @@ mempool state.
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
func (app *DummyApplication) CheckTx(tx []byte) types.Result {
|
||||
func (app *KVStoreApplication) CheckTx(tx []byte) types.Result {
|
||||
return types.OK
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@ merkle root of the data returned by the DeliverTx requests, or both.
|
||||
.. code-block:: go
|
||||
|
||||
// tx is either "key=value" or just arbitrary bytes
|
||||
func (app *DummyApplication) DeliverTx(tx []byte) types.Result {
|
||||
func (app *KVStoreApplication) DeliverTx(tx []byte) types.Result {
|
||||
parts := strings.Split(string(tx), "=")
|
||||
if len(parts) == 2 {
|
||||
app.state.Set([]byte(parts[0]), []byte(parts[1]))
|
||||
@@ -327,7 +327,7 @@ job of the `Handshake <#handshake>`__.
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
func (app *DummyApplication) Commit() types.Result {
|
||||
func (app *KVStoreApplication) Commit() types.Result {
|
||||
hash := app.state.Hash()
|
||||
return types.NewResultOK(hash, "")
|
||||
}
|
||||
@@ -369,7 +369,7 @@ pick up from when it restarts. See information on the Handshake, below.
|
||||
.. code-block:: go
|
||||
|
||||
// Track the block hash and header information
|
||||
func (app *PersistentDummyApplication) BeginBlock(params types.RequestBeginBlock) {
|
||||
func (app *PersistentKVStoreApplication) BeginBlock(params types.RequestBeginBlock) {
|
||||
// update latest block info
|
||||
app.blockHeader = params.Header
|
||||
|
||||
@@ -423,7 +423,7 @@ for details on how it tracks validators.
|
||||
.. code-block:: go
|
||||
|
||||
// Update the validator set
|
||||
func (app *PersistentDummyApplication) EndBlock(req types.RequestEndBlock) types.ResponseEndBlock {
|
||||
func (app *PersistentKVStoreApplication) EndBlock(req types.RequestEndBlock) types.ResponseEndBlock {
|
||||
return types.ResponseEndBlock{ValidatorUpdates: app.ValUpdates}
|
||||
}
|
||||
|
||||
@@ -477,7 +477,7 @@ Note: these query formats are subject to change!
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
func (app *DummyApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) {
|
||||
func (app *KVStoreApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) {
|
||||
if reqQuery.Prove {
|
||||
value, proof, exists := app.state.Proof(reqQuery.Data)
|
||||
resQuery.Index = -1 // TODO make Proof return index
|
||||
@@ -561,7 +561,7 @@ all blocks.
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
func (app *DummyApplication) Info(req types.RequestInfo) (resInfo types.ResponseInfo) {
|
||||
func (app *KVStoreApplication) Info(req types.RequestInfo) (resInfo types.ResponseInfo) {
|
||||
return types.ResponseInfo{Data: cmn.Fmt("{\"size\":%v}", app.state.Size())}
|
||||
}
|
||||
|
||||
@@ -595,7 +595,7 @@ consensus params.
|
||||
.. code-block:: go
|
||||
|
||||
// Save the validators in the merkle tree
|
||||
func (app *PersistentDummyApplication) InitChain(params types.RequestInitChain) {
|
||||
func (app *PersistentKVStoreApplication) InitChain(params types.RequestInitChain) {
|
||||
for _, v := range params.Validators {
|
||||
r := app.updateValidator(v)
|
||||
if r.IsErr() {
|
||||
|
||||
128
docs/architecture/adr-008-priv-validator.md
Normal file
128
docs/architecture/adr-008-priv-validator.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# ADR 008: PrivValidator
|
||||
|
||||
## Context
|
||||
|
||||
The current PrivValidator is monolithic and isn't easily reuseable by alternative signers.
|
||||
|
||||
For instance, see https://github.com/tendermint/tendermint/issues/673
|
||||
|
||||
The goal is to have a clean PrivValidator interface like:
|
||||
|
||||
```
|
||||
type PrivValidator interface {
|
||||
Address() data.Bytes
|
||||
PubKey() crypto.PubKey
|
||||
|
||||
SignVote(chainID string, vote *types.Vote) error
|
||||
SignProposal(chainID string, proposal *types.Proposal) error
|
||||
SignHeartbeat(chainID string, heartbeat *types.Heartbeat) error
|
||||
}
|
||||
```
|
||||
|
||||
It should also be easy to re-use the LastSignedInfo logic to avoid double signing.
|
||||
|
||||
## Decision
|
||||
|
||||
Tendermint node's should support only two in-process PrivValidator implementations:
|
||||
|
||||
- PrivValidatorUnencrypted uses an unencrypted private key in a "priv_validator.json" file - no configuration required (just `tendermint init`).
|
||||
- PrivValidatorSocket uses a socket to send signing requests to another process - user is responsible for starting that process themselves.
|
||||
|
||||
The PrivValidatorSocket address can be provided via flags at the command line -
|
||||
doing so will cause Tendermint to ignore any "priv_validator.json" file and to listen
|
||||
on the given address for incoming connections from an external priv_validator process.
|
||||
It will halt any operation until at least one external process succesfully
|
||||
connected.
|
||||
|
||||
The external priv_validator process will dial the address to connect to Tendermint,
|
||||
and then Tendermint will send requests on the ensuing connection to sign votes and proposals.
|
||||
Thus the external process initiates the connection, but the Tendermint process makes all requests.
|
||||
In a later stage we're going to support multiple validators for fault
|
||||
tolerance. To prevent double signing they need to be synced, which is deferred
|
||||
to an external solution (see #1185).
|
||||
|
||||
In addition, Tendermint will provide implementations that can be run in that external process.
|
||||
These include:
|
||||
|
||||
- PrivValidatorEncrypted uses an encrypted private key persisted to disk - user must enter password to decrypt key when process is started.
|
||||
- PrivValidatorLedger uses a Ledger Nano S to handle all signing.
|
||||
|
||||
What follows are descriptions of useful types
|
||||
|
||||
### Signer
|
||||
|
||||
```
|
||||
type Signer interface {
|
||||
Sign(msg []byte) (crypto.Signature, error)
|
||||
}
|
||||
```
|
||||
|
||||
Signer signs a message. It can also return an error.
|
||||
|
||||
### ValidatorID
|
||||
|
||||
|
||||
ValidatorID is just the Address and PubKey
|
||||
|
||||
```
|
||||
type ValidatorID struct {
|
||||
Address data.Bytes `json:"address"`
|
||||
PubKey crypto.PubKey `json:"pub_key"`
|
||||
}
|
||||
```
|
||||
|
||||
### LastSignedInfo
|
||||
|
||||
LastSignedInfo tracks the last thing we signed:
|
||||
|
||||
```
|
||||
type LastSignedInfo struct {
|
||||
Height int64 `json:"height"`
|
||||
Round int `json:"round"`
|
||||
Step int8 `json:"step"`
|
||||
Signature crypto.Signature `json:"signature,omitempty"` // so we dont lose signatures
|
||||
SignBytes data.Bytes `json:"signbytes,omitempty"` // so we dont lose signatures
|
||||
}
|
||||
```
|
||||
|
||||
It exposes methods for signing votes and proposals using a `Signer`.
|
||||
|
||||
This allows it to easily be reused by developers implemented their own PrivValidator.
|
||||
|
||||
### PrivValidatorUnencrypted
|
||||
|
||||
```
|
||||
type PrivValidatorUnencrypted struct {
|
||||
ID types.ValidatorID `json:"id"`
|
||||
PrivKey PrivKey `json:"priv_key"`
|
||||
LastSignedInfo *LastSignedInfo `json:"last_signed_info"`
|
||||
}
|
||||
```
|
||||
|
||||
Has the same structure as currently, but broken up into sub structs.
|
||||
|
||||
Note the LastSignedInfo is mutated in place every time we sign.
|
||||
|
||||
### PrivValidatorJSON
|
||||
|
||||
The "priv_validator.json" file supports only the PrivValidatorUnencrypted type.
|
||||
|
||||
It unmarshals into PrivValidatorJSON, which is used as the default PrivValidator type.
|
||||
It wraps the PrivValidatorUnencrypted and persists it to disk after every signature.
|
||||
|
||||
## Status
|
||||
|
||||
Accepted.
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- Cleaner separation of components enabling re-use.
|
||||
|
||||
### Negative
|
||||
|
||||
- More files - led to creation of new directory.
|
||||
|
||||
### Neutral
|
||||
|
||||
10
docs/conf.py
10
docs/conf.py
@@ -41,15 +41,15 @@ templates_path = ['_templates']
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
#
|
||||
source_suffix = ['.rst', '.md']
|
||||
# source_suffix = '.rst'
|
||||
#source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Tendermint'
|
||||
copyright = u'2017, The Authors'
|
||||
copyright = u'2018, The Authors'
|
||||
author = u'Tendermint'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
@@ -71,7 +71,7 @@ language = None
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This patterns also effect to html_static_path and html_extra_path
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'architecture']
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'architecture', 'specification/new-spec', 'examples']
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
@@ -203,6 +203,6 @@ urllib.urlretrieve(tools_repo+tools_branch+'/tm-bench/README.rst', filename=tool
|
||||
#### abci spec #################################
|
||||
|
||||
abci_repo = "https://raw.githubusercontent.com/tendermint/abci/"
|
||||
abci_branch = "spec-docs"
|
||||
abci_branch = "develop"
|
||||
|
||||
urllib.urlretrieve(abci_repo+abci_branch+'/specification.rst', filename='abci-spec.rst')
|
||||
|
||||
8
docs/determinism.rst
Normal file
8
docs/determinism.rst
Normal file
@@ -0,0 +1,8 @@
|
||||
On Determinism
|
||||
==============
|
||||
|
||||
Arguably, the most difficult part of blockchain programming is determinism - that is, ensuring that sources of indeterminism do not creep into the design of such systems.
|
||||
|
||||
See `this issue <https://github.com/tendermint/abci/issues/56>`__ for more information on the potential sources of indeterminism.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ The growing list of applications built using various pieces of the Tendermint st
|
||||
|
||||
* https://tendermint.com/ecosystem
|
||||
|
||||
We thank the community for their contributions thus far and welcome the addition of new projects. A pull request can be submitted to `this file <https://github.com/tendermint/tendermint/blob/master/docs/ecosystem.rst>`__ to include your project.
|
||||
We thank the community for their contributions thus far and welcome the addition of new projects. A pull request can be submitted to `this file <https://github.com/tendermint/aib-data/blob/master/json/ecosystem.json>`__ to include your project.
|
||||
|
||||
Other Tools
|
||||
-----------
|
||||
|
||||
@@ -12,7 +12,7 @@ and want to get started right away, continue. Otherwise, [review the documentati
|
||||
On a fresh Ubuntu 16.04 machine can be done with [this script](https://git.io/vNLfY), like so:
|
||||
|
||||
```
|
||||
curl -L https://git.io/vNLfY | bash
|
||||
curl -L https://git.io/vxWlX | bash
|
||||
source ~/.profile
|
||||
```
|
||||
|
||||
@@ -71,7 +71,7 @@ Configuring a cluster is covered further below.
|
||||
Start tendermint with a simple in-process application:
|
||||
|
||||
```
|
||||
tendermint node --proxy_app=dummy
|
||||
tendermint node --proxy_app=kvstore
|
||||
```
|
||||
|
||||
and blocks will start to stream in:
|
||||
@@ -89,7 +89,7 @@ curl -s localhost:46657/status
|
||||
|
||||
### Sending Transactions
|
||||
|
||||
With the dummy app running, we can send transactions:
|
||||
With the kvstore app running, we can send transactions:
|
||||
|
||||
```
|
||||
curl -s 'localhost:46657/broadcast_tx_commit?tx="abcd"'
|
||||
@@ -131,10 +131,10 @@ This will install `go` and other dependencies, get the Tendermint source code, t
|
||||
Next, `cd` into `docs/examples`. Each command below should be run from each node, in sequence:
|
||||
|
||||
```
|
||||
tendermint node --home ./node1 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656
|
||||
tendermint node --home ./node2 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656
|
||||
tendermint node --home ./node3 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656
|
||||
tendermint node --home ./node4 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656
|
||||
tendermint node --home ./node1 --proxy_app=kvstore --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656
|
||||
tendermint node --home ./node2 --proxy_app=kvstore --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656
|
||||
tendermint node --home ./node3 --proxy_app=kvstore --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656
|
||||
tendermint node --home ./node4 --proxy_app=kvstore --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656
|
||||
```
|
||||
|
||||
Note that after the third node is started, blocks will start to stream in because >2/3 of validators (defined in the `genesis.json`) have come online. Seeds can also be specified in the `config.toml`. See [this PR](https://github.com/tendermint/tendermint/pull/792) for more information about configuration options.
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
# and has only been tested on Digital Ocean
|
||||
|
||||
# get and unpack golang
|
||||
curl -O https://storage.googleapis.com/golang/go1.9.2.linux-amd64.tar.gz
|
||||
tar -xvf go1.9.2.linux-amd64.tar.gz
|
||||
curl -O https://storage.googleapis.com/golang/go1.10.linux-amd64.tar.gz
|
||||
tar -xvf go1.10.linux-amd64.tar.gz
|
||||
|
||||
apt install make
|
||||
|
||||
@@ -26,7 +26,7 @@ go get $REPO
|
||||
cd $GOPATH/src/$REPO
|
||||
|
||||
## build
|
||||
git checkout v0.15.0
|
||||
git checkout v0.17.0
|
||||
make get_tools
|
||||
make get_vendor_deps
|
||||
make install
|
||||
make install
|
||||
|
||||
@@ -27,38 +27,38 @@ Then run
|
||||
|
||||
go get -u github.com/tendermint/abci/cmd/abci-cli
|
||||
|
||||
If there is an error, install and run the ``glide`` tool to pin the
|
||||
If there is an error, install and run the `dep <https://github.com/golang/dep>`__ tool to pin the
|
||||
dependencies:
|
||||
|
||||
::
|
||||
|
||||
go get github.com/Masterminds/glide
|
||||
cd $GOPATH/src/github.com/tendermint/abci
|
||||
glide install
|
||||
go install ./cmd/abci-cli
|
||||
make get_tools
|
||||
make get_vendor_deps
|
||||
make install
|
||||
|
||||
Now you should have the ``abci-cli`` installed; you'll see
|
||||
a couple of commands (``counter`` and ``dummy``) that are
|
||||
a couple of commands (``counter`` and ``kvstore``) that are
|
||||
example applications written in Go. See below for an application
|
||||
written in JavaScript.
|
||||
|
||||
Now, let's run some apps!
|
||||
|
||||
Dummy - A First Example
|
||||
-----------------------
|
||||
KVStore - A First Example
|
||||
-------------------------
|
||||
|
||||
The dummy app is a `Merkle
|
||||
The kvstore app is a `Merkle
|
||||
tree <https://en.wikipedia.org/wiki/Merkle_tree>`__ that just stores all
|
||||
transactions. If the transaction contains an ``=``, e.g. ``key=value``,
|
||||
then the ``value`` is stored under the ``key`` in the Merkle tree.
|
||||
Otherwise, the full transaction bytes are stored as the key and the
|
||||
value.
|
||||
|
||||
Let's start a dummy application.
|
||||
Let's start a kvstore application.
|
||||
|
||||
::
|
||||
|
||||
abci-cli dummy
|
||||
abci-cli kvstore
|
||||
|
||||
In another terminal, we can start Tendermint. If you have never run
|
||||
Tendermint before, use:
|
||||
@@ -85,7 +85,7 @@ The ``-s`` just silences ``curl``. For nicer output, pipe the result
|
||||
into a tool like `jq <https://stedolan.github.io/jq/>`__ or
|
||||
`jsonpp <https://github.com/jmhodges/jsonpp>`__.
|
||||
|
||||
Now let's send some transactions to the dummy.
|
||||
Now let's send some transactions to the kvstore.
|
||||
|
||||
::
|
||||
|
||||
@@ -192,7 +192,7 @@ In this instance of the counter app, with ``serial=on``, ``CheckTx``
|
||||
only allows transactions whose integer is greater than the last
|
||||
committed one.
|
||||
|
||||
Let's kill the previous instance of ``tendermint`` and the ``dummy``
|
||||
Let's kill the previous instance of ``tendermint`` and the ``kvstore``
|
||||
application, and start the counter app. We can enable ``serial=on`` with
|
||||
a flag:
|
||||
|
||||
@@ -313,7 +313,7 @@ Neat, eh?
|
||||
Basecoin - A More Interesting Example
|
||||
-------------------------------------
|
||||
|
||||
We saved the best for last; the `Cosmos SDK <https://github.com/cosmos/cosmos-sdk>`__ is a general purpose framework for building cryptocurrencies. Unlike the ``dummy`` and ``counter``, which are strictly for example purposes. The reference implementation of Cosmos SDK is ``basecoin``, which demonstrates how to use the building blocks of the Cosmos SDK.
|
||||
We saved the best for last; the `Cosmos SDK <https://github.com/cosmos/cosmos-sdk>`__ is a general purpose framework for building cryptocurrencies. Unlike the ``kvstore`` and ``counter``, which are strictly for example purposes. The reference implementation of Cosmos SDK is ``basecoin``, which demonstrates how to use the building blocks of the Cosmos SDK.
|
||||
|
||||
The default ``basecoin`` application is a multi-asset cryptocurrency
|
||||
that supports inter-blockchain communication (IBC). For more details on how
|
||||
|
||||
@@ -5,7 +5,7 @@ Walk through example
|
||||
--------------------
|
||||
|
||||
We first create three connections (mempool, consensus and query) to the
|
||||
application (locally running dummy in this case).
|
||||
application (running ``kvstore`` locally in this case).
|
||||
|
||||
::
|
||||
|
||||
|
||||
@@ -65,10 +65,11 @@ Tendermint 201
|
||||
:maxdepth: 2
|
||||
|
||||
specification.rst
|
||||
determinism.rst
|
||||
|
||||
* For a deeper dive, see `this thesis <https://atrium.lib.uoguelph.ca/xmlui/handle/10214/9769>`__.
|
||||
* There is also the `original whitepaper <https://tendermint.com/static/docs/tendermint.pdf>`__, though it is now quite outdated.
|
||||
* Readers might also be interested in the `Cosmos Whitepaper <https://cosmos.network/whitepaper>`__ which describes Tendermint, ABCI, and how to build a scalable, heterogeneous, cryptocurrency network.
|
||||
* For example applications and related software built by the Tendermint team and other, see the `software ecosystem <https://tendermint.com/ecosystem>`__.
|
||||
|
||||
Join the `Cosmos and Tendermint Rocket Chat <https://cosmos.rocket.chat>`__ to ask questions and discuss projects.
|
||||
Join the `community <https://cosmos.network/community>`__ to ask questions and discuss projects.
|
||||
|
||||
@@ -9,7 +9,7 @@ To download pre-built binaries, see the `Download page <https://tendermint.com/d
|
||||
From Source
|
||||
-----------
|
||||
|
||||
You'll need ``go``, maybe ``glide``, and the Tendermint source code.
|
||||
You'll need ``go``, maybe `dep <https://github.com/golang/dep>`__, and the Tendermint source code.
|
||||
|
||||
Install Go
|
||||
^^^^^^^^^^
|
||||
@@ -31,21 +31,21 @@ installation worked.
|
||||
|
||||
If the installation failed, a dependency may have been updated and become
|
||||
incompatible with the latest Tendermint master branch. We solve this
|
||||
using the ``glide`` tool for dependency management.
|
||||
using the ``dep`` tool for dependency management.
|
||||
|
||||
First, install ``glide``:
|
||||
First, install ``dep``:
|
||||
|
||||
::
|
||||
|
||||
go get github.com/Masterminds/glide
|
||||
make get_tools
|
||||
|
||||
Now we can fetch the correct versions of each dependency by running:
|
||||
|
||||
::
|
||||
|
||||
cd $GOPATH/src/github.com/tendermint/tendermint
|
||||
glide install
|
||||
go install ./cmd/tendermint
|
||||
make get_vendor_deps
|
||||
make install
|
||||
|
||||
Note that even though ``go get`` originally failed, the repository was
|
||||
still cloned to the correct location in the ``$GOPATH``.
|
||||
@@ -60,7 +60,7 @@ If you already have Tendermint installed, and you make updates, simply
|
||||
::
|
||||
|
||||
cd $GOPATH/src/github.com/tendermint/tendermint
|
||||
go install ./cmd/tendermint
|
||||
make install
|
||||
|
||||
To upgrade, there are a few options:
|
||||
|
||||
@@ -72,18 +72,18 @@ To upgrade, there are a few options:
|
||||
its dependencies
|
||||
- fetch and checkout the latest master branch in
|
||||
``$GOPATH/src/github.com/tendermint/tendermint``, and then run
|
||||
``glide install && go install ./cmd/tendermint`` as above.
|
||||
``make get_vendor_deps && make install`` as above.
|
||||
|
||||
Note the first two options should usually work, but may fail. If they
|
||||
do, use ``glide``, as above:
|
||||
do, use ``dep``, as above:
|
||||
|
||||
::
|
||||
|
||||
cd $GOPATH/src/github.com/tendermint/tendermint
|
||||
glide install
|
||||
go install ./cmd/tendermint
|
||||
make get_vendor_deps
|
||||
make install
|
||||
|
||||
Since the third option just uses ``glide`` right away, it should always
|
||||
Since the third option just uses ``dep`` right away, it should always
|
||||
work.
|
||||
|
||||
Troubleshooting
|
||||
@@ -96,8 +96,8 @@ If ``go get`` failing bothers you, fetch the code using ``git``:
|
||||
mkdir -p $GOPATH/src/github.com/tendermint
|
||||
git clone https://github.com/tendermint/tendermint $GOPATH/src/github.com/tendermint/tendermint
|
||||
cd $GOPATH/src/github.com/tendermint/tendermint
|
||||
glide install
|
||||
go install ./cmd/tendermint
|
||||
make get_vendor_deps
|
||||
make install
|
||||
|
||||
Run
|
||||
^^^
|
||||
@@ -107,4 +107,4 @@ To start a one-node blockchain with a simple in-process application:
|
||||
::
|
||||
|
||||
tendermint init
|
||||
tendermint node --proxy_app=dummy
|
||||
tendermint node --proxy_app=kvstore
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Specification
|
||||
#############
|
||||
|
||||
Here you'll find details of the Tendermint specification. See `the spec repo <https://github.com/tendermint/spec>`__ for upcoming material. Tendermint's types are produced by `godoc <https://godoc.org/github.com/tendermint/tendermint/types>`__.
|
||||
Here you'll find details of the Tendermint specification. Tendermint's types are produced by `godoc <https://godoc.org/github.com/tendermint/tendermint/types>`__.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
@@ -10,6 +10,7 @@ Here you'll find details of the Tendermint specification. See `the spec repo <ht
|
||||
specification/block-structure.rst
|
||||
specification/byzantine-consensus-algorithm.rst
|
||||
specification/configuration.rst
|
||||
specification/corruption.rst
|
||||
specification/fast-sync.rst
|
||||
specification/genesis.rst
|
||||
specification/light-client-protocol.rst
|
||||
|
||||
@@ -329,11 +329,11 @@ collateral on all other forks. Clients should verify the signatures on
|
||||
the reorg-proposal, verify any evidence, and make a judgement or prompt
|
||||
the end-user for a decision. For example, a phone wallet app may prompt
|
||||
the user with a security warning, while a refrigerator may accept any
|
||||
reorg-proposal signed by +½ of the original validators.
|
||||
reorg-proposal signed by +1/2 of the original validators.
|
||||
|
||||
No non-synchronous Byzantine fault-tolerant algorithm can come to
|
||||
consensus when ⅓+ of validators are dishonest, yet a fork assumes that
|
||||
⅓+ of validators have already been dishonest by double-signing or
|
||||
consensus when 1/3+ of validators are dishonest, yet a fork assumes that
|
||||
1/3+ of validators have already been dishonest by double-signing or
|
||||
lock-changing without justification. So, signing the reorg-proposal is a
|
||||
coordination problem that cannot be solved by any non-synchronous
|
||||
protocol (i.e. automatically, and without making assumptions about the
|
||||
|
||||
@@ -89,6 +89,7 @@ like the file below, however, double check by inspecting the
|
||||
seeds = ""
|
||||
|
||||
# Comma separated list of nodes to keep persistent connections to
|
||||
# Do not add private peers to this list if you don't want them advertised
|
||||
persistent_peers = ""
|
||||
|
||||
# Path to address book
|
||||
@@ -121,6 +122,12 @@ like the file below, however, double check by inspecting the
|
||||
# Does not work if the peer-exchange reactor is disabled.
|
||||
seed_mode = false
|
||||
|
||||
# Authenticated encryption
|
||||
auth_enc = true
|
||||
|
||||
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
||||
private_peer_ids = ""
|
||||
|
||||
##### mempool configuration options #####
|
||||
[mempool]
|
||||
|
||||
|
||||
@@ -5,12 +5,6 @@ The genesis.json file in ``$TMHOME/config`` defines the initial TendermintCore
|
||||
state upon genesis of the blockchain (`see
|
||||
definition <https://github.com/tendermint/tendermint/blob/master/types/genesis.go>`__).
|
||||
|
||||
NOTE: This does not (yet) specify the application state (e.g. initial
|
||||
distribution of tokens). Currently we leave it up to the application to
|
||||
load the initial application genesis state. In the future, we may
|
||||
include genesis SetOption messages that get passed from TendermintCore
|
||||
to the app upon genesis.
|
||||
|
||||
Fields
|
||||
~~~~~~
|
||||
|
||||
@@ -26,6 +20,7 @@ Fields
|
||||
- ``app_hash``: The expected application hash (as returned by the
|
||||
``Commit`` ABCI message) upon genesis. If the app's hash does not
|
||||
match, a warning message is printed.
|
||||
- ``app_state``: The application state (e.g. initial distribution of tokens).
|
||||
|
||||
Sample genesis.json
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
@@ -69,5 +64,8 @@ Sample genesis.json
|
||||
"name": "mach4"
|
||||
}
|
||||
],
|
||||
"app_hash": "15005165891224E721CB664D15CB972240F5703F"
|
||||
"app_hash": "15005165891224E721CB664D15CB972240F5703F",
|
||||
"app_state": {
|
||||
{"account": "Bob", "coins": 5000}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,6 @@ This is a markdown specification of the Tendermint blockchain.
|
||||
It defines the base data structures, how they are validated,
|
||||
and how they are communicated over the network.
|
||||
|
||||
XXX: this spec is a work in progress and not yet complete - see github
|
||||
[issues](https://github.com/tendermint/tendermint/issues) and
|
||||
[pull requests](https://github.com/tendermint/tendermint/pulls)
|
||||
for more details.
|
||||
|
||||
If you find discrepancies between the spec and the code that
|
||||
do not have an associated issue or pull request on github,
|
||||
please submit them to our [bug bounty](https://tendermint.com/security)!
|
||||
@@ -24,18 +19,16 @@ please submit them to our [bug bounty](https://tendermint.com/security)!
|
||||
|
||||
### P2P and Network Protocols
|
||||
|
||||
TODO: update links
|
||||
|
||||
- [The Base P2P Layer](p2p/README.md): multiplex the protocols ("reactors") on authenticated and encrypted TCP connections
|
||||
- [Peer Exchange (PEX)](pex/README.md): gossip known peer addresses so peers can find each other
|
||||
- [Block Sync](block_sync/README.md): gossip blocks so peers can catch up quickly
|
||||
- [Consensus](consensus/README.md): gossip votes and block parts so new blocks can be committed
|
||||
- [Mempool](mempool/README.md): gossip transactions so they get included in blocks
|
||||
- [Evidence](evidence/README.md): TODO
|
||||
- [The Base P2P Layer](p2p): multiplex the protocols ("reactors") on authenticated and encrypted TCP connections
|
||||
- [Peer Exchange (PEX)](reactors/pex): gossip known peer addresses so peers can find each other
|
||||
- [Block Sync](reactors/block_sync): gossip blocks so peers can catch up quickly
|
||||
- [Consensus](reactors/consensus): gossip votes and block parts so new blocks can be committed
|
||||
- [Mempool](reactors/mempool): gossip transactions so they get included in blocks
|
||||
- Evidence: TODO
|
||||
|
||||
### More
|
||||
- [Light Client](light_client/README.md): TODO
|
||||
- [Persistence](persistence/README.md): TODO
|
||||
- Light Client: TODO
|
||||
- Persistence: TODO
|
||||
|
||||
## Overview
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Time in Tendermint is defined with the Time field of the block header.
|
||||
|
||||
It satisfies the following properties:
|
||||
|
||||
- Time Monotonicity: Time is monotonically increasing, i.e., given
|
||||
- Time Monotonicity: Time is monotonically increasing, i.e., given
|
||||
a header H1 for height h1 and a header H2 for height `h2 = h1 + 1`, `H1.Time < H2.Time`.
|
||||
- Time Validity: Given a set of Commit votes that forms the `block.LastCommit` field, a range of
|
||||
valid values for the Time field of the block header is defined only by
|
||||
@@ -16,7 +16,21 @@ In the context of Tendermint, time is of type int64 and denotes UNIX time in mil
|
||||
corresponds to the number of milliseconds since January 1, 1970. Before defining rules that need to be enforced by the
|
||||
Tendermint consensus protocol, so the properties above holds, we introduce the following definition:
|
||||
|
||||
- median of a set of `Vote` messages is equal to the median of `Vote.Time` fields of the corresponding `Vote` messages
|
||||
- median of a set of `Vote` messages is equal to the median of `Vote.Time` fields of the corresponding `Vote` messages,
|
||||
where the value of `Vote.Time` is counted number of times proportional to the process voting power. As in Tendermint
|
||||
the voting power is not uniform (one process one vote), a vote message is actually an aggregator of the same votes whose
|
||||
number is equal to the voting power of the process that has casted the corresponding votes message.
|
||||
|
||||
Let's consider the following example:
|
||||
- we have four processes p1, p2, p3 and p4, with the following voting power distribution (p1, 23), (p2, 27), (p3, 10)
|
||||
and (p4, 10). The total voting power is 70 (`N = 3f+1`, where `N` is the total voting power, and `f` is the maximum voting
|
||||
power of the faulty processes), so we assume that the faulty processes have at most 23 of voting power.
|
||||
Furthermore, we have the following vote messages in some LastCommit field (we ignore all fields except Time field):
|
||||
- (p1, 100), (p2, 98), (p3, 1000), (p4, 500). We assume that p3 and p4 are faulty processes. Let's assume that the
|
||||
`block.LastCommit` message contains votes of processes p2, p3 and p4. Median is then chosen the following way:
|
||||
the value 98 is counted 27 times, the value 1000 is counted 10 times and the value 500 is counted also 10 times.
|
||||
So the median value will be the value 98. No matter what set of messages with at least `2f+1` voting power we
|
||||
choose, the median value will always be between the values sent by correct processes.
|
||||
|
||||
We ensure Time Monotonicity and Time Validity properties by the following rules:
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ Updates (state transitions) happen on timeouts, complete proposals, and 2/3 majo
|
||||
It receives messages from peers, internal validators and from Timeout Ticker
|
||||
and invokes the corresponding handlers, potentially updating the RoundState.
|
||||
The details of the protocol (together with formal proofs of correctness) implemented by the Receive Routine are
|
||||
discussed in separate document (see [spec](https://github.com/tendermint/spec)). For understanding of this document
|
||||
discussed in separate document. For understanding of this document
|
||||
it is sufficient to understand that the Receive Routine manages and updates RoundState data structure that is
|
||||
then extensively used by the gossip routines to determine what information should be sent to peer processes.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ next block should be; a validator might vote with a `VoteMessage` for a differen
|
||||
round, enough number of processes vote for the same block, then this block is committed and later
|
||||
added to the blockchain. `ProposalMessage` and `VoteMessage` are signed by the private key of the
|
||||
validator. The internals of the protocol and how it ensures safety and liveness properties are
|
||||
explained [here](https://github.com/tendermint/spec).
|
||||
explained in a forthcoming document.
|
||||
|
||||
For efficiency reasons, validators in Tendermint consensus protocol do not agree directly on the
|
||||
block as the block size is big, i.e., they don't embed the block inside `Proposal` and
|
||||
|
||||
@@ -57,10 +57,17 @@ a trust metric (see below), but it's best to start with something simple.
|
||||
## Select Peers to Dial
|
||||
|
||||
When we need more peers, we pick them randomly from the addrbook with some
|
||||
configurable bias for unvetted peers. The bias should be lower when we have fewer peers,
|
||||
configurable bias for unvetted peers. The bias should be lower when we have fewer peers
|
||||
and can increase as we obtain more, ensuring that our first peers are more trustworthy,
|
||||
but always giving us the chance to discover new good peers.
|
||||
|
||||
We track the last time we dialed a peer and the number of unsuccessful attempts
|
||||
we've made. If too many attempts are made, we mark the peer as bad.
|
||||
|
||||
Connection attempts are made with exponential backoff (plus jitter). Because
|
||||
the selection process happens every `ensurePeersPeriod`, we might not end up
|
||||
dialing a peer for much longer than the backoff duration.
|
||||
|
||||
## Select Peers to Exchange
|
||||
|
||||
When we’re asked for peers, we select them as follows:
|
||||
|
||||
@@ -97,6 +97,7 @@ An HTTP Get request to the root RPC endpoint (e.g.
|
||||
http://localhost:46657/genesis
|
||||
http://localhost:46657/net_info
|
||||
http://localhost:46657/num_unconfirmed_txs
|
||||
http://localhost:46657/health
|
||||
http://localhost:46657/status
|
||||
http://localhost:46657/unconfirmed_txs
|
||||
http://localhost:46657/unsafe_flush_mempool
|
||||
|
||||
@@ -41,18 +41,18 @@ To run a Tendermint node, use
|
||||
tendermint node
|
||||
|
||||
By default, Tendermint will try to connect to an ABCI application on
|
||||
`127.0.0.1:46658 <127.0.0.1:46658>`__. If you have the ``dummy`` ABCI
|
||||
`127.0.0.1:46658 <127.0.0.1:46658>`__. If you have the ``kvstore`` ABCI
|
||||
app installed, run it in another window. If you don't, kill Tendermint
|
||||
and run an in-process version with
|
||||
and run an in-process version of the ``kvstore`` app:
|
||||
|
||||
::
|
||||
|
||||
tendermint node --proxy_app=dummy
|
||||
tendermint node --proxy_app=kvstore
|
||||
|
||||
After a few seconds you should see blocks start streaming in. Note that
|
||||
blocks are produced regularly, even if there are no transactions. See *No Empty Blocks*, below, to modify this setting.
|
||||
|
||||
Tendermint supports in-process versions of the dummy, counter, and nil
|
||||
Tendermint supports in-process versions of the ``counter``, ``kvstore`` and ``nil``
|
||||
apps that ship as examples in the `ABCI
|
||||
repository <https://github.com/tendermint/abci>`__. It's easy to compile
|
||||
your own app in-process with Tendermint if it's written in Go. If your
|
||||
|
||||
@@ -84,7 +84,8 @@ func (evR *EvidenceReactor) RemovePeer(peer p2p.Peer, reason interface{}) {
|
||||
func (evR *EvidenceReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) {
|
||||
_, msg, err := DecodeMessage(msgBytes)
|
||||
if err != nil {
|
||||
evR.Logger.Error("Error decoding message", "err", err)
|
||||
evR.Logger.Error("Error decoding message", "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes)
|
||||
evR.Switch.StopPeerForError(src, err)
|
||||
return
|
||||
}
|
||||
evR.Logger.Debug("Receive", "src", src, "chId", chID, "msg", msg)
|
||||
@@ -95,7 +96,8 @@ func (evR *EvidenceReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) {
|
||||
err := evR.evpool.AddEvidence(ev)
|
||||
if err != nil {
|
||||
evR.Logger.Info("Evidence is not valid", "evidence", msg.Evidence, "err", err)
|
||||
// TODO: punish peer
|
||||
// punish peer
|
||||
evR.Switch.StopPeerForError(src, err)
|
||||
}
|
||||
}
|
||||
default:
|
||||
|
||||
206
glide.lock
generated
206
glide.lock
generated
@@ -1,206 +0,0 @@
|
||||
hash: 322a0d4b9be08c59bf65df0e17e3be8d60762eaf9516f0c4126b50f9fd676f26
|
||||
updated: 2018-02-21T03:31:35.382568482Z
|
||||
imports:
|
||||
- name: github.com/btcsuite/btcd
|
||||
version: 50de9da05b50eb15658bb350f6ea24368a111ab7
|
||||
subpackages:
|
||||
- btcec
|
||||
- name: github.com/ebuchman/fail-test
|
||||
version: 95f809107225be108efcf10a3509e4ea6ceef3c4
|
||||
- name: github.com/fsnotify/fsnotify
|
||||
version: c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9
|
||||
- name: github.com/go-kit/kit
|
||||
version: 4dc7be5d2d12881735283bcab7352178e190fc71
|
||||
subpackages:
|
||||
- log
|
||||
- log/level
|
||||
- log/term
|
||||
- name: github.com/go-logfmt/logfmt
|
||||
version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5
|
||||
- name: github.com/go-stack/stack
|
||||
version: 259ab82a6cad3992b4e21ff5cac294ccb06474bc
|
||||
- name: github.com/gogo/protobuf
|
||||
version: 1adfc126b41513cc696b209667c8656ea7aac67c
|
||||
subpackages:
|
||||
- gogoproto
|
||||
- jsonpb
|
||||
- proto
|
||||
- protoc-gen-gogo/descriptor
|
||||
- sortkeys
|
||||
- types
|
||||
- name: github.com/golang/protobuf
|
||||
version: 925541529c1fa6821df4e44ce2723319eb2be768
|
||||
subpackages:
|
||||
- proto
|
||||
- ptypes
|
||||
- ptypes/any
|
||||
- ptypes/duration
|
||||
- ptypes/timestamp
|
||||
- name: github.com/golang/snappy
|
||||
version: 553a641470496b2327abcac10b36396bd98e45c9
|
||||
- name: github.com/gorilla/websocket
|
||||
version: ea4d1f681babbce9545c9c5f3d5194a789c89f5b
|
||||
- name: github.com/hashicorp/hcl
|
||||
version: 23c074d0eceb2b8a5bfdbb271ab780cde70f05a8
|
||||
subpackages:
|
||||
- hcl/ast
|
||||
- hcl/parser
|
||||
- hcl/scanner
|
||||
- hcl/strconv
|
||||
- hcl/token
|
||||
- json/parser
|
||||
- json/scanner
|
||||
- json/token
|
||||
- name: github.com/inconshreveable/mousetrap
|
||||
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
||||
- name: github.com/jmhodges/levigo
|
||||
version: c42d9e0ca023e2198120196f842701bb4c55d7b9
|
||||
- name: github.com/kr/logfmt
|
||||
version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0
|
||||
- name: github.com/magiconair/properties
|
||||
version: 49d762b9817ba1c2e9d0c69183c2b4a8b8f1d934
|
||||
- name: github.com/mitchellh/mapstructure
|
||||
version: b4575eea38cca1123ec2dc90c26529b5c5acfcff
|
||||
- name: github.com/pelletier/go-toml
|
||||
version: acdc4509485b587f5e675510c4f2c63e90ff68a8
|
||||
- name: github.com/pkg/errors
|
||||
version: 645ef00459ed84a119197bfb8d8205042c6df63d
|
||||
- name: github.com/rcrowley/go-metrics
|
||||
version: 8732c616f52954686704c8645fe1a9d59e9df7c1
|
||||
- name: github.com/spf13/afero
|
||||
version: bb8f1927f2a9d3ab41c9340aa034f6b803f4359c
|
||||
subpackages:
|
||||
- mem
|
||||
- name: github.com/spf13/cast
|
||||
version: acbeb36b902d72a7a4c18e8f3241075e7ab763e4
|
||||
- name: github.com/spf13/cobra
|
||||
version: 7b2c5ac9fc04fc5efafb60700713d4fa609b777b
|
||||
- name: github.com/spf13/jwalterweatherman
|
||||
version: 7c0cea34c8ece3fbeb2b27ab9b59511d360fb394
|
||||
- name: github.com/spf13/pflag
|
||||
version: e57e3eeb33f795204c1ca35f56c44f83227c6e66
|
||||
- name: github.com/spf13/viper
|
||||
version: 25b30aa063fc18e48662b86996252eabdcf2f0c7
|
||||
- name: github.com/syndtr/goleveldb
|
||||
version: 34011bf325bce385408353a30b101fe5e923eb6e
|
||||
subpackages:
|
||||
- leveldb
|
||||
- leveldb/cache
|
||||
- leveldb/comparer
|
||||
- leveldb/errors
|
||||
- leveldb/filter
|
||||
- leveldb/iterator
|
||||
- leveldb/journal
|
||||
- leveldb/memdb
|
||||
- leveldb/opt
|
||||
- leveldb/storage
|
||||
- leveldb/table
|
||||
- leveldb/util
|
||||
- name: github.com/tendermint/abci
|
||||
version: 68592f4d8ee34e97db94b7a7976b1309efdb7eb9
|
||||
subpackages:
|
||||
- client
|
||||
- example/code
|
||||
- example/counter
|
||||
- example/dummy
|
||||
- server
|
||||
- types
|
||||
- name: github.com/tendermint/ed25519
|
||||
version: d8387025d2b9d158cf4efb07e7ebf814bcce2057
|
||||
subpackages:
|
||||
- edwards25519
|
||||
- extra25519
|
||||
- name: github.com/tendermint/go-crypto
|
||||
version: dd20358a264c772b4a83e477b0cfce4c88a7001d
|
||||
- name: github.com/tendermint/go-wire
|
||||
version: b6fc872b42d41158a60307db4da051dd6f179415
|
||||
subpackages:
|
||||
- data
|
||||
- name: github.com/tendermint/tmlibs
|
||||
version: 1b9b5652a199ab0be2e781393fb275b66377309d
|
||||
subpackages:
|
||||
- autofile
|
||||
- cli
|
||||
- cli/flags
|
||||
- clist
|
||||
- common
|
||||
- db
|
||||
- flowrate
|
||||
- log
|
||||
- merkle
|
||||
- pubsub
|
||||
- pubsub/query
|
||||
- test
|
||||
- name: golang.org/x/crypto
|
||||
version: 1875d0a70c90e57f11972aefd42276df65e895b9
|
||||
subpackages:
|
||||
- curve25519
|
||||
- nacl/box
|
||||
- nacl/secretbox
|
||||
- openpgp/armor
|
||||
- openpgp/errors
|
||||
- poly1305
|
||||
- ripemd160
|
||||
- salsa20/salsa
|
||||
- name: golang.org/x/net
|
||||
version: 2fb46b16b8dda405028c50f7c7f0f9dd1fa6bfb1
|
||||
subpackages:
|
||||
- context
|
||||
- http2
|
||||
- http2/hpack
|
||||
- idna
|
||||
- internal/timeseries
|
||||
- lex/httplex
|
||||
- trace
|
||||
- name: golang.org/x/sys
|
||||
version: 37707fdb30a5b38865cfb95e5aab41707daec7fd
|
||||
subpackages:
|
||||
- unix
|
||||
- name: golang.org/x/text
|
||||
version: e19ae1496984b1c655b8044a65c0300a3c878dd3
|
||||
subpackages:
|
||||
- secure/bidirule
|
||||
- transform
|
||||
- unicode/bidi
|
||||
- unicode/norm
|
||||
- name: google.golang.org/genproto
|
||||
version: 4eb30f4778eed4c258ba66527a0d4f9ec8a36c45
|
||||
subpackages:
|
||||
- googleapis/rpc/status
|
||||
- name: google.golang.org/grpc
|
||||
version: 401e0e00e4bb830a10496d64cd95e068c5bf50de
|
||||
subpackages:
|
||||
- balancer
|
||||
- codes
|
||||
- connectivity
|
||||
- credentials
|
||||
- grpclb/grpc_lb_v1/messages
|
||||
- grpclog
|
||||
- internal
|
||||
- keepalive
|
||||
- metadata
|
||||
- naming
|
||||
- peer
|
||||
- resolver
|
||||
- stats
|
||||
- status
|
||||
- tap
|
||||
- transport
|
||||
- name: gopkg.in/yaml.v2
|
||||
version: d670f9405373e636a5a2765eea47fac0c9bc91a4
|
||||
testImports:
|
||||
- name: github.com/davecgh/go-spew
|
||||
version: 346938d642f2ec3594ed81d874461961cd0faa76
|
||||
subpackages:
|
||||
- spew
|
||||
- name: github.com/fortytw2/leaktest
|
||||
version: 3b724c3d7b8729a35bf4e577f71653aec6e53513
|
||||
- name: github.com/pmezard/go-difflib
|
||||
version: 792786c7400a136282c1664665ae0a8db921c6c2
|
||||
subpackages:
|
||||
- difflib
|
||||
- name: github.com/stretchr/testify
|
||||
version: 12b6f73e6084dad08a7c6e575284b177ecafbc71
|
||||
subpackages:
|
||||
- assert
|
||||
- require
|
||||
63
glide.yaml
63
glide.yaml
@@ -1,63 +0,0 @@
|
||||
package: github.com/tendermint/tendermint
|
||||
import:
|
||||
- package: github.com/ebuchman/fail-test
|
||||
- package: github.com/gogo/protobuf
|
||||
version: ^1.0.0
|
||||
subpackages:
|
||||
- proto
|
||||
- package: github.com/golang/protobuf
|
||||
version: ^1.0.0
|
||||
subpackages:
|
||||
- proto
|
||||
- package: github.com/gorilla/websocket
|
||||
version: v1.2.0
|
||||
- package: github.com/pkg/errors
|
||||
version: ~0.8.0
|
||||
- package: github.com/rcrowley/go-metrics
|
||||
- package: github.com/spf13/cobra
|
||||
version: v0.0.1
|
||||
- package: github.com/spf13/viper
|
||||
version: v1.0.0
|
||||
- package: github.com/tendermint/abci
|
||||
version: 0.10.0
|
||||
subpackages:
|
||||
- client
|
||||
- example/dummy
|
||||
- types
|
||||
- package: github.com/tendermint/go-crypto
|
||||
version: 0.4.1
|
||||
- package: github.com/tendermint/go-wire
|
||||
version: 0.7.2
|
||||
subpackages:
|
||||
- data
|
||||
- package: github.com/tendermint/tmlibs
|
||||
version: 0.7.0
|
||||
subpackages:
|
||||
- autofile
|
||||
- cli
|
||||
- cli/flags
|
||||
- clist
|
||||
- common
|
||||
- db
|
||||
- flowrate
|
||||
- log
|
||||
- merkle
|
||||
- pubsub
|
||||
- pubsub/query
|
||||
- package: golang.org/x/crypto
|
||||
subpackages:
|
||||
- nacl/box
|
||||
- nacl/secretbox
|
||||
- ripemd160
|
||||
- package: google.golang.org/grpc
|
||||
version: v1.7.3
|
||||
testImport:
|
||||
- package: github.com/fortytw2/leaktest
|
||||
- package: github.com/go-kit/kit
|
||||
version: ^0.6.0
|
||||
subpackages:
|
||||
- log/term
|
||||
- package: github.com/stretchr/testify
|
||||
subpackages:
|
||||
- assert
|
||||
- require
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/tendermint/abci/example/dummy"
|
||||
"github.com/tendermint/abci/example/kvstore"
|
||||
|
||||
nm "github.com/tendermint/tendermint/node"
|
||||
rpctest "github.com/tendermint/tendermint/rpc/test"
|
||||
@@ -14,7 +14,7 @@ var node *nm.Node
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// start a tendermint node (and merkleeyes) in the background to test against
|
||||
app := dummy.NewDummyApplication()
|
||||
app := kvstore.NewKVStoreApplication()
|
||||
node = rpctest.StartTendermint(app)
|
||||
code := m.Run()
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ func (v ValKeys) signHeader(header *types.Header, first, last int) *types.Commit
|
||||
vset := v.ToValidators(1, 0)
|
||||
|
||||
// fill in the votes we want
|
||||
for i := first; i < last; i++ {
|
||||
for i := first; i < last && i < len(v); i++ {
|
||||
vote := makeVote(header, vset, v[i])
|
||||
votes[vote.ValidatorIndex] = vote
|
||||
}
|
||||
@@ -102,7 +102,7 @@ func makeVote(header *types.Header, vals *types.ValidatorSet, key crypto.PrivKey
|
||||
BlockID: types.BlockID{Hash: header.Hash()},
|
||||
}
|
||||
// Sign it
|
||||
signBytes := types.SignBytes(header.ChainID, vote)
|
||||
signBytes := vote.SignBytes(header.ChainID)
|
||||
vote.Signature = key.Sign(signBytes)
|
||||
return vote
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ type memStoreProvider struct {
|
||||
// btree would be more efficient for larger sets
|
||||
byHeight fullCommits
|
||||
byHash map[string]FullCommit
|
||||
|
||||
sorted bool
|
||||
}
|
||||
|
||||
// fullCommits just exists to allow easy sorting
|
||||
@@ -52,25 +54,78 @@ func (m *memStoreProvider) StoreCommit(fc FullCommit) error {
|
||||
defer m.mtx.Unlock()
|
||||
m.byHash[key] = fc
|
||||
m.byHeight = append(m.byHeight, fc)
|
||||
sort.Sort(m.byHeight)
|
||||
m.sorted = false
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByHeight returns the FullCommit for height h or an error if the commit is not found.
|
||||
func (m *memStoreProvider) GetByHeight(h int64) (FullCommit, error) {
|
||||
// By heuristics, GetByHeight with linearsearch is fast enough
|
||||
// for about 50 keys but after that, it needs binary search.
|
||||
// See https://github.com/tendermint/tendermint/pull/1043#issue-285188242
|
||||
m.mtx.RLock()
|
||||
defer m.mtx.RUnlock()
|
||||
n := len(m.byHeight)
|
||||
m.mtx.RUnlock()
|
||||
|
||||
if n <= 50 {
|
||||
return m.getByHeightLinearSearch(h)
|
||||
}
|
||||
return m.getByHeightBinarySearch(h)
|
||||
}
|
||||
|
||||
func (m *memStoreProvider) sortByHeightIfNecessaryLocked() {
|
||||
if !m.sorted {
|
||||
sort.Sort(m.byHeight)
|
||||
m.sorted = true
|
||||
}
|
||||
}
|
||||
|
||||
func (m *memStoreProvider) getByHeightLinearSearch(h int64) (FullCommit, error) {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
m.sortByHeightIfNecessaryLocked()
|
||||
// search from highest to lowest
|
||||
for i := len(m.byHeight) - 1; i >= 0; i-- {
|
||||
fc := m.byHeight[i]
|
||||
if fc.Height() <= h {
|
||||
if fc := m.byHeight[i]; fc.Height() <= h {
|
||||
return fc, nil
|
||||
}
|
||||
}
|
||||
return FullCommit{}, liteErr.ErrCommitNotFound()
|
||||
}
|
||||
|
||||
func (m *memStoreProvider) getByHeightBinarySearch(h int64) (FullCommit, error) {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
m.sortByHeightIfNecessaryLocked()
|
||||
low, high := 0, len(m.byHeight)-1
|
||||
var mid int
|
||||
var hmid int64
|
||||
var midFC FullCommit
|
||||
// Our goal is to either find:
|
||||
// * item ByHeight with the query
|
||||
// * greatest height with a height <= query
|
||||
for low <= high {
|
||||
mid = int(uint(low+high) >> 1) // Avoid an overflow
|
||||
midFC = m.byHeight[mid]
|
||||
hmid = midFC.Height()
|
||||
switch {
|
||||
case hmid == h:
|
||||
return midFC, nil
|
||||
case hmid < h:
|
||||
low = mid + 1
|
||||
case hmid > h:
|
||||
high = mid - 1
|
||||
}
|
||||
}
|
||||
|
||||
if high >= 0 {
|
||||
if highFC := m.byHeight[high]; highFC.Height() < h {
|
||||
return highFC, nil
|
||||
}
|
||||
}
|
||||
return FullCommit{}, liteErr.ErrCommitNotFound()
|
||||
}
|
||||
|
||||
// GetByHash returns the FullCommit for the hash or an error if the commit is not found.
|
||||
func (m *memStoreProvider) GetByHash(hash []byte) (FullCommit, error) {
|
||||
m.mtx.RLock()
|
||||
@@ -85,12 +140,13 @@ func (m *memStoreProvider) GetByHash(hash []byte) (FullCommit, error) {
|
||||
|
||||
// LatestCommit returns the latest FullCommit or an error if no commits exist.
|
||||
func (m *memStoreProvider) LatestCommit() (FullCommit, error) {
|
||||
m.mtx.RLock()
|
||||
defer m.mtx.RUnlock()
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
l := len(m.byHeight)
|
||||
if l == 0 {
|
||||
return FullCommit{}, liteErr.ErrCommitNotFound()
|
||||
}
|
||||
m.sortByHeightIfNecessaryLocked()
|
||||
return m.byHeight[l-1], nil
|
||||
}
|
||||
|
||||
@@ -1,33 +1,129 @@
|
||||
package lite_test
|
||||
package lite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/tendermint/tendermint/lite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
liteErr "github.com/tendermint/tendermint/lite/errors"
|
||||
)
|
||||
|
||||
func TestMemStoreProvidergetByHeightBinaryAndLinearSameResult(t *testing.T) {
|
||||
p := NewMemStoreProvider().(*memStoreProvider)
|
||||
|
||||
// Store a bunch of commits at specific heights
|
||||
// and then ensure that:
|
||||
// * getByHeightLinearSearch
|
||||
// * getByHeightBinarySearch
|
||||
// both return the exact same result
|
||||
|
||||
// 1. Non-existent height commits
|
||||
nonExistent := []int64{-1000, -1, 0, 1, 10, 11, 17, 31, 67, 1000, 1e9}
|
||||
ensureNonExistentCommitsAtHeight(t, "getByHeightLinearSearch", p.getByHeightLinearSearch, nonExistent)
|
||||
ensureNonExistentCommitsAtHeight(t, "getByHeightBinarySearch", p.getByHeightBinarySearch, nonExistent)
|
||||
|
||||
// 2. Save some known height commits
|
||||
knownHeights := []int64{0, 1, 7, 9, 12, 13, 18, 44, 23, 16, 1024, 100, 199, 1e9}
|
||||
createAndStoreCommits(t, p, knownHeights)
|
||||
|
||||
// 3. Now check if those heights are retrieved
|
||||
ensureExistentCommitsAtHeight(t, "getByHeightLinearSearch", p.getByHeightLinearSearch, knownHeights)
|
||||
ensureExistentCommitsAtHeight(t, "getByHeightBinarySearch", p.getByHeightBinarySearch, knownHeights)
|
||||
|
||||
// 4. And now for the height probing to ensure that any height
|
||||
// requested returns a fullCommit of height <= requestedHeight.
|
||||
comparegetByHeightAlgorithms(t, p, 0, 0)
|
||||
comparegetByHeightAlgorithms(t, p, 1, 1)
|
||||
comparegetByHeightAlgorithms(t, p, 2, 1)
|
||||
comparegetByHeightAlgorithms(t, p, 5, 1)
|
||||
comparegetByHeightAlgorithms(t, p, 7, 7)
|
||||
comparegetByHeightAlgorithms(t, p, 10, 9)
|
||||
comparegetByHeightAlgorithms(t, p, 12, 12)
|
||||
comparegetByHeightAlgorithms(t, p, 14, 13)
|
||||
comparegetByHeightAlgorithms(t, p, 19, 18)
|
||||
comparegetByHeightAlgorithms(t, p, 43, 23)
|
||||
comparegetByHeightAlgorithms(t, p, 45, 44)
|
||||
comparegetByHeightAlgorithms(t, p, 1025, 1024)
|
||||
comparegetByHeightAlgorithms(t, p, 101, 100)
|
||||
comparegetByHeightAlgorithms(t, p, 1e3, 199)
|
||||
comparegetByHeightAlgorithms(t, p, 1e4, 1024)
|
||||
comparegetByHeightAlgorithms(t, p, 1e9, 1e9)
|
||||
comparegetByHeightAlgorithms(t, p, 1e9+1, 1e9)
|
||||
}
|
||||
|
||||
func createAndStoreCommits(t *testing.T, p Provider, heights []int64) {
|
||||
chainID := "cache-best-height-binary-and-linear"
|
||||
appHash := []byte("0xdeadbeef")
|
||||
keys := GenValKeys(len(heights) / 2)
|
||||
|
||||
for _, h := range heights {
|
||||
vals := keys.ToValidators(10, int64(len(heights)/2))
|
||||
fc := keys.GenFullCommit(chainID, h, nil, vals, appHash, []byte("params"), []byte("results"), 0, 5)
|
||||
err := p.StoreCommit(fc)
|
||||
require.NoError(t, err, "StoreCommit height=%d", h)
|
||||
}
|
||||
}
|
||||
|
||||
func comparegetByHeightAlgorithms(t *testing.T, p *memStoreProvider, ask, expect int64) {
|
||||
algos := map[string]func(int64) (FullCommit, error){
|
||||
"getHeightByLinearSearch": p.getByHeightLinearSearch,
|
||||
"getHeightByBinarySearch": p.getByHeightBinarySearch,
|
||||
}
|
||||
|
||||
for algo, fn := range algos {
|
||||
fc, err := fn(ask)
|
||||
// t.Logf("%s got=%v want=%d", algo, expect, fc.Height())
|
||||
require.Nil(t, err, "%s: %+v", algo, err)
|
||||
if assert.Equal(t, expect, fc.Height()) {
|
||||
err = p.StoreCommit(fc)
|
||||
require.Nil(t, err, "%s: %+v", algo, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var blankFullCommit FullCommit
|
||||
|
||||
func ensureNonExistentCommitsAtHeight(t *testing.T, prefix string, fn func(int64) (FullCommit, error), data []int64) {
|
||||
for i, qh := range data {
|
||||
fc, err := fn(qh)
|
||||
assert.NotNil(t, err, "#%d: %s: height=%d should return non-nil error", i, prefix, qh)
|
||||
assert.Equal(t, fc, blankFullCommit, "#%d: %s: height=%d\ngot =%+v\nwant=%+v", i, prefix, qh, fc, blankFullCommit)
|
||||
}
|
||||
}
|
||||
|
||||
func ensureExistentCommitsAtHeight(t *testing.T, prefix string, fn func(int64) (FullCommit, error), data []int64) {
|
||||
for i, qh := range data {
|
||||
fc, err := fn(qh)
|
||||
assert.Nil(t, err, "#%d: %s: height=%d should not return an error: %v", i, prefix, qh, err)
|
||||
assert.NotEqual(t, fc, blankFullCommit, "#%d: %s: height=%d got a blankCommit", i, prefix, qh)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGenCommit20(b *testing.B) {
|
||||
keys := lite.GenValKeys(20)
|
||||
keys := GenValKeys(20)
|
||||
benchmarkGenCommit(b, keys)
|
||||
}
|
||||
|
||||
func BenchmarkGenCommit100(b *testing.B) {
|
||||
keys := lite.GenValKeys(100)
|
||||
keys := GenValKeys(100)
|
||||
benchmarkGenCommit(b, keys)
|
||||
}
|
||||
|
||||
func BenchmarkGenCommitSec20(b *testing.B) {
|
||||
keys := lite.GenSecpValKeys(20)
|
||||
keys := GenSecpValKeys(20)
|
||||
benchmarkGenCommit(b, keys)
|
||||
}
|
||||
|
||||
func BenchmarkGenCommitSec100(b *testing.B) {
|
||||
keys := lite.GenSecpValKeys(100)
|
||||
keys := GenSecpValKeys(100)
|
||||
benchmarkGenCommit(b, keys)
|
||||
}
|
||||
|
||||
func benchmarkGenCommit(b *testing.B, keys lite.ValKeys) {
|
||||
func benchmarkGenCommit(b *testing.B, keys ValKeys) {
|
||||
chainID := fmt.Sprintf("bench-%d", len(keys))
|
||||
vals := keys.ToValidators(20, 10)
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -40,7 +136,7 @@ func benchmarkGenCommit(b *testing.B, keys lite.ValKeys) {
|
||||
|
||||
// this benchmarks generating one key
|
||||
func BenchmarkGenValKeys(b *testing.B) {
|
||||
keys := lite.GenValKeys(20)
|
||||
keys := GenValKeys(20)
|
||||
for i := 0; i < b.N; i++ {
|
||||
keys = keys.Extend(1)
|
||||
}
|
||||
@@ -48,7 +144,7 @@ func BenchmarkGenValKeys(b *testing.B) {
|
||||
|
||||
// this benchmarks generating one key
|
||||
func BenchmarkGenSecpValKeys(b *testing.B) {
|
||||
keys := lite.GenSecpValKeys(20)
|
||||
keys := GenSecpValKeys(20)
|
||||
for i := 0; i < b.N; i++ {
|
||||
keys = keys.Extend(1)
|
||||
}
|
||||
@@ -64,7 +160,7 @@ func BenchmarkToValidators100(b *testing.B) {
|
||||
|
||||
// this benchmarks constructing the validator set (.PubKey() * nodes)
|
||||
func benchmarkToValidators(b *testing.B, nodes int) {
|
||||
keys := lite.GenValKeys(nodes)
|
||||
keys := GenValKeys(nodes)
|
||||
for i := 1; i <= b.N; i++ {
|
||||
keys.ToValidators(int64(2*i), int64(i))
|
||||
}
|
||||
@@ -76,36 +172,36 @@ func BenchmarkToValidatorsSec100(b *testing.B) {
|
||||
|
||||
// this benchmarks constructing the validator set (.PubKey() * nodes)
|
||||
func benchmarkToValidatorsSec(b *testing.B, nodes int) {
|
||||
keys := lite.GenSecpValKeys(nodes)
|
||||
keys := GenSecpValKeys(nodes)
|
||||
for i := 1; i <= b.N; i++ {
|
||||
keys.ToValidators(int64(2*i), int64(i))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCertifyCommit20(b *testing.B) {
|
||||
keys := lite.GenValKeys(20)
|
||||
keys := GenValKeys(20)
|
||||
benchmarkCertifyCommit(b, keys)
|
||||
}
|
||||
|
||||
func BenchmarkCertifyCommit100(b *testing.B) {
|
||||
keys := lite.GenValKeys(100)
|
||||
keys := GenValKeys(100)
|
||||
benchmarkCertifyCommit(b, keys)
|
||||
}
|
||||
|
||||
func BenchmarkCertifyCommitSec20(b *testing.B) {
|
||||
keys := lite.GenSecpValKeys(20)
|
||||
keys := GenSecpValKeys(20)
|
||||
benchmarkCertifyCommit(b, keys)
|
||||
}
|
||||
|
||||
func BenchmarkCertifyCommitSec100(b *testing.B) {
|
||||
keys := lite.GenSecpValKeys(100)
|
||||
keys := GenSecpValKeys(100)
|
||||
benchmarkCertifyCommit(b, keys)
|
||||
}
|
||||
|
||||
func benchmarkCertifyCommit(b *testing.B, keys lite.ValKeys) {
|
||||
func benchmarkCertifyCommit(b *testing.B, keys ValKeys) {
|
||||
chainID := "bench-certify"
|
||||
vals := keys.ToValidators(20, 10)
|
||||
cert := lite.NewStaticCertifier(chainID, vals)
|
||||
cert := NewStaticCertifier(chainID, vals)
|
||||
check := keys.GenCommit(chainID, 123, nil, vals, []byte("foo"), []byte("params"), []byte("res"), 0, len(keys))
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := cert.Certify(check)
|
||||
@@ -115,3 +211,155 @@ func benchmarkCertifyCommit(b *testing.B, keys lite.ValKeys) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type algo bool
|
||||
|
||||
const (
|
||||
linearSearch = true
|
||||
binarySearch = false
|
||||
)
|
||||
|
||||
// Lazy load the commits
|
||||
var fcs5, fcs50, fcs100, fcs500, fcs1000 []FullCommit
|
||||
var h5, h50, h100, h500, h1000 []int64
|
||||
var commitsOnce sync.Once
|
||||
|
||||
func lazyGenerateFullCommits(b *testing.B) {
|
||||
b.Logf("Generating FullCommits")
|
||||
commitsOnce.Do(func() {
|
||||
fcs5, h5 = genFullCommits(nil, nil, 5)
|
||||
b.Logf("Generated 5 FullCommits")
|
||||
fcs50, h50 = genFullCommits(fcs5, h5, 50)
|
||||
b.Logf("Generated 50 FullCommits")
|
||||
fcs100, h100 = genFullCommits(fcs50, h50, 100)
|
||||
b.Logf("Generated 100 FullCommits")
|
||||
fcs500, h500 = genFullCommits(fcs100, h100, 500)
|
||||
b.Logf("Generated 500 FullCommits")
|
||||
fcs1000, h1000 = genFullCommits(fcs500, h500, 1000)
|
||||
b.Logf("Generated 1000 FullCommits")
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkMemStoreProviderGetByHeightLinearSearch5(b *testing.B) {
|
||||
benchmarkMemStoreProvidergetByHeight(b, fcs5, h5, linearSearch)
|
||||
}
|
||||
|
||||
func BenchmarkMemStoreProviderGetByHeightLinearSearch50(b *testing.B) {
|
||||
benchmarkMemStoreProvidergetByHeight(b, fcs50, h50, linearSearch)
|
||||
}
|
||||
|
||||
func BenchmarkMemStoreProviderGetByHeightLinearSearch100(b *testing.B) {
|
||||
benchmarkMemStoreProvidergetByHeight(b, fcs100, h100, linearSearch)
|
||||
}
|
||||
|
||||
func BenchmarkMemStoreProviderGetByHeightLinearSearch500(b *testing.B) {
|
||||
benchmarkMemStoreProvidergetByHeight(b, fcs500, h500, linearSearch)
|
||||
}
|
||||
|
||||
func BenchmarkMemStoreProviderGetByHeightLinearSearch1000(b *testing.B) {
|
||||
benchmarkMemStoreProvidergetByHeight(b, fcs1000, h1000, linearSearch)
|
||||
}
|
||||
|
||||
func BenchmarkMemStoreProviderGetByHeightBinarySearch5(b *testing.B) {
|
||||
benchmarkMemStoreProvidergetByHeight(b, fcs5, h5, binarySearch)
|
||||
}
|
||||
|
||||
func BenchmarkMemStoreProviderGetByHeightBinarySearch50(b *testing.B) {
|
||||
benchmarkMemStoreProvidergetByHeight(b, fcs50, h50, binarySearch)
|
||||
}
|
||||
|
||||
func BenchmarkMemStoreProviderGetByHeightBinarySearch100(b *testing.B) {
|
||||
benchmarkMemStoreProvidergetByHeight(b, fcs100, h100, binarySearch)
|
||||
}
|
||||
|
||||
func BenchmarkMemStoreProviderGetByHeightBinarySearch500(b *testing.B) {
|
||||
benchmarkMemStoreProvidergetByHeight(b, fcs500, h500, binarySearch)
|
||||
}
|
||||
|
||||
func BenchmarkMemStoreProviderGetByHeightBinarySearch1000(b *testing.B) {
|
||||
benchmarkMemStoreProvidergetByHeight(b, fcs1000, h1000, binarySearch)
|
||||
}
|
||||
|
||||
var rng = rand.New(rand.NewSource(10))
|
||||
|
||||
func benchmarkMemStoreProvidergetByHeight(b *testing.B, fcs []FullCommit, fHeights []int64, algo algo) {
|
||||
lazyGenerateFullCommits(b)
|
||||
|
||||
b.StopTimer()
|
||||
mp := NewMemStoreProvider()
|
||||
for i, fc := range fcs {
|
||||
if err := mp.StoreCommit(fc); err != nil {
|
||||
b.Fatalf("FullCommit #%d: err: %v", i, err)
|
||||
}
|
||||
}
|
||||
qHeights := make([]int64, len(fHeights))
|
||||
copy(qHeights, fHeights)
|
||||
// Append some non-existent heights to trigger the worst cases.
|
||||
qHeights = append(qHeights, 19, -100, -10000, 1e7, -17, 31, -1e9)
|
||||
|
||||
memP := mp.(*memStoreProvider)
|
||||
searchFn := memP.getByHeightLinearSearch
|
||||
if algo == binarySearch { // nolint
|
||||
searchFn = memP.getByHeightBinarySearch
|
||||
}
|
||||
|
||||
hPerm := rng.Perm(len(qHeights))
|
||||
b.StartTimer()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, j := range hPerm {
|
||||
h := qHeights[j]
|
||||
if _, err := searchFn(h); err != nil {
|
||||
}
|
||||
}
|
||||
}
|
||||
b.ReportAllocs()
|
||||
}
|
||||
|
||||
func genFullCommits(prevFC []FullCommit, prevH []int64, want int) ([]FullCommit, []int64) {
|
||||
fcs := make([]FullCommit, len(prevFC))
|
||||
copy(fcs, prevFC)
|
||||
heights := make([]int64, len(prevH))
|
||||
copy(heights, prevH)
|
||||
|
||||
appHash := []byte("benchmarks")
|
||||
chainID := "benchmarks-gen-full-commits"
|
||||
n := want
|
||||
keys := GenValKeys(2 + (n / 3))
|
||||
for i := 0; i < n; i++ {
|
||||
vals := keys.ToValidators(10, int64(n/2))
|
||||
h := int64(20 + 10*i)
|
||||
fcs = append(fcs, keys.GenFullCommit(chainID, h, nil, vals, appHash, []byte("params"), []byte("results"), 0, 5))
|
||||
heights = append(heights, h)
|
||||
}
|
||||
return fcs, heights
|
||||
}
|
||||
|
||||
func TestMemStoreProviderLatestCommitAlwaysUsesSorted(t *testing.T) {
|
||||
p := NewMemStoreProvider().(*memStoreProvider)
|
||||
// 1. With no commits yet stored, it should return ErrCommitNotFound
|
||||
got, err := p.LatestCommit()
|
||||
require.Equal(t, err.Error(), liteErr.ErrCommitNotFound().Error(), "should return ErrCommitNotFound()")
|
||||
require.Equal(t, got, blankFullCommit, "With no fullcommits, it should return a blank FullCommit")
|
||||
|
||||
// 2. Generate some full commits now and we'll add them unsorted.
|
||||
genAndStoreCommitsOfHeight(t, p, 27, 100, 1, 12, 1000, 17, 91)
|
||||
fc, err := p.LatestCommit()
|
||||
require.Nil(t, err, "with commits saved no error expected")
|
||||
require.NotEqual(t, fc, blankFullCommit, "with commits saved no blank FullCommit")
|
||||
require.Equal(t, fc.Height(), int64(1000), "the latest commit i.e. the largest expected")
|
||||
}
|
||||
|
||||
func genAndStoreCommitsOfHeight(t *testing.T, p Provider, heights ...int64) {
|
||||
n := len(heights)
|
||||
appHash := []byte("tests")
|
||||
chainID := "tests-gen-full-commits"
|
||||
keys := GenValKeys(2 + (n / 3))
|
||||
for i := 0; i < n; i++ {
|
||||
h := heights[i]
|
||||
vals := keys.ToValidators(10, int64(n/2))
|
||||
fc := keys.GenFullCommit(chainID, h, nil, vals, appHash, []byte("params"), []byte("results"), 0, 5)
|
||||
err := p.StoreCommit(fc)
|
||||
require.NoError(t, err, "StoreCommit height=%d", h)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,10 +103,10 @@ func checkProvider(t *testing.T, p lite.Provider, chainID, app string) {
|
||||
// this will make a get height, and if it is good, set the data as well
|
||||
func checkGetHeight(t *testing.T, p lite.Provider, ask, expect int64) {
|
||||
fc, err := p.GetByHeight(ask)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
require.Nil(t, err, "GetByHeight")
|
||||
if assert.Equal(t, expect, fc.Height()) {
|
||||
err = p.StoreCommit(fc)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
require.Nil(t, err, "StoreCommit")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/abci/example/dummy"
|
||||
"github.com/tendermint/abci/example/kvstore"
|
||||
|
||||
"github.com/tendermint/tendermint/lite"
|
||||
certclient "github.com/tendermint/tendermint/lite/client"
|
||||
@@ -23,7 +23,7 @@ var node *nm.Node
|
||||
// TODO fix tests!!
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
app := dummy.NewDummyApplication()
|
||||
app := kvstore.NewKVStoreApplication()
|
||||
|
||||
node = rpctest.StartTendermint(app)
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestMain(m *testing.M) {
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func dummyTx(k, v []byte) []byte {
|
||||
func kvstoreTx(k, v []byte) []byte {
|
||||
return []byte(fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func _TestAppProofs(t *testing.T) {
|
||||
k := []byte("my-key")
|
||||
v := []byte("my-value")
|
||||
|
||||
tx := dummyTx(k, v)
|
||||
tx := kvstoreTx(k, v)
|
||||
br, err := cl.BroadcastTxCommit(tx)
|
||||
require.NoError(err, "%+v", err)
|
||||
require.EqualValues(0, br.CheckTx.Code, "%#v", br.CheckTx)
|
||||
@@ -107,7 +107,7 @@ func _TestTxProofs(t *testing.T) {
|
||||
cl := client.NewLocal(node)
|
||||
client.WaitForHeight(cl, 1, nil)
|
||||
|
||||
tx := dummyTx([]byte("key-a"), []byte("value-a"))
|
||||
tx := kvstoreTx([]byte("key-a"), []byte("value-a"))
|
||||
br, err := cl.BroadcastTxCommit(tx)
|
||||
require.NoError(err, "%+v", err)
|
||||
require.EqualValues(0, br.CheckTx.Code, "%#v", br.CheckTx)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/abci/example/counter"
|
||||
"github.com/tendermint/abci/example/dummy"
|
||||
"github.com/tendermint/abci/example/kvstore"
|
||||
abci "github.com/tendermint/abci/types"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
@@ -73,7 +73,7 @@ func checkTxs(t *testing.T, mempool *Mempool, count int) types.Txs {
|
||||
}
|
||||
|
||||
func TestTxsAvailable(t *testing.T) {
|
||||
app := dummy.NewDummyApplication()
|
||||
app := kvstore.NewKVStoreApplication()
|
||||
cc := proxy.NewLocalClientCreator(app)
|
||||
mempool := newMempoolWithApp(cc)
|
||||
mempool.EnableTxsAvailable()
|
||||
@@ -238,7 +238,7 @@ func TestMempoolCloseWAL(t *testing.T) {
|
||||
// 3. Create the mempool
|
||||
wcfg := cfg.DefaultMempoolConfig()
|
||||
wcfg.RootDir = rootDir
|
||||
app := dummy.NewDummyApplication()
|
||||
app := kvstore.NewKVStoreApplication()
|
||||
cc := proxy.NewLocalClientCreator(app)
|
||||
appConnMem, _ := cc.NewABCIClient()
|
||||
mempool := NewMempool(wcfg, appConnMem, 10)
|
||||
|
||||
@@ -73,7 +73,8 @@ func (memR *MempoolReactor) RemovePeer(peer p2p.Peer, reason interface{}) {
|
||||
func (memR *MempoolReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) {
|
||||
_, msg, err := DecodeMessage(msgBytes)
|
||||
if err != nil {
|
||||
memR.Logger.Error("Error decoding message", "err", err)
|
||||
memR.Logger.Error("Error decoding message", "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes)
|
||||
memR.Switch.StopPeerForError(src, err)
|
||||
return
|
||||
}
|
||||
memR.Logger.Debug("Receive", "src", src, "chId", chID, "msg", msg)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
"github.com/go-kit/kit/log/term"
|
||||
|
||||
"github.com/tendermint/abci/example/dummy"
|
||||
"github.com/tendermint/abci/example/kvstore"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
@@ -39,7 +39,7 @@ func makeAndConnectMempoolReactors(config *cfg.Config, N int) []*MempoolReactor
|
||||
reactors := make([]*MempoolReactor, N)
|
||||
logger := mempoolLogger()
|
||||
for i := 0; i < N; i++ {
|
||||
app := dummy.NewDummyApplication()
|
||||
app := kvstore.NewKVStoreApplication()
|
||||
cc := proxy.NewLocalClientCreator(app)
|
||||
mempool := newMempoolWithApp(cc)
|
||||
|
||||
|
||||
51
node/node.go
51
node/node.go
@@ -34,6 +34,7 @@ import (
|
||||
"github.com/tendermint/tendermint/state/txindex/kv"
|
||||
"github.com/tendermint/tendermint/state/txindex/null"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
priv_val "github.com/tendermint/tendermint/types/priv_validator"
|
||||
"github.com/tendermint/tendermint/version"
|
||||
|
||||
_ "net/http/pprof"
|
||||
@@ -82,7 +83,8 @@ func DefaultNewNode(config *cfg.Config, logger log.Logger) (*Node, error) {
|
||||
proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()),
|
||||
DefaultGenesisDocProviderFunc(config),
|
||||
DefaultDBProvider,
|
||||
logger)
|
||||
logger,
|
||||
)
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -160,7 +162,7 @@ func NewNode(config *cfg.Config,
|
||||
// and sync tendermint and the app by performing a handshake
|
||||
// and replaying any necessary blocks
|
||||
consensusLogger := logger.With("module", "consensus")
|
||||
handshaker := cs.NewHandshaker(stateDB, state, blockStore)
|
||||
handshaker := cs.NewHandshaker(stateDB, state, blockStore, genDoc.AppState())
|
||||
handshaker.SetLogger(consensusLogger)
|
||||
proxyApp := proxy.NewAppConns(clientCreator, handshaker)
|
||||
proxyApp.SetLogger(logger.With("module", "proxy"))
|
||||
@@ -171,6 +173,27 @@ func NewNode(config *cfg.Config,
|
||||
// reload the state (it may have been updated by the handshake)
|
||||
state = sm.LoadState(stateDB)
|
||||
|
||||
// If an address is provided, listen on the socket for a
|
||||
// connection from an external signing process.
|
||||
if config.PrivValidatorListenAddr != "" {
|
||||
var (
|
||||
// TODO: persist this key so external signer
|
||||
// can actually authenticate us
|
||||
privKey = crypto.GenPrivKeyEd25519()
|
||||
pvsc = priv_val.NewSocketClient(
|
||||
logger.With("module", "priv_val"),
|
||||
config.PrivValidatorListenAddr,
|
||||
privKey,
|
||||
)
|
||||
)
|
||||
|
||||
if err := pvsc.Start(); err != nil {
|
||||
return nil, fmt.Errorf("Error starting private validator client: %v", err)
|
||||
}
|
||||
|
||||
privValidator = pvsc
|
||||
}
|
||||
|
||||
// Decide whether to fast-sync or not
|
||||
// We don't fast-sync when the only validator is us.
|
||||
fastSync := config.FastSync
|
||||
@@ -258,12 +281,21 @@ func NewNode(config *cfg.Config,
|
||||
if config.P2P.Seeds != "" {
|
||||
seeds = strings.Split(config.P2P.Seeds, ",")
|
||||
}
|
||||
var privatePeerIDs []string
|
||||
if config.P2P.PrivatePeerIDs != "" {
|
||||
privatePeerIDs = strings.Split(config.P2P.PrivatePeerIDs, ",")
|
||||
}
|
||||
pexReactor := pex.NewPEXReactor(addrBook,
|
||||
&pex.PEXReactorConfig{Seeds: seeds, SeedMode: config.P2P.SeedMode})
|
||||
&pex.PEXReactorConfig{
|
||||
Seeds: seeds,
|
||||
SeedMode: config.P2P.SeedMode,
|
||||
PrivatePeerIDs: privatePeerIDs})
|
||||
pexReactor.SetLogger(p2pLogger)
|
||||
sw.AddReactor("PEX", pexReactor)
|
||||
}
|
||||
|
||||
sw.SetAddrBook(addrBook)
|
||||
|
||||
// Filter peers by addr or pubkey with an ABCI query.
|
||||
// If the query return code is OK, add peer.
|
||||
// XXX: Query format subject to change
|
||||
@@ -279,8 +311,8 @@ func NewNode(config *cfg.Config,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
sw.SetPubKeyFilter(func(pubkey crypto.PubKey) error {
|
||||
resQuery, err := proxyApp.Query().QuerySync(abci.RequestQuery{Path: cmn.Fmt("/p2p/filter/pubkey/%X", pubkey.Bytes())})
|
||||
sw.SetIDFilter(func(id p2p.ID) error {
|
||||
resQuery, err := proxyApp.Query().QuerySync(abci.RequestQuery{Path: cmn.Fmt("/p2p/filter/pubkey/%s", id)})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -375,7 +407,7 @@ func (n *Node) OnStart() error {
|
||||
n.sw.AddListener(l)
|
||||
|
||||
// Generate node PrivKey
|
||||
// TODO: pass in like priv_val
|
||||
// TODO: pass in like privValidator
|
||||
nodeKey, err := p2p.LoadOrGenNodeKey(n.config.NodeKeyFile())
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -418,8 +450,13 @@ func (n *Node) OnStop() {
|
||||
}
|
||||
|
||||
n.eventBus.Stop()
|
||||
|
||||
n.indexerService.Stop()
|
||||
|
||||
if pvsc, ok := n.privValidator.(*priv_val.SocketClient); ok {
|
||||
if err := pvsc.Stop(); err != nil {
|
||||
n.Logger.Error("Error stopping priv validator socket client", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RunForever waits for an interrupt signal and stops the node.
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
)
|
||||
|
||||
type dummyConn struct {
|
||||
type kvstoreConn struct {
|
||||
*io.PipeReader
|
||||
*io.PipeWriter
|
||||
}
|
||||
|
||||
func (drw dummyConn) Close() (err error) {
|
||||
func (drw kvstoreConn) Close() (err error) {
|
||||
err2 := drw.PipeWriter.CloseWithError(io.EOF)
|
||||
err1 := drw.PipeReader.Close()
|
||||
if err2 != nil {
|
||||
@@ -23,14 +23,14 @@ func (drw dummyConn) Close() (err error) {
|
||||
}
|
||||
|
||||
// Each returned ReadWriteCloser is akin to a net.Connection
|
||||
func makeDummyConnPair() (fooConn, barConn dummyConn) {
|
||||
func makeKVStoreConnPair() (fooConn, barConn kvstoreConn) {
|
||||
barReader, fooWriter := io.Pipe()
|
||||
fooReader, barWriter := io.Pipe()
|
||||
return dummyConn{fooReader, fooWriter}, dummyConn{barReader, barWriter}
|
||||
return kvstoreConn{fooReader, fooWriter}, kvstoreConn{barReader, barWriter}
|
||||
}
|
||||
|
||||
func makeSecretConnPair(tb testing.TB) (fooSecConn, barSecConn *SecretConnection) {
|
||||
fooConn, barConn := makeDummyConnPair()
|
||||
fooConn, barConn := makeKVStoreConnPair()
|
||||
fooPrvKey := crypto.GenPrivKeyEd25519().Wrap()
|
||||
fooPubKey := fooPrvKey.PubKey()
|
||||
barPrvKey := crypto.GenPrivKeyEd25519().Wrap()
|
||||
@@ -78,7 +78,7 @@ func TestSecretConnectionHandshake(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSecretConnectionReadWrite(t *testing.T) {
|
||||
fooConn, barConn := makeDummyConnPair()
|
||||
fooConn, barConn := makeKVStoreConnPair()
|
||||
fooWrites, barWrites := []string{}, []string{}
|
||||
fooReads, barReads := []string{}, []string{}
|
||||
|
||||
@@ -89,7 +89,7 @@ func TestSecretConnectionReadWrite(t *testing.T) {
|
||||
}
|
||||
|
||||
// A helper that will run with (fooConn, fooWrites, fooReads) and vice versa
|
||||
genNodeRunner := func(nodeConn dummyConn, nodeWrites []string, nodeReads *[]string) func() {
|
||||
genNodeRunner := func(nodeConn kvstoreConn, nodeWrites []string, nodeReads *[]string) func() {
|
||||
return func() {
|
||||
// Node handskae
|
||||
nodePrvKey := crypto.GenPrivKeyEd25519().Wrap()
|
||||
|
||||
72
p2p/dummy/peer.go
Normal file
72
p2p/dummy/peer.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package dummy
|
||||
|
||||
import (
|
||||
p2p "github.com/tendermint/tendermint/p2p"
|
||||
tmconn "github.com/tendermint/tendermint/p2p/conn"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
)
|
||||
|
||||
type peer struct {
|
||||
cmn.BaseService
|
||||
kv map[string]interface{}
|
||||
}
|
||||
|
||||
var _ p2p.Peer = (*peer)(nil)
|
||||
|
||||
// NewPeer creates new dummy peer.
|
||||
func NewPeer() *peer {
|
||||
p := &peer{
|
||||
kv: make(map[string]interface{}),
|
||||
}
|
||||
p.BaseService = *cmn.NewBaseService(nil, "peer", p)
|
||||
return p
|
||||
}
|
||||
|
||||
// ID always returns dummy.
|
||||
func (p *peer) ID() p2p.ID {
|
||||
return p2p.ID("dummy")
|
||||
}
|
||||
|
||||
// IsOutbound always returns false.
|
||||
func (p *peer) IsOutbound() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsPersistent always returns false.
|
||||
func (p *peer) IsPersistent() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// NodeInfo always returns empty node info.
|
||||
func (p *peer) NodeInfo() p2p.NodeInfo {
|
||||
return p2p.NodeInfo{}
|
||||
}
|
||||
|
||||
// Status always returns empry connection status.
|
||||
func (p *peer) Status() tmconn.ConnectionStatus {
|
||||
return tmconn.ConnectionStatus{}
|
||||
}
|
||||
|
||||
// Send does not do anything and just returns true.
|
||||
func (p *peer) Send(byte, interface{}) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// TrySend does not do anything and just returns true.
|
||||
func (p *peer) TrySend(byte, interface{}) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Set records value under key specified in the map.
|
||||
func (p *peer) Set(key string, value interface{}) {
|
||||
p.kv[key] = value
|
||||
}
|
||||
|
||||
// Get returns a value associated with the key. Nil is returned if no value
|
||||
// found.
|
||||
func (p *peer) Get(key string) interface{} {
|
||||
if value, ok := p.kv[key]; ok {
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -34,15 +34,10 @@ type NodeInfo struct {
|
||||
}
|
||||
|
||||
// Validate checks the self-reported NodeInfo is safe.
|
||||
// It returns an error if the info.PubKey doesn't match the given pubKey,
|
||||
// or if there are too many Channels or any duplicate Channels.
|
||||
// It returns an error if there
|
||||
// are too many Channels or any duplicate Channels.
|
||||
// TODO: constraints for Moniker/Other? Or is that for the UI ?
|
||||
func (info NodeInfo) Validate(pubKey crypto.PubKey) error {
|
||||
if !info.PubKey.Equals(pubKey) {
|
||||
return fmt.Errorf("info.PubKey (%v) doesn't match peer.PubKey (%v)",
|
||||
info.PubKey, pubKey)
|
||||
}
|
||||
|
||||
func (info NodeInfo) Validate() error {
|
||||
if len(info.Channels) > maxNumChannels {
|
||||
return fmt.Errorf("info.Channels is too long (%v). Max is %v", len(info.Channels), maxNumChannels)
|
||||
}
|
||||
|
||||
164
p2p/peer.go
164
p2p/peer.go
@@ -34,23 +34,53 @@ type Peer interface {
|
||||
|
||||
//----------------------------------------------------------
|
||||
|
||||
// peerConn contains the raw connection and its config.
|
||||
type peerConn struct {
|
||||
outbound bool
|
||||
persistent bool
|
||||
config *PeerConfig
|
||||
conn net.Conn // source connection
|
||||
}
|
||||
|
||||
// ID only exists for SecretConnection.
|
||||
// NOTE: Will panic if conn is not *SecretConnection.
|
||||
func (pc peerConn) ID() ID {
|
||||
return PubKeyToID(pc.conn.(*tmconn.SecretConnection).RemotePubKey())
|
||||
}
|
||||
|
||||
// peer implements Peer.
|
||||
//
|
||||
// Before using a peer, you will need to perform a handshake on connection.
|
||||
type peer struct {
|
||||
cmn.BaseService
|
||||
|
||||
outbound bool
|
||||
// raw peerConn and the multiplex connection
|
||||
peerConn
|
||||
mconn *tmconn.MConnection
|
||||
|
||||
conn net.Conn // source connection
|
||||
mconn *tmconn.MConnection // multiplex connection
|
||||
// peer's node info and the channel it knows about
|
||||
// channels = nodeInfo.Channels
|
||||
// cached to avoid copying nodeInfo in hasChannel
|
||||
nodeInfo NodeInfo
|
||||
channels []byte
|
||||
|
||||
persistent bool
|
||||
config *PeerConfig
|
||||
// User data
|
||||
Data *cmn.CMap
|
||||
}
|
||||
|
||||
nodeInfo NodeInfo // peer's node info
|
||||
channels []byte // channels the peer knows about
|
||||
Data *cmn.CMap // User data.
|
||||
func newPeer(pc peerConn, nodeInfo NodeInfo,
|
||||
reactorsByCh map[byte]Reactor, chDescs []*tmconn.ChannelDescriptor,
|
||||
onPeerError func(Peer, interface{})) *peer {
|
||||
|
||||
p := &peer{
|
||||
peerConn: pc,
|
||||
nodeInfo: nodeInfo,
|
||||
channels: nodeInfo.Channels,
|
||||
Data: cmn.NewCMap(),
|
||||
}
|
||||
p.mconn = createMConnection(pc.conn, p, reactorsByCh, chDescs, onPeerError, pc.config.MConfig)
|
||||
p.BaseService = *cmn.NewBaseService(nil, "Peer", p)
|
||||
return p
|
||||
}
|
||||
|
||||
// PeerConfig is a Peer configuration.
|
||||
@@ -79,36 +109,42 @@ func DefaultPeerConfig() *PeerConfig {
|
||||
}
|
||||
}
|
||||
|
||||
func newOutboundPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*tmconn.ChannelDescriptor,
|
||||
onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKey, config *PeerConfig, persistent bool) (*peer, error) {
|
||||
func newOutboundPeerConn(addr *NetAddress, config *PeerConfig, persistent bool, ourNodePrivKey crypto.PrivKey) (peerConn, error) {
|
||||
var pc peerConn
|
||||
|
||||
conn, err := dial(addr, config)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error creating peer")
|
||||
return pc, errors.Wrap(err, "Error creating peer")
|
||||
}
|
||||
|
||||
peer, err := newPeerFromConnAndConfig(conn, true, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, config)
|
||||
pc, err = newPeerConn(conn, config, true, persistent, ourNodePrivKey)
|
||||
if err != nil {
|
||||
if err := conn.Close(); err != nil {
|
||||
return nil, err
|
||||
if err2 := conn.Close(); err2 != nil {
|
||||
return pc, errors.Wrap(err, err2.Error())
|
||||
}
|
||||
return nil, err
|
||||
return pc, err
|
||||
}
|
||||
peer.persistent = persistent
|
||||
|
||||
return peer, nil
|
||||
// ensure dialed ID matches connection ID
|
||||
if config.AuthEnc && addr.ID != pc.ID() {
|
||||
if err2 := conn.Close(); err2 != nil {
|
||||
return pc, errors.Wrap(err, err2.Error())
|
||||
}
|
||||
return pc, ErrSwitchAuthenticationFailure{addr, pc.ID()}
|
||||
}
|
||||
return pc, nil
|
||||
}
|
||||
|
||||
func newInboundPeer(conn net.Conn, reactorsByCh map[byte]Reactor, chDescs []*tmconn.ChannelDescriptor,
|
||||
onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKey, config *PeerConfig) (*peer, error) {
|
||||
func newInboundPeerConn(conn net.Conn, config *PeerConfig, ourNodePrivKey crypto.PrivKey) (peerConn, error) {
|
||||
|
||||
// TODO: issue PoW challenge
|
||||
|
||||
return newPeerFromConnAndConfig(conn, false, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, config)
|
||||
return newPeerConn(conn, config, false, false, ourNodePrivKey)
|
||||
}
|
||||
|
||||
func newPeerFromConnAndConfig(rawConn net.Conn, outbound bool, reactorsByCh map[byte]Reactor, chDescs []*tmconn.ChannelDescriptor,
|
||||
onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKey, config *PeerConfig) (*peer, error) {
|
||||
func newPeerConn(rawConn net.Conn,
|
||||
config *PeerConfig, outbound, persistent bool,
|
||||
ourNodePrivKey crypto.PrivKey) (pc peerConn, err error) {
|
||||
|
||||
conn := rawConn
|
||||
|
||||
@@ -118,32 +154,26 @@ func newPeerFromConnAndConfig(rawConn net.Conn, outbound bool, reactorsByCh map[
|
||||
conn = FuzzConnAfterFromConfig(conn, 10*time.Second, config.FuzzConfig)
|
||||
}
|
||||
|
||||
// Encrypt connection
|
||||
if config.AuthEnc {
|
||||
// Set deadline for secret handshake
|
||||
if err := conn.SetDeadline(time.Now().Add(config.HandshakeTimeout * time.Second)); err != nil {
|
||||
return nil, errors.Wrap(err, "Error setting deadline while encrypting connection")
|
||||
return pc, errors.Wrap(err, "Error setting deadline while encrypting connection")
|
||||
}
|
||||
|
||||
var err error
|
||||
// Encrypt connection
|
||||
conn, err = tmconn.MakeSecretConnection(conn, ourNodePrivKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error creating peer")
|
||||
return pc, errors.Wrap(err, "Error creating peer")
|
||||
}
|
||||
}
|
||||
|
||||
// NodeInfo is set after Handshake
|
||||
p := &peer{
|
||||
outbound: outbound,
|
||||
conn: conn,
|
||||
config: config,
|
||||
Data: cmn.NewCMap(),
|
||||
}
|
||||
|
||||
p.mconn = createMConnection(conn, p, reactorsByCh, chDescs, onPeerError, config.MConfig)
|
||||
|
||||
p.BaseService = *cmn.NewBaseService(nil, "Peer", p)
|
||||
|
||||
return p, nil
|
||||
// Only the information we already have
|
||||
return peerConn{
|
||||
config: config,
|
||||
outbound: outbound,
|
||||
persistent: persistent,
|
||||
conn: conn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
//---------------------------------------------------
|
||||
@@ -175,17 +205,17 @@ func (p *peer) OnStop() {
|
||||
|
||||
// ID returns the peer's ID - the hex encoded hash of its pubkey.
|
||||
func (p *peer) ID() ID {
|
||||
return PubKeyToID(p.PubKey())
|
||||
return p.nodeInfo.ID()
|
||||
}
|
||||
|
||||
// IsOutbound returns true if the connection is outbound, false otherwise.
|
||||
func (p *peer) IsOutbound() bool {
|
||||
return p.outbound
|
||||
return p.peerConn.outbound
|
||||
}
|
||||
|
||||
// IsPersistent returns true if the peer is persitent, false otherwise.
|
||||
func (p *peer) IsPersistent() bool {
|
||||
return p.persistent
|
||||
return p.peerConn.persistent
|
||||
}
|
||||
|
||||
// NodeInfo returns a copy of the peer's NodeInfo.
|
||||
@@ -250,68 +280,48 @@ func (p *peer) hasChannel(chID byte) bool {
|
||||
// methods used by the Switch
|
||||
|
||||
// CloseConn should be called by the Switch if the peer was created but never started.
|
||||
func (p *peer) CloseConn() {
|
||||
p.conn.Close() // nolint: errcheck
|
||||
func (pc *peerConn) CloseConn() {
|
||||
pc.conn.Close() // nolint: errcheck
|
||||
}
|
||||
|
||||
// HandshakeTimeout performs the Tendermint P2P handshake between a given node and the peer
|
||||
// by exchanging their NodeInfo. It sets the received nodeInfo on the peer.
|
||||
// NOTE: blocking
|
||||
func (p *peer) HandshakeTimeout(ourNodeInfo NodeInfo, timeout time.Duration) error {
|
||||
func (pc *peerConn) HandshakeTimeout(ourNodeInfo NodeInfo, timeout time.Duration) (peerNodeInfo NodeInfo, err error) {
|
||||
// Set deadline for handshake so we don't block forever on conn.ReadFull
|
||||
if err := p.conn.SetDeadline(time.Now().Add(timeout)); err != nil {
|
||||
return errors.Wrap(err, "Error setting deadline")
|
||||
if err := pc.conn.SetDeadline(time.Now().Add(timeout)); err != nil {
|
||||
return peerNodeInfo, errors.Wrap(err, "Error setting deadline")
|
||||
}
|
||||
|
||||
var peerNodeInfo NodeInfo
|
||||
var err1 error
|
||||
var err2 error
|
||||
cmn.Parallel(
|
||||
func() {
|
||||
var n int
|
||||
wire.WriteBinary(&ourNodeInfo, p.conn, &n, &err1)
|
||||
wire.WriteBinary(&ourNodeInfo, pc.conn, &n, &err1)
|
||||
},
|
||||
func() {
|
||||
var n int
|
||||
wire.ReadBinary(&peerNodeInfo, p.conn, MaxNodeInfoSize(), &n, &err2)
|
||||
p.Logger.Info("Peer handshake", "peerNodeInfo", peerNodeInfo)
|
||||
wire.ReadBinary(&peerNodeInfo, pc.conn, MaxNodeInfoSize(), &n, &err2)
|
||||
})
|
||||
if err1 != nil {
|
||||
return errors.Wrap(err1, "Error during handshake/write")
|
||||
return peerNodeInfo, errors.Wrap(err1, "Error during handshake/write")
|
||||
}
|
||||
if err2 != nil {
|
||||
return errors.Wrap(err2, "Error during handshake/read")
|
||||
return peerNodeInfo, errors.Wrap(err2, "Error during handshake/read")
|
||||
}
|
||||
|
||||
// Remove deadline
|
||||
if err := p.conn.SetDeadline(time.Time{}); err != nil {
|
||||
return errors.Wrap(err, "Error removing deadline")
|
||||
if err := pc.conn.SetDeadline(time.Time{}); err != nil {
|
||||
return peerNodeInfo, errors.Wrap(err, "Error removing deadline")
|
||||
}
|
||||
|
||||
p.setNodeInfo(peerNodeInfo)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *peer) setNodeInfo(nodeInfo NodeInfo) {
|
||||
p.nodeInfo = nodeInfo
|
||||
// cache the channels so we dont copy nodeInfo
|
||||
// every time we check hasChannel
|
||||
p.channels = nodeInfo.Channels
|
||||
return peerNodeInfo, nil
|
||||
}
|
||||
|
||||
// Addr returns peer's remote network address.
|
||||
func (p *peer) Addr() net.Addr {
|
||||
return p.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
// PubKey returns peer's public key.
|
||||
func (p *peer) PubKey() crypto.PubKey {
|
||||
if !p.nodeInfo.PubKey.Empty() {
|
||||
return p.nodeInfo.PubKey
|
||||
} else if p.config.AuthEnc {
|
||||
return p.conn.(*tmconn.SecretConnection).RemotePubKey()
|
||||
}
|
||||
panic("Attempt to get peer's PubKey before calling Handshake")
|
||||
return p.peerConn.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
// CanSend returns true if the send queue is not full, false otherwise.
|
||||
@@ -348,7 +358,9 @@ func createMConnection(conn net.Conn, p *peer, reactorsByCh map[byte]Reactor, ch
|
||||
onReceive := func(chID byte, msgBytes []byte) {
|
||||
reactor := reactorsByCh[chID]
|
||||
if reactor == nil {
|
||||
cmn.PanicSanity(cmn.Fmt("Unknown channel %X", chID))
|
||||
// Note that its ok to panic here as it's caught in the conn._recover,
|
||||
// which does onPeerError.
|
||||
panic(cmn.Fmt("Unknown channel %X", chID))
|
||||
}
|
||||
reactor.Receive(chID, p, msgBytes)
|
||||
}
|
||||
|
||||
@@ -11,12 +11,13 @@ import (
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
)
|
||||
|
||||
// Returns an empty dummy peer
|
||||
// Returns an empty kvstore peer
|
||||
func randPeer() *peer {
|
||||
pubKey := crypto.GenPrivKeyEd25519().Wrap().PubKey()
|
||||
return &peer{
|
||||
nodeInfo: NodeInfo{
|
||||
ListenAddr: cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256),
|
||||
PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(),
|
||||
PubKey: pubKey,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
tmconn "github.com/tendermint/tendermint/p2p/conn"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
)
|
||||
|
||||
const testCh = 0x01
|
||||
@@ -35,8 +36,8 @@ func TestPeerBasic(t *testing.T) {
|
||||
assert.False(p.IsPersistent())
|
||||
p.persistent = true
|
||||
assert.True(p.IsPersistent())
|
||||
assert.Equal(rp.Addr().String(), p.Addr().String())
|
||||
assert.Equal(rp.PubKey(), p.PubKey())
|
||||
assert.Equal(rp.Addr().DialString(), p.Addr().String())
|
||||
assert.Equal(rp.ID(), p.ID())
|
||||
}
|
||||
|
||||
func TestPeerWithoutAuthEnc(t *testing.T) {
|
||||
@@ -89,11 +90,11 @@ func createOutboundPeerAndPerformHandshake(addr *NetAddress, config *PeerConfig)
|
||||
}
|
||||
reactorsByCh := map[byte]Reactor{testCh: NewTestReactor(chDescs, true)}
|
||||
pk := crypto.GenPrivKeyEd25519().Wrap()
|
||||
p, err := newOutboundPeer(addr, reactorsByCh, chDescs, func(p Peer, r interface{}) {}, pk, config, false)
|
||||
pc, err := newOutboundPeerConn(addr, config, false, pk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = p.HandshakeTimeout(NodeInfo{
|
||||
nodeInfo, err := pc.HandshakeTimeout(NodeInfo{
|
||||
PubKey: pk.PubKey(),
|
||||
Moniker: "host_peer",
|
||||
Network: "testing",
|
||||
@@ -103,6 +104,9 @@ func createOutboundPeerAndPerformHandshake(addr *NetAddress, config *PeerConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := newPeer(pc, nodeInfo, reactorsByCh, chDescs, func(p Peer, r interface{}) {})
|
||||
p.SetLogger(log.TestingLogger().With("peer", addr))
|
||||
return p, nil
|
||||
}
|
||||
|
||||
@@ -117,8 +121,8 @@ func (p *remotePeer) Addr() *NetAddress {
|
||||
return p.addr
|
||||
}
|
||||
|
||||
func (p *remotePeer) PubKey() crypto.PubKey {
|
||||
return p.PrivKey.PubKey()
|
||||
func (p *remotePeer) ID() ID {
|
||||
return PubKeyToID(p.PrivKey.PubKey())
|
||||
}
|
||||
|
||||
func (p *remotePeer) Start() {
|
||||
@@ -126,7 +130,7 @@ func (p *remotePeer) Start() {
|
||||
if e != nil {
|
||||
golog.Fatalf("net.Listen tcp :0: %+v", e)
|
||||
}
|
||||
p.addr = NewNetAddress("", l.Addr())
|
||||
p.addr = NewNetAddress(PubKeyToID(p.PrivKey.PubKey()), l.Addr())
|
||||
p.quit = make(chan struct{})
|
||||
go p.accept(l)
|
||||
}
|
||||
@@ -136,16 +140,18 @@ func (p *remotePeer) Stop() {
|
||||
}
|
||||
|
||||
func (p *remotePeer) accept(l net.Listener) {
|
||||
conns := []net.Conn{}
|
||||
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
golog.Fatalf("Failed to accept conn: %+v", err)
|
||||
}
|
||||
peer, err := newInboundPeer(conn, make(map[byte]Reactor), make([]*tmconn.ChannelDescriptor, 0), func(p Peer, r interface{}) {}, p.PrivKey, p.Config)
|
||||
pc, err := newInboundPeerConn(conn, p.Config, p.PrivKey)
|
||||
if err != nil {
|
||||
golog.Fatalf("Failed to create a peer: %+v", err)
|
||||
}
|
||||
err = peer.HandshakeTimeout(NodeInfo{
|
||||
_, err = pc.HandshakeTimeout(NodeInfo{
|
||||
PubKey: p.PrivKey.PubKey(),
|
||||
Moniker: "remote_peer",
|
||||
Network: "testing",
|
||||
@@ -156,10 +162,15 @@ func (p *remotePeer) accept(l net.Listener) {
|
||||
if err != nil {
|
||||
golog.Fatalf("Failed to perform handshake: %+v", err)
|
||||
}
|
||||
|
||||
conns = append(conns, conn)
|
||||
|
||||
select {
|
||||
case <-p.quit:
|
||||
if err := conn.Close(); err != nil {
|
||||
golog.Fatal(err)
|
||||
for _, conn := range conns {
|
||||
if err := conn.Close(); err != nil {
|
||||
golog.Fatal(err)
|
||||
}
|
||||
}
|
||||
return
|
||||
default:
|
||||
|
||||
@@ -139,6 +139,10 @@ func (a *addrBook) Wait() {
|
||||
a.wg.Wait()
|
||||
}
|
||||
|
||||
func (a *addrBook) FilePath() string {
|
||||
return a.filePath
|
||||
}
|
||||
|
||||
//-------------------------------------------------------
|
||||
|
||||
// AddOurAddress one of our addresses.
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -26,17 +27,24 @@ func createTempFileName(prefix string) string {
|
||||
return fname
|
||||
}
|
||||
|
||||
func deleteTempFile(fname string) {
|
||||
err := os.Remove(fname)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddrBookPickAddress(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
fname := createTempFileName("addrbook_test")
|
||||
defer deleteTempFile(fname)
|
||||
|
||||
// 0 addresses
|
||||
book := NewAddrBook(fname, true)
|
||||
book.SetLogger(log.TestingLogger())
|
||||
assert.Zero(book.Size())
|
||||
assert.Zero(t, book.Size())
|
||||
|
||||
addr := book.PickAddress(50)
|
||||
assert.Nil(addr, "expected no address")
|
||||
assert.Nil(t, addr, "expected no address")
|
||||
|
||||
randAddrs := randNetAddressPairs(t, 1)
|
||||
addrSrc := randAddrs[0]
|
||||
@@ -44,26 +52,27 @@ func TestAddrBookPickAddress(t *testing.T) {
|
||||
|
||||
// pick an address when we only have new address
|
||||
addr = book.PickAddress(0)
|
||||
assert.NotNil(addr, "expected an address")
|
||||
assert.NotNil(t, addr, "expected an address")
|
||||
addr = book.PickAddress(50)
|
||||
assert.NotNil(addr, "expected an address")
|
||||
assert.NotNil(t, addr, "expected an address")
|
||||
addr = book.PickAddress(100)
|
||||
assert.NotNil(addr, "expected an address")
|
||||
assert.NotNil(t, addr, "expected an address")
|
||||
|
||||
// pick an address when we only have old address
|
||||
book.MarkGood(addrSrc.addr)
|
||||
addr = book.PickAddress(0)
|
||||
assert.NotNil(addr, "expected an address")
|
||||
assert.NotNil(t, addr, "expected an address")
|
||||
addr = book.PickAddress(50)
|
||||
assert.NotNil(addr, "expected an address")
|
||||
assert.NotNil(t, addr, "expected an address")
|
||||
|
||||
// in this case, nNew==0 but we biased 100% to new, so we return nil
|
||||
addr = book.PickAddress(100)
|
||||
assert.Nil(addr, "did not expected an address")
|
||||
assert.Nil(t, addr, "did not expected an address")
|
||||
}
|
||||
|
||||
func TestAddrBookSaveLoad(t *testing.T) {
|
||||
fname := createTempFileName("addrbook_test")
|
||||
defer deleteTempFile(fname)
|
||||
|
||||
// 0 addresses
|
||||
book := NewAddrBook(fname, true)
|
||||
@@ -95,6 +104,7 @@ func TestAddrBookSaveLoad(t *testing.T) {
|
||||
|
||||
func TestAddrBookLookup(t *testing.T) {
|
||||
fname := createTempFileName("addrbook_test")
|
||||
defer deleteTempFile(fname)
|
||||
|
||||
randAddrs := randNetAddressPairs(t, 100)
|
||||
|
||||
@@ -115,8 +125,8 @@ func TestAddrBookLookup(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAddrBookPromoteToOld(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
fname := createTempFileName("addrbook_test")
|
||||
defer deleteTempFile(fname)
|
||||
|
||||
randAddrs := randNetAddressPairs(t, 100)
|
||||
|
||||
@@ -147,11 +157,12 @@ func TestAddrBookPromoteToOld(t *testing.T) {
|
||||
t.Errorf("selection could not be bigger than the book")
|
||||
}
|
||||
|
||||
assert.Equal(book.Size(), 100, "expecting book size to be 100")
|
||||
assert.Equal(t, book.Size(), 100, "expecting book size to be 100")
|
||||
}
|
||||
|
||||
func TestAddrBookHandlesDuplicates(t *testing.T) {
|
||||
fname := createTempFileName("addrbook_test")
|
||||
defer deleteTempFile(fname)
|
||||
|
||||
book := NewAddrBook(fname, true)
|
||||
book.SetLogger(log.TestingLogger())
|
||||
@@ -202,6 +213,8 @@ func randIPv4Address(t *testing.T) *p2p.NetAddress {
|
||||
|
||||
func TestAddrBookRemoveAddress(t *testing.T) {
|
||||
fname := createTempFileName("addrbook_test")
|
||||
defer deleteTempFile(fname)
|
||||
|
||||
book := NewAddrBook(fname, true)
|
||||
book.SetLogger(log.TestingLogger())
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -38,6 +39,8 @@ const (
|
||||
defaultSeedDisconnectWaitPeriod = 2 * time.Minute // disconnect after this
|
||||
defaultCrawlPeerInterval = 2 * time.Minute // dont redial for this. TODO: back-off
|
||||
defaultCrawlPeersPeriod = 30 * time.Second // check some peers every this
|
||||
|
||||
maxAttemptsToDial = 16 // ~ 35h in total (last attempt - 18h)
|
||||
)
|
||||
|
||||
// PEXReactor handles PEX (peer exchange) and ensures that an
|
||||
@@ -59,6 +62,8 @@ type PEXReactor struct {
|
||||
// maps to prevent abuse
|
||||
requestsSent *cmn.CMap // ID->struct{}: unanswered send requests
|
||||
lastReceivedRequests *cmn.CMap // ID->time.Time: last time peer requested from us
|
||||
|
||||
attemptsToDial sync.Map // address (string) -> {number of attempts (int), last time dialed (time.Time)}
|
||||
}
|
||||
|
||||
// PEXReactorConfig holds reactor specific configuration data.
|
||||
@@ -69,6 +74,15 @@ type PEXReactorConfig struct {
|
||||
// Seeds is a list of addresses reactor may use
|
||||
// if it can't connect to peers in the addrbook.
|
||||
Seeds []string
|
||||
|
||||
// PrivatePeerIDs is a list of peer IDs, which must not be gossiped to other
|
||||
// peers.
|
||||
PrivatePeerIDs []string
|
||||
}
|
||||
|
||||
type _attemptsToDial struct {
|
||||
number int
|
||||
lastDialed time.Time
|
||||
}
|
||||
|
||||
// NewPEXReactor creates new PEX reactor.
|
||||
@@ -142,7 +156,9 @@ func (r *PEXReactor) AddPeer(p Peer) {
|
||||
// Let the ensurePeersRoutine handle asking for more
|
||||
// peers when we need - we don't trust inbound peers as much.
|
||||
addr := p.NodeInfo().NetAddress()
|
||||
r.book.AddAddress(addr, addr)
|
||||
if !isAddrPrivate(addr, r.config.PrivatePeerIDs) {
|
||||
r.book.AddAddress(addr, addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +173,8 @@ func (r *PEXReactor) RemovePeer(p Peer, reason interface{}) {
|
||||
func (r *PEXReactor) Receive(chID byte, src Peer, msgBytes []byte) {
|
||||
_, msg, err := DecodeMessage(msgBytes)
|
||||
if err != nil {
|
||||
r.Logger.Error("Error decoding message", "err", err)
|
||||
r.Logger.Error("Error decoding message", "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes)
|
||||
r.Switch.StopPeerForError(src, err)
|
||||
return
|
||||
}
|
||||
r.Logger.Debug("Received message", "src", src, "chId", chID, "msg", msg)
|
||||
@@ -241,7 +258,7 @@ func (r *PEXReactor) ReceiveAddrs(addrs []*p2p.NetAddress, src Peer) error {
|
||||
|
||||
srcAddr := src.NodeInfo().NetAddress()
|
||||
for _, netAddr := range addrs {
|
||||
if netAddr != nil {
|
||||
if netAddr != nil && !isAddrPrivate(netAddr, r.config.PrivatePeerIDs) {
|
||||
r.book.AddAddress(netAddr, srcAddr)
|
||||
}
|
||||
}
|
||||
@@ -260,9 +277,17 @@ func (r *PEXReactor) SetEnsurePeersPeriod(d time.Duration) {
|
||||
|
||||
// Ensures that sufficient peers are connected. (continuous)
|
||||
func (r *PEXReactor) ensurePeersRoutine() {
|
||||
// Randomize when routine starts
|
||||
ensurePeersPeriodMs := r.ensurePeersPeriod.Nanoseconds() / 1e6
|
||||
time.Sleep(time.Duration(rand.Int63n(ensurePeersPeriodMs)) * time.Millisecond)
|
||||
var (
|
||||
seed = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
jitter = seed.Int63n(r.ensurePeersPeriod.Nanoseconds())
|
||||
)
|
||||
|
||||
// Randomize first round of communication to avoid thundering herd.
|
||||
// If no potential peers are present directly start connecting so we guarantee
|
||||
// swift setup with the help of configured seeds.
|
||||
if r.hasPotentialPeers() {
|
||||
time.Sleep(time.Duration(jitter))
|
||||
}
|
||||
|
||||
// fire once immediately.
|
||||
// ensures we dial the seeds right away if the book is empty
|
||||
@@ -287,9 +312,18 @@ func (r *PEXReactor) ensurePeersRoutine() {
|
||||
// the node operator. It should not be used to compute what addresses are
|
||||
// already connected or not.
|
||||
func (r *PEXReactor) ensurePeers() {
|
||||
numOutPeers, numInPeers, numDialing := r.Switch.NumPeers()
|
||||
numToDial := defaultMinNumOutboundPeers - (numOutPeers + numDialing)
|
||||
r.Logger.Info("Ensure peers", "numOutPeers", numOutPeers, "numDialing", numDialing, "numToDial", numToDial)
|
||||
var (
|
||||
out, in, dial = r.Switch.NumPeers()
|
||||
numToDial = defaultMinNumOutboundPeers - (out + dial)
|
||||
)
|
||||
r.Logger.Info(
|
||||
"Ensure peers",
|
||||
"numOutPeers", out,
|
||||
"numInPeers", in,
|
||||
"numDialing", dial,
|
||||
"numToDial", numToDial,
|
||||
)
|
||||
|
||||
if numToDial <= 0 {
|
||||
return
|
||||
}
|
||||
@@ -297,11 +331,12 @@ func (r *PEXReactor) ensurePeers() {
|
||||
// bias to prefer more vetted peers when we have fewer connections.
|
||||
// not perfect, but somewhate ensures that we prioritize connecting to more-vetted
|
||||
// NOTE: range here is [10, 90]. Too high ?
|
||||
newBias := cmn.MinInt(numOutPeers, 8)*10 + 10
|
||||
newBias := cmn.MinInt(out, 8)*10 + 10
|
||||
|
||||
toDial := make(map[p2p.ID]*p2p.NetAddress)
|
||||
// Try maxAttempts times to pick numToDial addresses to dial
|
||||
maxAttempts := numToDial * 3
|
||||
|
||||
for i := 0; i < maxAttempts && len(toDial) < numToDial; i++ {
|
||||
try := r.book.PickAddress(newBias)
|
||||
if try == nil {
|
||||
@@ -321,18 +356,8 @@ func (r *PEXReactor) ensurePeers() {
|
||||
}
|
||||
|
||||
// Dial picked addresses
|
||||
for _, item := range toDial {
|
||||
go func(picked *p2p.NetAddress) {
|
||||
_, err := r.Switch.DialPeerWithAddress(picked, false)
|
||||
if err != nil {
|
||||
// TODO: detect more "bad peer" scenarios
|
||||
if _, ok := err.(p2p.ErrSwitchAuthenticationFailure); ok {
|
||||
r.book.MarkBad(picked)
|
||||
} else {
|
||||
r.book.MarkAttempt(picked)
|
||||
}
|
||||
}
|
||||
}(item)
|
||||
for _, addr := range toDial {
|
||||
go r.dialPeer(addr)
|
||||
}
|
||||
|
||||
// If we need more addresses, pick a random peer and ask for more.
|
||||
@@ -347,12 +372,58 @@ func (r *PEXReactor) ensurePeers() {
|
||||
}
|
||||
|
||||
// If we are not connected to nor dialing anybody, fallback to dialing a seed.
|
||||
if numOutPeers+numInPeers+numDialing+len(toDial) == 0 {
|
||||
if out+in+dial+len(toDial) == 0 {
|
||||
r.Logger.Info("No addresses to dial nor connected peers. Falling back to seeds")
|
||||
r.dialSeeds()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *PEXReactor) dialPeer(addr *p2p.NetAddress) {
|
||||
var attempts int
|
||||
var lastDialed time.Time
|
||||
if lAttempts, attempted := r.attemptsToDial.Load(addr.DialString()); attempted {
|
||||
attempts = lAttempts.(_attemptsToDial).number
|
||||
lastDialed = lAttempts.(_attemptsToDial).lastDialed
|
||||
}
|
||||
|
||||
if attempts > maxAttemptsToDial {
|
||||
r.Logger.Error("Reached max attempts to dial", "addr", addr, "attempts", attempts)
|
||||
r.book.MarkBad(addr)
|
||||
return
|
||||
}
|
||||
|
||||
// exponential backoff if it's not our first attempt to dial given address
|
||||
if attempts > 0 {
|
||||
jitterSeconds := time.Duration(rand.Float64() * float64(time.Second)) // 1s == (1e9 ns)
|
||||
backoffDuration := jitterSeconds + ((1 << uint(attempts)) * time.Second)
|
||||
sinceLastDialed := time.Since(lastDialed)
|
||||
if sinceLastDialed < backoffDuration {
|
||||
r.Logger.Debug("Too early to dial", "addr", addr, "backoff_duration", backoffDuration, "last_dialed", lastDialed, "time_since", sinceLastDialed)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err := r.Switch.DialPeerWithAddress(addr, false)
|
||||
if err != nil {
|
||||
r.Logger.Error("Dialing failed", "addr", addr, "err", err, "attempts", attempts)
|
||||
// TODO: detect more "bad peer" scenarios
|
||||
if _, ok := err.(p2p.ErrSwitchAuthenticationFailure); ok {
|
||||
r.book.MarkBad(addr)
|
||||
r.attemptsToDial.Delete(addr.DialString())
|
||||
} else {
|
||||
r.book.MarkAttempt(addr)
|
||||
// FIXME: if the addr is going to be removed from the addrbook (hard to
|
||||
// tell at this point), we need to Delete it from attemptsToDial, not
|
||||
// record another attempt.
|
||||
// record attempt
|
||||
r.attemptsToDial.Store(addr.DialString(), _attemptsToDial{attempts + 1, time.Now()})
|
||||
}
|
||||
} else {
|
||||
// cleanup any history
|
||||
r.attemptsToDial.Delete(addr.DialString())
|
||||
}
|
||||
}
|
||||
|
||||
// check seed addresses are well formed
|
||||
func (r *PEXReactor) checkSeeds() error {
|
||||
lSeeds := len(r.config.Seeds)
|
||||
@@ -381,17 +452,26 @@ func (r *PEXReactor) dialSeeds() {
|
||||
for _, i := range perm {
|
||||
// dial a random seed
|
||||
seedAddr := seedAddrs[i]
|
||||
peer, err := r.Switch.DialPeerWithAddress(seedAddr, false)
|
||||
if err != nil {
|
||||
r.Switch.Logger.Error("Error dialing seed", "err", err, "seed", seedAddr)
|
||||
} else {
|
||||
r.Switch.Logger.Info("Connected to seed", "peer", peer)
|
||||
err := r.Switch.DialPeerWithAddress(seedAddr, false)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
r.Switch.Logger.Error("Error dialing seed", "err", err, "seed", seedAddr)
|
||||
}
|
||||
r.Switch.Logger.Error("Couldn't connect to any seeds")
|
||||
}
|
||||
|
||||
// AttemptsToDial returns the number of attempts to dial specific address. It
|
||||
// returns 0 if never attempted or successfully connected.
|
||||
func (r *PEXReactor) AttemptsToDial(addr *p2p.NetAddress) int {
|
||||
lAttempts, attempted := r.attemptsToDial.Load(addr.DialString())
|
||||
if attempted {
|
||||
return lAttempts.(_attemptsToDial).number
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------
|
||||
|
||||
// Explores the network searching for more peers. (continuous)
|
||||
@@ -415,6 +495,14 @@ func (r *PEXReactor) crawlPeersRoutine() {
|
||||
}
|
||||
}
|
||||
|
||||
// hasPotentialPeers indicates if there is a potential peer to connect to, by
|
||||
// consulting the Switch as well as the AddrBook.
|
||||
func (r *PEXReactor) hasPotentialPeers() bool {
|
||||
out, in, dial := r.Switch.NumPeers()
|
||||
|
||||
return out+in+dial > 0 && len(r.book.ListOfKnownAddresses()) > 0
|
||||
}
|
||||
|
||||
// crawlPeerInfo handles temporary data needed for the
|
||||
// network crawling performed during seed/crawler mode.
|
||||
type crawlPeerInfo struct {
|
||||
@@ -470,7 +558,7 @@ func (r *PEXReactor) crawlPeers() {
|
||||
continue
|
||||
}
|
||||
// Otherwise, attempt to connect with the known address
|
||||
_, err := r.Switch.DialPeerWithAddress(pi.Addr, false)
|
||||
err := r.Switch.DialPeerWithAddress(pi.Addr, false)
|
||||
if err != nil {
|
||||
r.book.MarkAttempt(pi.Addr)
|
||||
continue
|
||||
@@ -502,6 +590,16 @@ func (r *PEXReactor) attemptDisconnects() {
|
||||
}
|
||||
}
|
||||
|
||||
// isAddrPrivate returns true if addr is private.
|
||||
func isAddrPrivate(addr *p2p.NetAddress, privatePeerIDs []string) bool {
|
||||
for _, id := range privatePeerIDs {
|
||||
if string(addr.ID) == id {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Messages
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -12,12 +13,11 @@ import (
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
"github.com/tendermint/tendermint/p2p/conn"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -30,49 +30,33 @@ func init() {
|
||||
}
|
||||
|
||||
func TestPEXReactorBasic(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
r, book := createReactor(&PEXReactorConfig{})
|
||||
defer teardownReactor(book)
|
||||
|
||||
dir, err := ioutil.TempDir("", "pex_reactor")
|
||||
require.Nil(err)
|
||||
defer os.RemoveAll(dir) // nolint: errcheck
|
||||
book := NewAddrBook(dir+"addrbook.json", true)
|
||||
book.SetLogger(log.TestingLogger())
|
||||
|
||||
r := NewPEXReactor(book, &PEXReactorConfig{})
|
||||
r.SetLogger(log.TestingLogger())
|
||||
|
||||
assert.NotNil(r)
|
||||
assert.NotEmpty(r.GetChannels())
|
||||
assert.NotNil(t, r)
|
||||
assert.NotEmpty(t, r.GetChannels())
|
||||
}
|
||||
|
||||
func TestPEXReactorAddRemovePeer(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
dir, err := ioutil.TempDir("", "pex_reactor")
|
||||
require.Nil(err)
|
||||
defer os.RemoveAll(dir) // nolint: errcheck
|
||||
book := NewAddrBook(dir+"addrbook.json", true)
|
||||
book.SetLogger(log.TestingLogger())
|
||||
|
||||
r := NewPEXReactor(book, &PEXReactorConfig{})
|
||||
r.SetLogger(log.TestingLogger())
|
||||
r, book := createReactor(&PEXReactorConfig{})
|
||||
defer teardownReactor(book)
|
||||
|
||||
size := book.Size()
|
||||
peer := p2p.CreateRandomPeer(false)
|
||||
|
||||
r.AddPeer(peer)
|
||||
assert.Equal(size+1, book.Size())
|
||||
assert.Equal(t, size+1, book.Size())
|
||||
|
||||
r.RemovePeer(peer, "peer not available")
|
||||
assert.Equal(size+1, book.Size())
|
||||
assert.Equal(t, size+1, book.Size())
|
||||
|
||||
outboundPeer := p2p.CreateRandomPeer(true)
|
||||
|
||||
r.AddPeer(outboundPeer)
|
||||
assert.Equal(size+1, book.Size(), "outbound peers should not be added to the address book")
|
||||
assert.Equal(t, size+1, book.Size(), "outbound peers should not be added to the address book")
|
||||
|
||||
r.RemovePeer(outboundPeer, "peer not available")
|
||||
assert.Equal(size+1, book.Size())
|
||||
assert.Equal(t, size+1, book.Size())
|
||||
}
|
||||
|
||||
func TestPEXReactorRunning(t *testing.T) {
|
||||
@@ -82,7 +66,7 @@ func TestPEXReactorRunning(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "pex_reactor")
|
||||
require.Nil(t, err)
|
||||
defer os.RemoveAll(dir) // nolint: errcheck
|
||||
book := NewAddrBook(dir+"addrbook.json", false)
|
||||
book := NewAddrBook(filepath.Join(dir, "addrbook.json"), false)
|
||||
book.SetLogger(log.TestingLogger())
|
||||
|
||||
// create switches
|
||||
@@ -100,7 +84,7 @@ func TestPEXReactorRunning(t *testing.T) {
|
||||
|
||||
// fill the address book and add listeners
|
||||
for _, s := range switches {
|
||||
addr, _ := p2p.NewNetAddressString(s.NodeInfo().ListenAddr)
|
||||
addr := s.NodeInfo().NetAddress()
|
||||
book.AddAddress(addr, addr)
|
||||
s.AddListener(p2p.NewDefaultListener("tcp", s.NodeInfo().ListenAddr, true, log.TestingLogger()))
|
||||
}
|
||||
@@ -119,50 +103,9 @@ func TestPEXReactorRunning(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func assertPeersWithTimeout(t *testing.T, switches []*p2p.Switch, checkPeriod, timeout time.Duration, nPeers int) {
|
||||
ticker := time.NewTicker(checkPeriod)
|
||||
remaining := timeout
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
// check peers are connected
|
||||
allGood := true
|
||||
for _, s := range switches {
|
||||
outbound, inbound, _ := s.NumPeers()
|
||||
if outbound+inbound < nPeers {
|
||||
allGood = false
|
||||
}
|
||||
}
|
||||
remaining -= checkPeriod
|
||||
if remaining < 0 {
|
||||
remaining = 0
|
||||
}
|
||||
if allGood {
|
||||
return
|
||||
}
|
||||
case <-time.After(remaining):
|
||||
numPeersStr := ""
|
||||
for i, s := range switches {
|
||||
outbound, inbound, _ := s.NumPeers()
|
||||
numPeersStr += fmt.Sprintf("%d => {outbound: %d, inbound: %d}, ", i, outbound, inbound)
|
||||
}
|
||||
t.Errorf("expected all switches to be connected to at least one peer (switches: %s)", numPeersStr)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPEXReactorReceive(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
dir, err := ioutil.TempDir("", "pex_reactor")
|
||||
require.Nil(err)
|
||||
defer os.RemoveAll(dir) // nolint: errcheck
|
||||
book := NewAddrBook(dir+"addrbook.json", false)
|
||||
book.SetLogger(log.TestingLogger())
|
||||
|
||||
r := NewPEXReactor(book, &PEXReactorConfig{})
|
||||
r.SetLogger(log.TestingLogger())
|
||||
r, book := createReactor(&PEXReactorConfig{})
|
||||
defer teardownReactor(book)
|
||||
|
||||
peer := p2p.CreateRandomPeer(false)
|
||||
|
||||
@@ -173,89 +116,69 @@ func TestPEXReactorReceive(t *testing.T) {
|
||||
addrs := []*p2p.NetAddress{peer.NodeInfo().NetAddress()}
|
||||
msg := wire.BinaryBytes(struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}})
|
||||
r.Receive(PexChannel, peer, msg)
|
||||
assert.Equal(size+1, book.Size())
|
||||
assert.Equal(t, size+1, book.Size())
|
||||
|
||||
msg = wire.BinaryBytes(struct{ PexMessage }{&pexRequestMessage{}})
|
||||
r.Receive(PexChannel, peer, msg)
|
||||
}
|
||||
|
||||
func TestPEXReactorRequestMessageAbuse(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
r, book := createReactor(&PEXReactorConfig{})
|
||||
defer teardownReactor(book)
|
||||
|
||||
dir, err := ioutil.TempDir("", "pex_reactor")
|
||||
require.Nil(err)
|
||||
defer os.RemoveAll(dir) // nolint: errcheck
|
||||
book := NewAddrBook(dir+"addrbook.json", true)
|
||||
book.SetLogger(log.TestingLogger())
|
||||
|
||||
r := NewPEXReactor(book, &PEXReactorConfig{})
|
||||
sw := p2p.MakeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch { return sw })
|
||||
sw.SetLogger(log.TestingLogger())
|
||||
sw.AddReactor("PEX", r)
|
||||
r.SetSwitch(sw)
|
||||
r.SetLogger(log.TestingLogger())
|
||||
sw := createSwitchAndAddReactors(r)
|
||||
|
||||
peer := newMockPeer()
|
||||
p2p.AddPeerToSwitch(sw, peer)
|
||||
assert.True(sw.Peers().Has(peer.ID()))
|
||||
assert.True(t, sw.Peers().Has(peer.ID()))
|
||||
|
||||
id := string(peer.ID())
|
||||
msg := wire.BinaryBytes(struct{ PexMessage }{&pexRequestMessage{}})
|
||||
|
||||
// first time creates the entry
|
||||
r.Receive(PexChannel, peer, msg)
|
||||
assert.True(r.lastReceivedRequests.Has(id))
|
||||
assert.True(sw.Peers().Has(peer.ID()))
|
||||
assert.True(t, r.lastReceivedRequests.Has(id))
|
||||
assert.True(t, sw.Peers().Has(peer.ID()))
|
||||
|
||||
// next time sets the last time value
|
||||
r.Receive(PexChannel, peer, msg)
|
||||
assert.True(r.lastReceivedRequests.Has(id))
|
||||
assert.True(sw.Peers().Has(peer.ID()))
|
||||
assert.True(t, r.lastReceivedRequests.Has(id))
|
||||
assert.True(t, sw.Peers().Has(peer.ID()))
|
||||
|
||||
// third time is too many too soon - peer is removed
|
||||
r.Receive(PexChannel, peer, msg)
|
||||
assert.False(r.lastReceivedRequests.Has(id))
|
||||
assert.False(sw.Peers().Has(peer.ID()))
|
||||
assert.False(t, r.lastReceivedRequests.Has(id))
|
||||
assert.False(t, sw.Peers().Has(peer.ID()))
|
||||
}
|
||||
|
||||
func TestPEXReactorAddrsMessageAbuse(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
r, book := createReactor(&PEXReactorConfig{})
|
||||
defer teardownReactor(book)
|
||||
|
||||
dir, err := ioutil.TempDir("", "pex_reactor")
|
||||
require.Nil(err)
|
||||
defer os.RemoveAll(dir) // nolint: errcheck
|
||||
book := NewAddrBook(dir+"addrbook.json", true)
|
||||
book.SetLogger(log.TestingLogger())
|
||||
|
||||
r := NewPEXReactor(book, &PEXReactorConfig{})
|
||||
sw := p2p.MakeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch { return sw })
|
||||
sw.SetLogger(log.TestingLogger())
|
||||
sw.AddReactor("PEX", r)
|
||||
r.SetSwitch(sw)
|
||||
r.SetLogger(log.TestingLogger())
|
||||
sw := createSwitchAndAddReactors(r)
|
||||
|
||||
peer := newMockPeer()
|
||||
p2p.AddPeerToSwitch(sw, peer)
|
||||
assert.True(sw.Peers().Has(peer.ID()))
|
||||
assert.True(t, sw.Peers().Has(peer.ID()))
|
||||
|
||||
id := string(peer.ID())
|
||||
|
||||
// request addrs from the peer
|
||||
r.RequestAddrs(peer)
|
||||
assert.True(r.requestsSent.Has(id))
|
||||
assert.True(sw.Peers().Has(peer.ID()))
|
||||
assert.True(t, r.requestsSent.Has(id))
|
||||
assert.True(t, sw.Peers().Has(peer.ID()))
|
||||
|
||||
addrs := []*p2p.NetAddress{peer.NodeInfo().NetAddress()}
|
||||
msg := wire.BinaryBytes(struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}})
|
||||
|
||||
// receive some addrs. should clear the request
|
||||
r.Receive(PexChannel, peer, msg)
|
||||
assert.False(r.requestsSent.Has(id))
|
||||
assert.True(sw.Peers().Has(peer.ID()))
|
||||
assert.False(t, r.requestsSent.Has(id))
|
||||
assert.True(t, sw.Peers().Has(peer.ID()))
|
||||
|
||||
// receiving more addrs causes a disconnect
|
||||
r.Receive(PexChannel, peer, msg)
|
||||
assert.False(sw.Peers().Has(peer.ID()))
|
||||
assert.False(t, sw.Peers().Has(peer.ID()))
|
||||
}
|
||||
|
||||
func TestPEXReactorUsesSeedsIfNeeded(t *testing.T) {
|
||||
@@ -263,59 +186,68 @@ func TestPEXReactorUsesSeedsIfNeeded(t *testing.T) {
|
||||
require.Nil(t, err)
|
||||
defer os.RemoveAll(dir) // nolint: errcheck
|
||||
|
||||
book := NewAddrBook(dir+"addrbook.json", false)
|
||||
book := NewAddrBook(filepath.Join(dir, "addrbook.json"), false)
|
||||
book.SetLogger(log.TestingLogger())
|
||||
|
||||
// 1. create seed
|
||||
seed := p2p.MakeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch {
|
||||
sw.SetLogger(log.TestingLogger())
|
||||
seed := p2p.MakeSwitch(
|
||||
config,
|
||||
0,
|
||||
"127.0.0.1",
|
||||
"123.123.123",
|
||||
func(i int, sw *p2p.Switch) *p2p.Switch {
|
||||
sw.SetLogger(log.TestingLogger())
|
||||
|
||||
r := NewPEXReactor(book, &PEXReactorConfig{})
|
||||
r.SetLogger(log.TestingLogger())
|
||||
r.SetEnsurePeersPeriod(250 * time.Millisecond)
|
||||
sw.AddReactor("pex", r)
|
||||
return sw
|
||||
})
|
||||
seed.AddListener(p2p.NewDefaultListener("tcp", seed.NodeInfo().ListenAddr, true, log.TestingLogger()))
|
||||
err = seed.Start()
|
||||
require.Nil(t, err)
|
||||
r := NewPEXReactor(book, &PEXReactorConfig{})
|
||||
r.SetLogger(log.TestingLogger())
|
||||
sw.AddReactor("pex", r)
|
||||
return sw
|
||||
},
|
||||
)
|
||||
seed.AddListener(
|
||||
p2p.NewDefaultListener(
|
||||
"tcp",
|
||||
seed.NodeInfo().ListenAddr,
|
||||
true,
|
||||
log.TestingLogger(),
|
||||
),
|
||||
)
|
||||
require.Nil(t, seed.Start())
|
||||
defer seed.Stop()
|
||||
|
||||
// 2. create usual peer
|
||||
sw := p2p.MakeSwitch(config, 1, "127.0.0.1", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch {
|
||||
sw.SetLogger(log.TestingLogger())
|
||||
// 2. create usual peer with only seed configured.
|
||||
peer := p2p.MakeSwitch(
|
||||
config,
|
||||
1,
|
||||
"127.0.0.1",
|
||||
"123.123.123",
|
||||
func(i int, sw *p2p.Switch) *p2p.Switch {
|
||||
sw.SetLogger(log.TestingLogger())
|
||||
|
||||
r := NewPEXReactor(book, &PEXReactorConfig{Seeds: []string{seed.NodeInfo().ListenAddr}})
|
||||
r.SetLogger(log.TestingLogger())
|
||||
r.SetEnsurePeersPeriod(250 * time.Millisecond)
|
||||
sw.AddReactor("pex", r)
|
||||
return sw
|
||||
})
|
||||
err = sw.Start()
|
||||
require.Nil(t, err)
|
||||
defer sw.Stop()
|
||||
r := NewPEXReactor(
|
||||
book,
|
||||
&PEXReactorConfig{
|
||||
Seeds: []string{seed.NodeInfo().NetAddress().String()},
|
||||
},
|
||||
)
|
||||
r.SetLogger(log.TestingLogger())
|
||||
sw.AddReactor("pex", r)
|
||||
return sw
|
||||
},
|
||||
)
|
||||
require.Nil(t, peer.Start())
|
||||
defer peer.Stop()
|
||||
|
||||
// 3. check that peer at least connects to seed
|
||||
assertPeersWithTimeout(t, []*p2p.Switch{sw}, 10*time.Millisecond, 10*time.Second, 1)
|
||||
// 3. check that the peer connects to seed immediately
|
||||
assertPeersWithTimeout(t, []*p2p.Switch{peer}, 10*time.Millisecond, 1*time.Second, 1)
|
||||
}
|
||||
|
||||
func TestPEXReactorCrawlStatus(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
pexR, book := createReactor(&PEXReactorConfig{SeedMode: true})
|
||||
defer teardownReactor(book)
|
||||
|
||||
dir, err := ioutil.TempDir("", "pex_reactor")
|
||||
require.Nil(err)
|
||||
defer os.RemoveAll(dir) // nolint: errcheck
|
||||
book := NewAddrBook(dir+"addrbook.json", false)
|
||||
book.SetLogger(log.TestingLogger())
|
||||
|
||||
pexR := NewPEXReactor(book, &PEXReactorConfig{SeedMode: true})
|
||||
// Seed/Crawler mode uses data from the Switch
|
||||
p2p.MakeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch {
|
||||
pexR.SetLogger(log.TestingLogger())
|
||||
sw.SetLogger(log.TestingLogger().With("switch", i))
|
||||
sw.AddReactor("pex", pexR)
|
||||
return sw
|
||||
})
|
||||
_ = createSwitchAndAddReactors(pexR)
|
||||
|
||||
// Create a peer, add it to the peer set and the addrbook.
|
||||
peer := p2p.CreateRandomPeer(false)
|
||||
@@ -331,11 +263,62 @@ func TestPEXReactorCrawlStatus(t *testing.T) {
|
||||
peerInfos := pexR.getPeersToCrawl()
|
||||
|
||||
// Make sure it has the proper number of elements
|
||||
assert.Equal(2, len(peerInfos))
|
||||
assert.Equal(t, 2, len(peerInfos))
|
||||
|
||||
// TODO: test
|
||||
}
|
||||
|
||||
func TestPEXReactorDoesNotAddPrivatePeersToAddrBook(t *testing.T) {
|
||||
peer := p2p.CreateRandomPeer(false)
|
||||
|
||||
pexR, book := createReactor(&PEXReactorConfig{PrivatePeerIDs: []string{string(peer.NodeInfo().ID())}})
|
||||
defer teardownReactor(book)
|
||||
|
||||
// we have to send a request to receive responses
|
||||
pexR.RequestAddrs(peer)
|
||||
|
||||
size := book.Size()
|
||||
addrs := []*p2p.NetAddress{peer.NodeInfo().NetAddress()}
|
||||
msg := wire.BinaryBytes(struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}})
|
||||
pexR.Receive(PexChannel, peer, msg)
|
||||
assert.Equal(t, size, book.Size())
|
||||
|
||||
pexR.AddPeer(peer)
|
||||
assert.Equal(t, size, book.Size())
|
||||
}
|
||||
|
||||
func TestPEXReactorDialPeer(t *testing.T) {
|
||||
pexR, book := createReactor(&PEXReactorConfig{})
|
||||
defer teardownReactor(book)
|
||||
|
||||
_ = createSwitchAndAddReactors(pexR)
|
||||
|
||||
peer := newMockPeer()
|
||||
addr := peer.NodeInfo().NetAddress()
|
||||
|
||||
assert.Equal(t, 0, pexR.AttemptsToDial(addr))
|
||||
|
||||
// 1st unsuccessful attempt
|
||||
pexR.dialPeer(addr)
|
||||
|
||||
assert.Equal(t, 1, pexR.AttemptsToDial(addr))
|
||||
|
||||
// 2nd unsuccessful attempt
|
||||
pexR.dialPeer(addr)
|
||||
|
||||
// must be skipped because it is too early
|
||||
assert.Equal(t, 1, pexR.AttemptsToDial(addr))
|
||||
|
||||
if !testing.Short() {
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
// 3rd attempt
|
||||
pexR.dialPeer(addr)
|
||||
|
||||
assert.Equal(t, 2, pexR.AttemptsToDial(addr))
|
||||
}
|
||||
}
|
||||
|
||||
type mockPeer struct {
|
||||
*cmn.BaseService
|
||||
pubKey crypto.PubKey
|
||||
@@ -368,3 +351,77 @@ func (mp mockPeer) Send(byte, interface{}) bool { return false }
|
||||
func (mp mockPeer) TrySend(byte, interface{}) bool { return false }
|
||||
func (mp mockPeer) Set(string, interface{}) {}
|
||||
func (mp mockPeer) Get(string) interface{} { return nil }
|
||||
|
||||
func assertPeersWithTimeout(
|
||||
t *testing.T,
|
||||
switches []*p2p.Switch,
|
||||
checkPeriod, timeout time.Duration,
|
||||
nPeers int,
|
||||
) {
|
||||
var (
|
||||
ticker = time.NewTicker(checkPeriod)
|
||||
remaining = timeout
|
||||
)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
// check peers are connected
|
||||
allGood := true
|
||||
for _, s := range switches {
|
||||
outbound, inbound, _ := s.NumPeers()
|
||||
if outbound+inbound < nPeers {
|
||||
allGood = false
|
||||
}
|
||||
}
|
||||
remaining -= checkPeriod
|
||||
if remaining < 0 {
|
||||
remaining = 0
|
||||
}
|
||||
if allGood {
|
||||
return
|
||||
}
|
||||
case <-time.After(remaining):
|
||||
numPeersStr := ""
|
||||
for i, s := range switches {
|
||||
outbound, inbound, _ := s.NumPeers()
|
||||
numPeersStr += fmt.Sprintf("%d => {outbound: %d, inbound: %d}, ", i, outbound, inbound)
|
||||
}
|
||||
t.Errorf(
|
||||
"expected all switches to be connected to at least one peer (switches: %s)",
|
||||
numPeersStr,
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createReactor(config *PEXReactorConfig) (r *PEXReactor, book *addrBook) {
|
||||
dir, err := ioutil.TempDir("", "pex_reactor")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
book = NewAddrBook(filepath.Join(dir, "addrbook.json"), true)
|
||||
book.SetLogger(log.TestingLogger())
|
||||
|
||||
r = NewPEXReactor(book, config)
|
||||
r.SetLogger(log.TestingLogger())
|
||||
return
|
||||
}
|
||||
|
||||
func teardownReactor(book *addrBook) {
|
||||
err := os.RemoveAll(filepath.Dir(book.FilePath()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func createSwitchAndAddReactors(reactors ...p2p.Reactor) *p2p.Switch {
|
||||
sw := p2p.MakeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch { return sw })
|
||||
sw.SetLogger(log.TestingLogger())
|
||||
for _, r := range reactors {
|
||||
sw.AddReactor(r.String(), r)
|
||||
r.SetSwitch(sw)
|
||||
}
|
||||
return sw
|
||||
}
|
||||
|
||||
160
p2p/switch.go
160
p2p/switch.go
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/p2p/conn"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
@@ -36,6 +35,7 @@ const (
|
||||
|
||||
type AddrBook interface {
|
||||
AddAddress(addr *NetAddress, src *NetAddress) error
|
||||
MarkGood(*NetAddress)
|
||||
Save()
|
||||
}
|
||||
|
||||
@@ -58,9 +58,10 @@ type Switch struct {
|
||||
dialing *cmn.CMap
|
||||
nodeInfo NodeInfo // our node info
|
||||
nodeKey *NodeKey // our node privkey
|
||||
addrBook AddrBook
|
||||
|
||||
filterConnByAddr func(net.Addr) error
|
||||
filterConnByPubKey func(crypto.PubKey) error
|
||||
filterConnByAddr func(net.Addr) error
|
||||
filterConnByID func(ID) error
|
||||
|
||||
rng *rand.Rand // seed for randomizing dial times and orders
|
||||
}
|
||||
@@ -85,6 +86,7 @@ func NewSwitch(config *cfg.P2PConfig) *Switch {
|
||||
sw.peerConfig.MConfig.SendRate = config.SendRate
|
||||
sw.peerConfig.MConfig.RecvRate = config.RecvRate
|
||||
sw.peerConfig.MConfig.MaxMsgPacketPayloadSize = config.MaxMsgPacketPayloadSize
|
||||
sw.peerConfig.AuthEnc = config.AuthEnc
|
||||
|
||||
sw.BaseService = *cmn.NewBaseService(nil, "P2P Switch", sw)
|
||||
return sw
|
||||
@@ -287,14 +289,13 @@ func (sw *Switch) reconnectToPeer(peer Peer) {
|
||||
return
|
||||
}
|
||||
|
||||
peer, err := sw.DialPeerWithAddress(netAddr, true)
|
||||
err := sw.DialPeerWithAddress(netAddr, true)
|
||||
if err != nil {
|
||||
sw.Logger.Info("Error reconnecting to peer. Trying again", "tries", i, "err", err, "peer", peer)
|
||||
// sleep a set amount
|
||||
sw.randomSleep(reconnectInterval)
|
||||
continue
|
||||
} else {
|
||||
sw.Logger.Info("Reconnected to peer", "peer", peer)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -309,18 +310,28 @@ func (sw *Switch) reconnectToPeer(peer Peer) {
|
||||
// sleep an exponentially increasing amount
|
||||
sleepIntervalSeconds := math.Pow(reconnectBackOffBaseSeconds, float64(i))
|
||||
sw.randomSleep(time.Duration(sleepIntervalSeconds) * time.Second)
|
||||
peer, err := sw.DialPeerWithAddress(netAddr, true)
|
||||
if err != nil {
|
||||
sw.Logger.Info("Error reconnecting to peer. Trying again", "tries", i, "err", err, "peer", peer)
|
||||
continue
|
||||
} else {
|
||||
sw.Logger.Info("Reconnected to peer", "peer", peer)
|
||||
return
|
||||
err := sw.DialPeerWithAddress(netAddr, true)
|
||||
if err == nil {
|
||||
return // success
|
||||
}
|
||||
sw.Logger.Info("Error reconnecting to peer. Trying again", "tries", i, "err", err, "peer", peer)
|
||||
}
|
||||
sw.Logger.Error("Failed to reconnect to peer. Giving up", "peer", peer, "elapsed", time.Since(start))
|
||||
}
|
||||
|
||||
// SetAddrBook allows to set address book on Switch.
|
||||
func (sw *Switch) SetAddrBook(addrBook AddrBook) {
|
||||
sw.addrBook = addrBook
|
||||
}
|
||||
|
||||
// MarkPeerAsGood marks the given peer as good when it did something useful
|
||||
// like contributed to consensus.
|
||||
func (sw *Switch) MarkPeerAsGood(peer Peer) {
|
||||
if sw.addrBook != nil {
|
||||
sw.addrBook.MarkGood(peer.NodeInfo().NetAddress())
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Dialing
|
||||
|
||||
@@ -358,11 +369,9 @@ func (sw *Switch) DialPeersAsync(addrBook AddrBook, peers []string, persistent b
|
||||
go func(i int) {
|
||||
sw.randomSleep(0)
|
||||
j := perm[i]
|
||||
peer, err := sw.DialPeerWithAddress(netAddrs[j], persistent)
|
||||
err := sw.DialPeerWithAddress(netAddrs[j], persistent)
|
||||
if err != nil {
|
||||
sw.Logger.Error("Error dialing peer", "err", err)
|
||||
} else {
|
||||
sw.Logger.Info("Connected to peer", "peer", peer)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
@@ -371,7 +380,7 @@ func (sw *Switch) DialPeersAsync(addrBook AddrBook, peers []string, persistent b
|
||||
|
||||
// DialPeerWithAddress dials the given peer and runs sw.addPeer if it connects and authenticates successfully.
|
||||
// If `persistent == true`, the switch will always try to reconnect to this peer if the connection ever fails.
|
||||
func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (Peer, error) {
|
||||
func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) error {
|
||||
sw.dialing.Set(string(addr.ID), addr)
|
||||
defer sw.dialing.Delete(string(addr.ID))
|
||||
return sw.addOutboundPeerWithConfig(addr, sw.peerConfig, persistent)
|
||||
@@ -394,10 +403,10 @@ func (sw *Switch) FilterConnByAddr(addr net.Addr) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FilterConnByPubKey returns an error if connecting to the given public key is forbidden.
|
||||
func (sw *Switch) FilterConnByPubKey(pubkey crypto.PubKey) error {
|
||||
if sw.filterConnByPubKey != nil {
|
||||
return sw.filterConnByPubKey(pubkey)
|
||||
// FilterConnByID returns an error if connecting to the given peer ID is forbidden.
|
||||
func (sw *Switch) FilterConnByID(id ID) error {
|
||||
if sw.filterConnByID != nil {
|
||||
return sw.filterConnByID(id)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -408,9 +417,9 @@ func (sw *Switch) SetAddrFilter(f func(net.Addr) error) {
|
||||
sw.filterConnByAddr = f
|
||||
}
|
||||
|
||||
// SetPubKeyFilter sets the function for filtering connections by public key.
|
||||
func (sw *Switch) SetPubKeyFilter(f func(crypto.PubKey) error) {
|
||||
sw.filterConnByPubKey = f
|
||||
// SetIDFilter sets the function for filtering connections by peer ID.
|
||||
func (sw *Switch) SetIDFilter(f func(ID) error) {
|
||||
sw.filterConnByID = f
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
@@ -441,14 +450,13 @@ func (sw *Switch) listenerRoutine(l Listener) {
|
||||
}
|
||||
|
||||
func (sw *Switch) addInboundPeerWithConfig(conn net.Conn, config *PeerConfig) error {
|
||||
peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, config)
|
||||
peerConn, err := newInboundPeerConn(conn, config, sw.nodeKey.PrivKey)
|
||||
if err != nil {
|
||||
conn.Close() // peer is nil
|
||||
return err
|
||||
}
|
||||
peer.SetLogger(sw.Logger.With("peer", conn.RemoteAddr()))
|
||||
if err = sw.addPeer(peer); err != nil {
|
||||
peer.CloseConn()
|
||||
if err = sw.addPeer(peerConn); err != nil {
|
||||
peerConn.CloseConn()
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -457,31 +465,20 @@ func (sw *Switch) addInboundPeerWithConfig(conn net.Conn, config *PeerConfig) er
|
||||
|
||||
// dial the peer; make secret connection; authenticate against the dialed ID;
|
||||
// add the peer.
|
||||
func (sw *Switch) addOutboundPeerWithConfig(addr *NetAddress, config *PeerConfig, persistent bool) (Peer, error) {
|
||||
func (sw *Switch) addOutboundPeerWithConfig(addr *NetAddress, config *PeerConfig, persistent bool) error {
|
||||
sw.Logger.Info("Dialing peer", "address", addr)
|
||||
peer, err := newOutboundPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, config, persistent)
|
||||
peerConn, err := newOutboundPeerConn(addr, config, persistent, sw.nodeKey.PrivKey)
|
||||
if err != nil {
|
||||
sw.Logger.Error("Failed to dial peer", "address", addr, "err", err)
|
||||
return nil, err
|
||||
}
|
||||
peer.SetLogger(sw.Logger.With("peer", addr))
|
||||
|
||||
// authenticate peer
|
||||
if addr.ID == "" {
|
||||
peer.Logger.Info("Dialed peer with unknown ID - unable to authenticate", "addr", addr)
|
||||
} else if addr.ID != peer.ID() {
|
||||
peer.CloseConn()
|
||||
return nil, ErrSwitchAuthenticationFailure{addr, peer.ID()}
|
||||
return err
|
||||
}
|
||||
|
||||
err = sw.addPeer(peer)
|
||||
if err != nil {
|
||||
if err := sw.addPeer(peerConn); err != nil {
|
||||
sw.Logger.Error("Failed to add peer", "address", addr, "err", err)
|
||||
peer.CloseConn()
|
||||
return nil, err
|
||||
peerConn.CloseConn()
|
||||
return err
|
||||
}
|
||||
sw.Logger.Info("Dialed and added peer", "address", addr, "peer", peer)
|
||||
return peer, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// addPeer performs the Tendermint P2P handshake with a peer
|
||||
@@ -489,44 +486,70 @@ func (sw *Switch) addOutboundPeerWithConfig(addr *NetAddress, config *PeerConfig
|
||||
// it starts the peer and adds it to the switch.
|
||||
// NOTE: This performs a blocking handshake before the peer is added.
|
||||
// NOTE: If error is returned, caller is responsible for calling peer.CloseConn()
|
||||
func (sw *Switch) addPeer(peer *peer) error {
|
||||
func (sw *Switch) addPeer(pc peerConn) error {
|
||||
|
||||
addr := pc.conn.RemoteAddr()
|
||||
if err := sw.FilterConnByAddr(addr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// NOTE: if AuthEnc==false, we don't have a peerID until after the handshake.
|
||||
// If AuthEnc==true then we already know the ID and could do the checks first before the handshake,
|
||||
// but it's simple to just deal with both cases the same after the handshake.
|
||||
|
||||
// Exchange NodeInfo on the conn
|
||||
peerNodeInfo, err := pc.HandshakeTimeout(sw.nodeInfo, time.Duration(sw.peerConfig.HandshakeTimeout*time.Second))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peerID := peerNodeInfo.ID()
|
||||
|
||||
// ensure connection key matches self reported key
|
||||
if pc.config.AuthEnc {
|
||||
connID := pc.ID()
|
||||
|
||||
if peerID != connID {
|
||||
return fmt.Errorf("nodeInfo.ID() (%v) doesn't match conn.ID() (%v)",
|
||||
peerID, connID)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the peers nodeInfo
|
||||
if err := peerNodeInfo.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Avoid self
|
||||
if sw.nodeKey.ID() == peer.ID() {
|
||||
if sw.nodeKey.ID() == peerID {
|
||||
return ErrSwitchConnectToSelf
|
||||
}
|
||||
|
||||
// Avoid duplicate
|
||||
if sw.peers.Has(peer.ID()) {
|
||||
if sw.peers.Has(peerID) {
|
||||
return ErrSwitchDuplicatePeer
|
||||
|
||||
}
|
||||
|
||||
// Filter peer against white list
|
||||
if err := sw.FilterConnByAddr(peer.Addr()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := sw.FilterConnByPubKey(peer.PubKey()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Exchange NodeInfo with the peer
|
||||
if err := peer.HandshakeTimeout(sw.nodeInfo, time.Duration(sw.peerConfig.HandshakeTimeout*time.Second)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate the peers nodeInfo against the pubkey
|
||||
if err := peer.NodeInfo().Validate(peer.PubKey()); err != nil {
|
||||
// Filter peer against ID white list
|
||||
if err := sw.FilterConnByID(peerID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check version, chain id
|
||||
if err := sw.nodeInfo.CompatibleWith(peer.NodeInfo()); err != nil {
|
||||
if err := sw.nodeInfo.CompatibleWith(peerNodeInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peer := newPeer(pc, peerNodeInfo, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError)
|
||||
peer.SetLogger(sw.Logger.With("peer", addr))
|
||||
|
||||
peer.Logger.Info("Successful handshake with peer", "peerNodeInfo", peerNodeInfo)
|
||||
|
||||
// All good. Start peer
|
||||
if sw.IsRunning() {
|
||||
sw.startInitPeer(peer)
|
||||
if err = sw.startInitPeer(peer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Add the peer to .peers.
|
||||
@@ -540,14 +563,17 @@ func (sw *Switch) addPeer(peer *peer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sw *Switch) startInitPeer(peer *peer) {
|
||||
func (sw *Switch) startInitPeer(peer *peer) error {
|
||||
err := peer.Start() // spawn send/recv routines
|
||||
if err != nil {
|
||||
// Should never happen
|
||||
sw.Logger.Error("Error starting peer", "peer", peer, "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, reactor := range sw.reactors {
|
||||
reactor.AddPeer(peer)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ func assertNoPeersAfterTimeout(t *testing.T, sw *Switch, timeout time.Duration)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnPubKeyFilter(t *testing.T) {
|
||||
func TestConnIDFilter(t *testing.T) {
|
||||
s1 := MakeSwitch(config, 1, "testing", "123.123.123", initSwitchFunc)
|
||||
s2 := MakeSwitch(config, 1, "testing", "123.123.123", initSwitchFunc)
|
||||
defer s1.Stop()
|
||||
@@ -200,15 +200,20 @@ func TestConnPubKeyFilter(t *testing.T) {
|
||||
|
||||
c1, c2 := conn.NetPipe()
|
||||
|
||||
// set pubkey filter
|
||||
s1.SetPubKeyFilter(func(pubkey crypto.PubKey) error {
|
||||
if bytes.Equal(pubkey.Bytes(), s2.nodeInfo.PubKey.Bytes()) {
|
||||
s1.SetIDFilter(func(id ID) error {
|
||||
if id == PubKeyToID(s2.nodeInfo.PubKey) {
|
||||
return fmt.Errorf("Error: pipe is blacklisted")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
s2.SetIDFilter(func(id ID) error {
|
||||
if id == PubKeyToID(s1.nodeInfo.PubKey) {
|
||||
return fmt.Errorf("Error: pipe is blacklisted")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// connect to good peer
|
||||
go func() {
|
||||
err := s1.addPeerWithConnection(c1)
|
||||
assert.NotNil(t, err, "expected error")
|
||||
@@ -237,13 +242,16 @@ func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) {
|
||||
rp.Start()
|
||||
defer rp.Stop()
|
||||
|
||||
peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, DefaultPeerConfig(), false)
|
||||
pc, err := newOutboundPeerConn(rp.Addr(), DefaultPeerConfig(), false, sw.nodeKey.PrivKey)
|
||||
require.Nil(err)
|
||||
err = sw.addPeer(peer)
|
||||
err = sw.addPeer(pc)
|
||||
require.Nil(err)
|
||||
|
||||
peer := sw.Peers().Get(rp.ID())
|
||||
require.NotNil(peer)
|
||||
|
||||
// simulate failure by closing connection
|
||||
peer.CloseConn()
|
||||
pc.CloseConn()
|
||||
|
||||
assertNoPeersAfterTimeout(t, sw, 100*time.Millisecond)
|
||||
assert.False(peer.IsRunning())
|
||||
@@ -264,13 +272,17 @@ func TestSwitchReconnectsToPersistentPeer(t *testing.T) {
|
||||
rp.Start()
|
||||
defer rp.Stop()
|
||||
|
||||
peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, DefaultPeerConfig(), true)
|
||||
require.Nil(err)
|
||||
err = sw.addPeer(peer)
|
||||
pc, err := newOutboundPeerConn(rp.Addr(), DefaultPeerConfig(), true, sw.nodeKey.PrivKey)
|
||||
// sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey,
|
||||
require.Nil(err)
|
||||
|
||||
require.Nil(sw.addPeer(pc))
|
||||
|
||||
peer := sw.Peers().Get(rp.ID())
|
||||
require.NotNil(peer)
|
||||
|
||||
// simulate failure by closing connection
|
||||
peer.CloseConn()
|
||||
pc.CloseConn()
|
||||
|
||||
// TODO: remove sleep, detect the disconnection, wait for reconnect
|
||||
npeers := sw.Peers().Size()
|
||||
|
||||
@@ -19,12 +19,14 @@ func AddPeerToSwitch(sw *Switch, peer Peer) {
|
||||
func CreateRandomPeer(outbound bool) *peer {
|
||||
addr, netAddr := CreateRoutableAddr()
|
||||
p := &peer{
|
||||
peerConn: peerConn{
|
||||
outbound: outbound,
|
||||
},
|
||||
nodeInfo: NodeInfo{
|
||||
ListenAddr: netAddr.DialString(),
|
||||
PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(),
|
||||
},
|
||||
outbound: outbound,
|
||||
mconn: &conn.MConnection{},
|
||||
mconn: &conn.MConnection{},
|
||||
}
|
||||
p.SetLogger(log.TestingLogger().With("peer", addr))
|
||||
return p
|
||||
@@ -98,16 +100,15 @@ func Connect2Switches(switches []*Switch, i, j int) {
|
||||
}
|
||||
|
||||
func (sw *Switch) addPeerWithConnection(conn net.Conn) error {
|
||||
peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, sw.peerConfig)
|
||||
pc, err := newInboundPeerConn(conn, sw.peerConfig, sw.nodeKey.PrivKey)
|
||||
if err != nil {
|
||||
if err := conn.Close(); err != nil {
|
||||
sw.Logger.Error("Error closing connection", "err", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
peer.SetLogger(sw.Logger.With("peer", conn.RemoteAddr()))
|
||||
if err = sw.addPeer(peer); err != nil {
|
||||
peer.CloseConn()
|
||||
if err = sw.addPeer(pc); err != nil {
|
||||
pc.CloseConn()
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
abcicli "github.com/tendermint/abci/client"
|
||||
"github.com/tendermint/abci/example/dummy"
|
||||
"github.com/tendermint/abci/example/kvstore"
|
||||
"github.com/tendermint/abci/server"
|
||||
"github.com/tendermint/abci/types"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
@@ -49,7 +49,7 @@ func TestEcho(t *testing.T) {
|
||||
clientCreator := NewRemoteClientCreator(sockPath, SOCKET, true)
|
||||
|
||||
// Start server
|
||||
s := server.NewSocketServer(sockPath, dummy.NewDummyApplication())
|
||||
s := server.NewSocketServer(sockPath, kvstore.NewKVStoreApplication())
|
||||
s.SetLogger(log.TestingLogger().With("module", "abci-server"))
|
||||
if err := s.Start(); err != nil {
|
||||
t.Fatalf("Error starting socket server: %v", err.Error())
|
||||
@@ -83,7 +83,7 @@ func BenchmarkEcho(b *testing.B) {
|
||||
clientCreator := NewRemoteClientCreator(sockPath, SOCKET, true)
|
||||
|
||||
// Start server
|
||||
s := server.NewSocketServer(sockPath, dummy.NewDummyApplication())
|
||||
s := server.NewSocketServer(sockPath, kvstore.NewKVStoreApplication())
|
||||
s.SetLogger(log.TestingLogger().With("module", "abci-server"))
|
||||
if err := s.Start(); err != nil {
|
||||
b.Fatalf("Error starting socket server: %v", err.Error())
|
||||
@@ -122,7 +122,7 @@ func TestInfo(t *testing.T) {
|
||||
clientCreator := NewRemoteClientCreator(sockPath, SOCKET, true)
|
||||
|
||||
// Start server
|
||||
s := server.NewSocketServer(sockPath, dummy.NewDummyApplication())
|
||||
s := server.NewSocketServer(sockPath, kvstore.NewKVStoreApplication())
|
||||
s.SetLogger(log.TestingLogger().With("module", "abci-server"))
|
||||
if err := s.Start(); err != nil {
|
||||
t.Fatalf("Error starting socket server: %v", err.Error())
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
abcicli "github.com/tendermint/abci/client"
|
||||
"github.com/tendermint/abci/example/dummy"
|
||||
"github.com/tendermint/abci/example/kvstore"
|
||||
"github.com/tendermint/abci/types"
|
||||
)
|
||||
|
||||
@@ -64,10 +64,14 @@ func (r *remoteClientCreator) NewABCIClient() (abcicli.Client, error) {
|
||||
|
||||
func DefaultClientCreator(addr, transport, dbDir string) ClientCreator {
|
||||
switch addr {
|
||||
case "kvstore":
|
||||
fallthrough
|
||||
case "dummy":
|
||||
return NewLocalClientCreator(dummy.NewDummyApplication())
|
||||
return NewLocalClientCreator(kvstore.NewKVStoreApplication())
|
||||
case "persistent_kvstore":
|
||||
fallthrough
|
||||
case "persistent_dummy":
|
||||
return NewLocalClientCreator(dummy.NewPersistentDummyApplication(dbDir))
|
||||
return NewLocalClientCreator(kvstore.NewPersistentKVStoreApplication(dbDir))
|
||||
case "nilapp":
|
||||
return NewLocalClientCreator(types.NewBaseApplication())
|
||||
default:
|
||||
|
||||
@@ -122,6 +122,15 @@ func (c *HTTP) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *HTTP) Health() (*ctypes.ResultHealth, error) {
|
||||
result := new(ctypes.ResultHealth)
|
||||
_, err := c.rpc.Call("health", map[string]interface{}{}, result)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Health")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *HTTP) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) {
|
||||
result := new(ctypes.ResultBlockchainInfo)
|
||||
_, err := c.rpc.Call("blockchain",
|
||||
|
||||
@@ -83,6 +83,7 @@ type Client interface {
|
||||
type NetworkClient interface {
|
||||
NetInfo() (*ctypes.ResultNetInfo, error)
|
||||
DumpConsensusState() (*ctypes.ResultDumpConsensusState, error)
|
||||
Health() (*ctypes.ResultHealth, error)
|
||||
}
|
||||
|
||||
// EventsClient is reactive, you can subscribe to any message, given the proper
|
||||
|
||||
@@ -84,6 +84,10 @@ func (Local) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) {
|
||||
return core.DumpConsensusState()
|
||||
}
|
||||
|
||||
func (Local) Health() (*ctypes.ResultHealth, error) {
|
||||
return core.Health()
|
||||
}
|
||||
|
||||
func (Local) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) {
|
||||
return core.UnsafeDialSeeds(seeds)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/tendermint/abci/example/dummy"
|
||||
"github.com/tendermint/abci/example/kvstore"
|
||||
nm "github.com/tendermint/tendermint/node"
|
||||
rpctest "github.com/tendermint/tendermint/rpc/test"
|
||||
)
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
var node *nm.Node
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// start a tendermint node (and dummy) in the background to test against
|
||||
app := dummy.NewDummyApplication()
|
||||
// start a tendermint node (and kvstore) in the background to test against
|
||||
app := kvstore.NewKVStoreApplication()
|
||||
node = rpctest.StartTendermint(app)
|
||||
code := m.Run()
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/abci/example/dummy"
|
||||
"github.com/tendermint/abci/example/kvstore"
|
||||
abci "github.com/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/rpc/client"
|
||||
"github.com/tendermint/tendermint/rpc/client/mock"
|
||||
@@ -156,7 +156,7 @@ func TestABCIRecorder(t *testing.T) {
|
||||
|
||||
func TestABCIApp(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
app := dummy.NewDummyApplication()
|
||||
app := kvstore.NewKVStoreApplication()
|
||||
m := mock.ABCIApp{app}
|
||||
|
||||
// get some info
|
||||
|
||||
@@ -78,6 +78,15 @@ func TestDumpConsensusState(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHealth(t *testing.T) {
|
||||
for i, c := range GetClients() {
|
||||
nc, ok := c.(client.NetworkClient)
|
||||
require.True(t, ok, "%d", i)
|
||||
_, err := nc.Health()
|
||||
require.Nil(t, err, "%d: %+v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenesisAndValidators(t *testing.T) {
|
||||
for i, c := range GetClients() {
|
||||
|
||||
@@ -336,7 +345,7 @@ func TestTxSearch(t *testing.T) {
|
||||
require.Nil(t, err, "%+v", err)
|
||||
require.Len(t, results, 0)
|
||||
|
||||
// we query using a tag (see dummy application)
|
||||
// we query using a tag (see kvstore application)
|
||||
results, err = c.TxSearch("app.creator='jae'", false)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
if len(results) == 0 {
|
||||
|
||||
@@ -81,6 +81,7 @@ Available endpoints:
|
||||
/net_info
|
||||
/num_unconfirmed_txs
|
||||
/status
|
||||
/health
|
||||
/unconfirmed_txs
|
||||
/unsafe_flush_mempool
|
||||
/unsafe_stop_cpu_profiler
|
||||
|
||||
31
rpc/core/health.go
Normal file
31
rpc/core/health.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
)
|
||||
|
||||
// Get node health. Returns empty result (200 OK) on success, no response - in
|
||||
// case of an error.
|
||||
//
|
||||
// ```shell
|
||||
// curl 'localhost:46657/health'
|
||||
// ```
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:46657", "/websocket")
|
||||
// result, err := client.Health()
|
||||
// ```
|
||||
//
|
||||
// > The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "error": "",
|
||||
// "result": {},
|
||||
// "id": "",
|
||||
// "jsonrpc": "2.0"
|
||||
// }
|
||||
// ```
|
||||
func Health() (*ctypes.ResultHealth, error) {
|
||||
return &ctypes.ResultHealth{}, nil
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package core
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user