Browse Source

Merge pull request #1373 from tendermint/release/0.17.0

Release/0.17.0
pull/1377/head v0.17.0
Ethan Buchman 6 years ago
committed by GitHub
parent
commit
e5cd006bce
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
192 changed files with 5395 additions and 1804 deletions
  1. +222
    -0
      .circleci/config.yml
  2. +0
    -26
      .codecov.yml
  3. +3
    -0
      .github/ISSUE_TEMPLATE
  4. +3
    -0
      .gitignore
  5. +25
    -1
      CHANGELOG.md
  6. +22
    -8
      CONTRIBUTING.md
  7. +2
    -2
      DOCKER/Dockerfile.develop
  8. +2
    -2
      DOCKER/README.md
  9. +386
    -0
      Gopkg.lock
  10. +95
    -0
      Gopkg.toml
  11. +78
    -19
      Makefile
  12. +3
    -3
      benchmarks/blockchain/localsync.sh
  13. +29
    -16
      blockchain/pool.go
  14. +13
    -12
      blockchain/pool_test.go
  15. +25
    -14
      blockchain/reactor.go
  16. +7
    -7
      blockchain/store.go
  17. +0
    -35
      circle.yml
  18. +53
    -0
      cmd/priv_val_server/main.go
  19. +2
    -2
      cmd/tendermint/commands/init.go
  20. +28
    -1
      cmd/tendermint/commands/lite.go
  21. +9
    -5
      cmd/tendermint/commands/run_node.go
  22. +25
    -0
      cmd/tendermint/commands/show_node_id.go
  23. +6
    -5
      cmd/tendermint/commands/testnet.go
  24. +1
    -0
      cmd/tendermint/main.go
  25. +18
    -0
      codecov.yml
  26. +18
    -6
      config/config.go
  27. +7
    -0
      config/toml.go
  28. +11
    -9
      consensus/byzantine_test.go
  29. +5
    -5
      consensus/common_test.go
  30. +1
    -0
      consensus/mempool_test.go
  31. +66
    -3
      consensus/reactor.go
  32. +121
    -12
      consensus/reactor_test.go
  33. +19
    -5
      consensus/replay.go
  34. +7
    -2
      consensus/replay_file.go
  35. +10
    -10
      consensus/replay_test.go
  36. +110
    -84
      consensus/state.go
  37. +8
    -5
      consensus/types/height_vote_set.go
  38. +2
    -2
      consensus/types/height_vote_set_test.go
  39. +7
    -0
      consensus/types/state.go
  40. +4
    -4
      consensus/wal_generator.go
  41. +18
    -18
      docs/abci-cli.rst
  42. +12
    -12
      docs/app-development.rst
  43. +128
    -0
      docs/architecture/adr-008-priv-validator.md
  44. +5
    -5
      docs/conf.py
  45. +8
    -0
      docs/determinism.rst
  46. +1
    -1
      docs/ecosystem.rst
  47. +7
    -7
      docs/examples/getting-started.md
  48. +4
    -4
      docs/examples/install_tendermint.sh
  49. +13
    -13
      docs/getting-started.rst
  50. +1
    -1
      docs/how-to-read-logs.rst
  51. +2
    -1
      docs/index.rst
  52. +15
    -15
      docs/install.rst
  53. +2
    -1
      docs/specification.rst
  54. +3
    -3
      docs/specification/byzantine-consensus-algorithm.rst
  55. +7
    -0
      docs/specification/configuration.rst
  56. +5
    -7
      docs/specification/genesis.rst
  57. +8
    -15
      docs/specification/new-spec/README.md
  58. +16
    -2
      docs/specification/new-spec/bft-time.md
  59. +1
    -1
      docs/specification/new-spec/reactors/consensus/consensus-reactor.md
  60. +1
    -1
      docs/specification/new-spec/reactors/consensus/consensus.md
  61. +8
    -1
      docs/specification/new-spec/reactors/pex/pex.md
  62. +1
    -0
      docs/specification/rpc.rst
  63. +4
    -4
      docs/using-tendermint.rst
  64. +4
    -2
      evidence/reactor.go
  65. +0
    -206
      glide.lock
  66. +0
    -63
      glide.yaml
  67. +2
    -2
      lite/client/main_test.go
  68. +2
    -2
      lite/helpers.go
  69. +62
    -6
      lite/memprovider.go
  70. +265
    -17
      lite/performance_test.go
  71. +2
    -2
      lite/provider_test.go
  72. +5
    -5
      lite/proxy/query_test.go
  73. +3
    -3
      mempool/mempool_test.go
  74. +2
    -1
      mempool/reactor.go
  75. +2
    -2
      mempool/reactor_test.go
  76. +44
    -7
      node/node.go
  77. +7
    -7
      p2p/conn/secret_connection_test.go
  78. +72
    -0
      p2p/dummy/peer.go
  79. +3
    -8
      p2p/node_info.go
  80. +88
    -76
      p2p/peer.go
  81. +3
    -2
      p2p/peer_set_test.go
  82. +22
    -11
      p2p/peer_test.go
  83. +4
    -0
      p2p/pex/addrbook.go
  84. +24
    -11
      p2p/pex/addrbook_test.go
  85. +127
    -29
      p2p/pex/pex_reactor.go
  86. +214
    -157
      p2p/pex/pex_reactor_test.go
  87. +90
    -64
      p2p/switch.go
  88. +24
    -12
      p2p/switch_test.go
  89. +7
    -6
      p2p/test_util.go
  90. +4
    -4
      proxy/app_conn_test.go
  91. +7
    -3
      proxy/client.go
  92. +9
    -0
      rpc/client/httpclient.go
  93. +1
    -0
      rpc/client/interface.go
  94. +4
    -0
      rpc/client/localclient.go
  95. +3
    -3
      rpc/client/main_test.go
  96. +2
    -2
      rpc/client/mock/abci_test.go
  97. +10
    -1
      rpc/client/rpc_test.go
  98. +1
    -0
      rpc/core/doc.go
  99. +31
    -0
      rpc/core/health.go
  100. +1
    -0
      rpc/core/net.go

+ 222
- 0
.circleci/config.yml View 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

+ 0
- 26
.codecov.yml View File

@ -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
- 0
.github/ISSUE_TEMPLATE View File

@ -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
- 0
.gitignore View File

@ -21,3 +21,6 @@ docs/tools
scripts/wal2json/wal2json
scripts/cutWALUntil/cutWALUntil
.idea/
*.iml

+ 25
- 1
CHANGELOG.md View File

@ -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


+ 22
- 8
CONTRIBUTING.md View File

@ -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


+ 2
- 2
DOCKER/Dockerfile.develop View File

@ -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"]

+ 2
- 2
DOCKER/README.md View File

@ -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
- 0
Gopkg.lock View 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
- 0
Gopkg.toml View 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

+ 78
- 19
Makefile View File

@ -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)
test_race:
@echo "--> Running go test --race"
@go test -v -race $(PACKAGES)
## required to be run first by most tests
build_docker_test_image:
docker build -t tester -f ./test/docker/Dockerfile .
### 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

+ 3
- 3
benchmarks/blockchain/localsync.sh View File

@ -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 \


+ 29
- 16
blockchain/pool.go View File

@ -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
- 12
blockchain/pool_test.go View File

@ -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!


+ 25
- 14
blockchain/reactor.go View File

@ -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


+ 7
- 7
blockchain/store.go View File

@ -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
}

+ 0
- 35
circle.yml View File

@ -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
- 0
cmd/priv_val_server/main.go View 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)
}
})
}

+ 2
- 2
cmd/tendermint/commands/init.go View File

@ -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)
}
}

+ 28
- 1
cmd/tendermint/commands/lite.go View File

@ -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")


+ 9
- 5
cmd/tendermint/commands/run_node.go View File

@ -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
- 0
cmd/tendermint/commands/show_node_id.go View 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
}

+ 6
- 5
cmd/tendermint/commands/testnet.go View File

@ -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
}


+ 1
- 0
cmd/tendermint/main.go View File

@ -24,6 +24,7 @@ func main() {
cmd.ResetPrivValidatorCmd,
cmd.ShowValidatorCmd,
cmd.TestnetFilesCmd,
cmd.ShowNodeIDCmd,
cmd.VersionCmd)
// NOTE:


+ 18
- 0
codecov.yml View 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

+ 18
- 6
config/config.go View File

@ -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,
}
}


+ 7
- 0
config/toml.go View File

@ -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]


+ 11
- 9
consensus/byzantine_test.go View File

@ -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
}


+ 5
- 5
consensus/common_test.go View File

@ -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)
}

+ 1
- 0
consensus/mempool_test.go View File

@ -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)
}


+ 66
- 3
consensus/reactor.go View File

@ -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)
}


+ 121
- 12
consensus/reactor_test.go View File

@ -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)


+ 19
- 5
consensus/replay.go View File

@ -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
}
}


+ 7
- 2
consensus/replay_file.go View File

@ -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))


+ 10
- 10
consensus/replay_test.go View File

@ -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)
}


+ 110
- 84
consensus/state.go View File

@ -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 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)
}
if !added {
return added, err
}
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})
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)
// 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
}
height := cs.Height
added, err = cs.Votes.AddVote(vote, peerID)
if !added {
// Either duplicate, or error upon cs.Votes.AddByIndex()
return
}
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)
}
default:
cmn.PanicSanity(cmn.Fmt("Unexpected vote type %X", vote.Type)) // Should not happen.
}
} else if cs.Round <= vote.Round && precommits.HasTwoThirdsAny() {
cs.enterNewRound(height, vote.Round)
cs.enterPrecommit(height, vote.Round)
cs.enterPrecommitWait(height, vote.Round)
}
// Either duplicate, or error upon cs.Votes.AddByIndex()
return
} else {
err = ErrVoteHeightMismatch
default:
panic(cmn.Fmt("Unexpected vote type %X", vote.Type)) // go-wire should prevent this.
}
// Height mismatch, bad peer?
cs.Logger.Info("Vote ignored and not added", "voteHeight", vote.Height, "csHeight", cs.Height, "err", err)
return
}


+ 8
- 5
consensus/types/height_vote_set.go View File

@ -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)
}

+ 2
- 2
consensus/types/height_vote_set_test.go View File

@ -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.")


+ 7
- 0
consensus/types/state.go View File

@ -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+" "),


+ 4
- 4
consensus/wal_generator.go View File

@ -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 {


+ 18
- 18
docs/abci-cli.rst View File

@ -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,


+ 12
- 12
docs/app-development.rst View File

@ -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
- 0
docs/architecture/adr-008-priv-validator.md View 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

+ 5
- 5
docs/conf.py View File

@ -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
- 0
docs/determinism.rst View 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.

+ 1
- 1
docs/ecosystem.rst View File

@ -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
-----------


+ 7
- 7
docs/examples/getting-started.md View File

@ -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
- 4
docs/examples/install_tendermint.sh View File

@ -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

+ 13
- 13
docs/getting-started.rst View File

@ -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


+ 1
- 1
docs/how-to-read-logs.rst View File

@ -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).
::


+ 2
- 1
docs/index.rst View File

@ -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.

+ 15
- 15
docs/install.rst View File

@ -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
- 1
docs/specification.rst View File

@ -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


+ 3
- 3
docs/specification/byzantine-consensus-algorithm.rst View File

@ -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


+ 7
- 0
docs/specification/configuration.rst View File

@ -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
- 7
docs/specification/genesis.rst View File

@ -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}
}
}

+ 8
- 15
docs/specification/new-spec/README.md View File

@ -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


+ 16
- 2
docs/specification/new-spec/bft-time.md View File

@ -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:


+ 1
- 1
docs/specification/new-spec/reactors/consensus/consensus-reactor.md View File

@ -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.


+ 1
- 1
docs/specification/new-spec/reactors/consensus/consensus.md View File

@ -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


+ 8
- 1
docs/specification/new-spec/reactors/pex/pex.md View File

@ -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:


+ 1
- 0
docs/specification/rpc.rst View File

@ -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


+ 4
- 4
docs/using-tendermint.rst View File

@ -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


+ 4
- 2
evidence/reactor.go View File

@ -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:


+ 0
- 206
glide.lock View File

@ -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

+ 0
- 63
glide.yaml View File

@ -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

+ 2
- 2
lite/client/main_test.go View File

@ -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()


+ 2
- 2
lite/helpers.go View File

@ -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
}


+ 62
- 6
lite/memprovider.go View File

@ -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
}

+ 265
- 17
lite/performance_test.go View File

@ -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)
}
}

+ 2
- 2
lite/provider_test.go View File

@ -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")
}
}


+ 5
- 5
lite/proxy/query_test.go View File

@ -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)


+ 3
- 3
mempool/mempool_test.go View File

@ -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)


+ 2
- 1
mempool/reactor.go View File

@ -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)


+ 2
- 2
mempool/reactor_test.go View File

@ -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)


+ 44
- 7
node/node.go View File

@ -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.


+ 7
- 7
p2p/conn/secret_connection_test.go View File

@ -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
- 0
p2p/dummy/peer.go View 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
}

+ 3
- 8
p2p/node_info.go View File

@ -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)
}


+ 88
- 76
p2p/peer.go View File

@ -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
}
func newPeer(pc peerConn, nodeInfo NodeInfo,
reactorsByCh map[byte]Reactor, chDescs []*tmconn.ChannelDescriptor,
onPeerError func(Peer, interface{})) *peer {
nodeInfo NodeInfo // peer's node info
channels []byte // channels the peer knows about
Data *cmn.CMap // User data.
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)
}


+ 3
- 2
p2p/peer_set_test.go View File

@ -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,
},
}
}


+ 22
- 11
p2p/peer_test.go View File

@ -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:


+ 4
- 0
p2p/pex/addrbook.go View File

@ -139,6 +139,10 @@ func (a *addrBook) Wait() {
a.wg.Wait()
}
func (a *addrBook) FilePath() string {
return a.filePath
}
//-------------------------------------------------------
// AddOurAddress one of our addresses.


+ 24
- 11
p2p/pex/addrbook_test.go View File

@ -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())


+ 127
- 29
p2p/pex/pex_reactor.go View File

@ -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


+ 214
- 157
p2p/pex/pex_reactor_test.go View File

@ -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())
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)
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())
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())
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()
// 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().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)
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, book := createReactor(&PEXReactorConfig{SeedMode: true})
defer teardownReactor(book)
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
}

+ 90
- 64
p2p/switch.go View File

@ -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 {
// Avoid self
if sw.nodeKey.ID() == peer.ID() {
return ErrSwitchConnectToSelf
func (sw *Switch) addPeer(pc peerConn) error {
addr := pc.conn.RemoteAddr()
if err := sw.FilterConnByAddr(addr); err != nil {
return err
}
// Avoid duplicate
if sw.peers.Has(peer.ID()) {
return ErrSwitchDuplicatePeer
// 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
}
// Filter peer against white list
if err := sw.FilterConnByAddr(peer.Addr()); 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)
}
}
if err := sw.FilterConnByPubKey(peer.PubKey()); err != nil {
// Validate the peers nodeInfo
if err := peerNodeInfo.Validate(); 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
// Avoid self
if sw.nodeKey.ID() == peerID {
return ErrSwitchConnectToSelf
}
// Validate the peers nodeInfo against the pubkey
if err := peer.NodeInfo().Validate(peer.PubKey()); err != nil {
// Avoid duplicate
if sw.peers.Has(peerID) {
return ErrSwitchDuplicatePeer
}
// 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
}

+ 24
- 12
p2p/switch_test.go View File

@ -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()


+ 7
- 6
p2p/test_util.go View File

@ -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
}


+ 4
- 4
proxy/app_conn_test.go View File

@ -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())


+ 7
- 3
proxy/client.go View File

@ -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:


+ 9
- 0
rpc/client/httpclient.go View File

@ -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",


+ 1
- 0
rpc/client/interface.go View File

@ -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


+ 4
- 0
rpc/client/localclient.go View File

@ -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)
}


+ 3
- 3
rpc/client/main_test.go View File

@ -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()


+ 2
- 2
rpc/client/mock/abci_test.go View File

@ -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


+ 10
- 1
rpc/client/rpc_test.go View File

@ -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 {


+ 1
- 0
rpc/core/doc.go View File

@ -81,6 +81,7 @@ Available endpoints:
/net_info
/num_unconfirmed_txs
/status
/health
/unconfirmed_txs
/unsafe_flush_mempool
/unsafe_stop_cpu_profiler


+ 31
- 0
rpc/core/health.go View 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
}

+ 1
- 0
rpc/core/net.go View File

@ -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 changed in this diff

Loading…
Cancel
Save