mirror of
https://github.com/fluencelabs/tendermint
synced 2025-07-19 14:21:57 +00:00
Compare commits
467 Commits
v0.17.1
...
v0.21.0-rc
Author | SHA1 | Date | |
---|---|---|---|
|
c84be3b8dd | ||
|
050636d5ce | ||
|
27bd1deabe | ||
|
76c82fd433 | ||
|
9481cabd50 | ||
|
46b957929c | ||
|
512e563d4b | ||
|
a6a4fc7784 | ||
|
bfcec02423 | ||
|
fcf61b8088 | ||
|
46fb179605 | ||
|
89925501f3 | ||
|
6b8613b3e7 | ||
|
8be27494bb | ||
|
c661a3ec21 | ||
|
8e45348737 | ||
|
7dfc74a6b6 | ||
|
2edc68c59b | ||
|
fe4123684d | ||
|
2897685c57 | ||
|
71556c62eb | ||
|
54e61468d4 | ||
|
5c7ccbd4a7 | ||
|
e2f5a6fbe4 | ||
|
aa8be33da1 | ||
|
909f66e841 | ||
|
3d2c4fd309 | ||
|
e5bca1df6f | ||
|
866bcceb35 | ||
|
e1e6878a4d | ||
|
e4147b6f1a | ||
|
7606b7595f | ||
|
485b4a0c6f | ||
|
575d94dbb9 | ||
|
ebd2fe7a68 | ||
|
f28eae7816 | ||
|
e13c1ab735 | ||
|
0e0461d9bc | ||
|
057e076ca9 | ||
|
775fef31c2 | ||
|
9cb079dcc6 | ||
|
67180344b7 | ||
|
825fdf2c24 | ||
|
61002ad264 | ||
|
41e847ec97 | ||
|
55bae62d71 | ||
|
d2259696af | ||
|
32719123d9 | ||
|
2ce8179c8b | ||
|
b8c076ca79 | ||
|
1b2e34738a | ||
|
566024b64f | ||
|
932381effa | ||
|
2007c66091 | ||
|
97c5533c35 | ||
|
3d33226e80 | ||
|
edb851280a | ||
|
dd62f06994 | ||
|
19d95b5410 | ||
|
53937a8129 | ||
|
f1c53c7358 | ||
|
3445f1206e | ||
|
097f778c1e | ||
|
c85c21d1bc | ||
|
fd4db8dfdc | ||
|
1318bd18cd | ||
|
ea896865a7 | ||
|
aeb91dfc22 | ||
|
5727916c5b | ||
|
876c8f14e7 | ||
|
67416feb3a | ||
|
8706ae765c | ||
|
954a8941ff | ||
|
1f22f34edf | ||
|
0562009275 | ||
|
fedd07c522 | ||
|
3fa734ef5a | ||
|
cd6bfdc42f | ||
|
98b0c51b5f | ||
|
c777be256a | ||
|
d66f8bf829 | ||
|
bf370d36c2 | ||
|
1c643701f5 | ||
|
0b0290bdb2 | ||
|
a4779fdf51 | ||
|
7030d5c2a7 | ||
|
f34d1009c4 | ||
|
0e3dc32b3d | ||
|
d292fa4541 | ||
|
3255c076e5 | ||
|
978277a4c1 | ||
|
58eb76f34d | ||
|
a017f2fdd4 | ||
|
aaaa5f23e2 | ||
|
178e357d7f | ||
|
683b527534 | ||
|
d584e03427 | ||
|
d454b1b25f | ||
|
432f21452d | ||
|
f0ce8b3883 | ||
|
3da5198631 | ||
|
252a0a392b | ||
|
f7106bfb39 | ||
|
2a517ac98c | ||
|
b542dce2e1 | ||
|
82ded582f2 | ||
|
e0d4fe2dba | ||
|
83c6f2864d | ||
|
33ec8cb609 | ||
|
43a652bcbf | ||
|
094a40084d | ||
|
eec9f142b5 | ||
|
5796e879b9 | ||
|
846ca5ce56 | ||
|
fd6021876b | ||
|
2763c8539c | ||
|
1c0bfe1158 | ||
|
1e87ef7f75 | ||
|
c8be091d4a | ||
|
ec34c8f9d2 | ||
|
6004587347 | ||
|
f55725ebfa | ||
|
7f20eb5f8e | ||
|
eeabb4c06b | ||
|
4da81aa0b7 | ||
|
67068a34f2 | ||
|
2a0e9f93ce | ||
|
708f35e5c1 | ||
|
f3f5c7f472 | ||
|
68f6226bea | ||
|
118b86b1ef | ||
|
b9afcbe3a2 | ||
|
a885af0826 | ||
|
3a947b0117 | ||
|
caf5afc084 | ||
|
2aa5285c66 | ||
|
b166831fb5 | ||
|
423fef1416 | ||
|
b4d10b5b91 | ||
|
6f1bfb6280 | ||
|
5e7177053c | ||
|
a0201e7862 | ||
|
126ddca1a6 | ||
|
186d38dd8a | ||
|
01fd102dba | ||
|
e11f3167ff | ||
|
7d98cfd3d6 | ||
|
4848e88737 | ||
|
60d7486de2 | ||
|
229c18f1bd | ||
|
91b6d3f18c | ||
|
20e9dd0737 | ||
|
7b02b5b66b | ||
|
0cd92a4948 | ||
|
747f28f85f | ||
|
a9d0adbdef | ||
|
3485edf4f5 | ||
|
c6f612bfc3 | ||
|
bb9aa85d22 | ||
|
c4fef499b6 | ||
|
b77d5344fc | ||
|
21f5f3faa7 | ||
|
bf6527fc59 | ||
|
ed8d9951c0 | ||
|
97b39f340e | ||
|
383c255f35 | ||
|
931fb385d7 | ||
|
018e096748 | ||
|
ee4eb59355 | ||
|
082a02e6d1 | ||
|
2c40966e46 | ||
|
0a9dc9f875 | ||
|
87cefb724d | ||
|
6701dba876 | ||
|
442bbe592f | ||
|
301aa92f9c | ||
|
52f27686ef | ||
|
6f9867cba6 | ||
|
02615c8695 | ||
|
2df137193c | ||
|
1ef415728d | ||
|
773e3917ec | ||
|
26fdfe10fd | ||
|
d76e2dc3ff | ||
|
420f925a4d | ||
|
d7d12c8030 | ||
|
6c4a26f248 | ||
|
2a26c47da5 | ||
|
aabe96f1af | ||
|
0e1f730fbb | ||
|
0b68ec4b8e | ||
|
ca120798e4 | ||
|
595fc24c56 | ||
|
4611cf44f0 | ||
|
d596ed1bc2 | ||
|
0fb33ca91d | ||
|
35428ceb53 | ||
|
de8d4325de | ||
|
5a041baa36 | ||
|
202a43a5af | ||
|
2987158a65 | ||
|
c9001d5a11 | ||
|
90446261f3 | ||
|
ae572b9038 | ||
|
0908e668bd | ||
|
e0dbc3673c | ||
|
545990f845 | ||
|
19ccd1842f | ||
|
b4d6bf7697 | ||
|
1854ce41fc | ||
|
547e8223b9 | ||
|
8e46df14e7 | ||
|
8d60a5a7bd | ||
|
5115618550 | ||
|
a6b74b82d1 | ||
|
e5220360c5 | ||
|
b5c4098c53 | ||
|
bc8768cfea | ||
|
d832bde280 | ||
|
5e3a23df6d | ||
|
6f7333fd5f | ||
|
58e3246ffc | ||
|
bbe1355957 | ||
|
7c14fa820d | ||
|
0d93424c6a | ||
|
efc01cf582 | ||
|
754be1887c | ||
|
775b015173 | ||
|
b698a9febc | ||
|
c5f45275ec | ||
|
77f09f5b5e | ||
|
1fe41be929 | ||
|
68a0b3f95b | ||
|
b1f3c11948 | ||
|
e1a3f16fa4 | ||
|
aab98828fe | ||
|
03f6a29a64 | ||
|
4851653d8e | ||
|
162811476a | ||
|
b5ac9ede8a | ||
|
ff5dfc0c15 | ||
|
d3a98675aa | ||
|
e3c4625e63 | ||
|
01ac378c96 | ||
|
83ca46396d | ||
|
2c125b6c78 | ||
|
3dde0584ed | ||
|
4bca7c1009 | ||
|
4be3ffbe9b | ||
|
5b9a1423ae | ||
|
16932f889f | ||
|
e9804d76cf | ||
|
a41f0d3891 | ||
|
658060150c | ||
|
d0229e8b1e | ||
|
56c9e0da7e | ||
|
fbe253767e | ||
|
edbec10f9e | ||
|
c0a1a8d3c0 | ||
|
ac2d3a917e | ||
|
64408a4041 | ||
|
cae31157b1 | ||
|
66c2b60324 | ||
|
e2e2127365 | ||
|
f395b82f73 | ||
|
47557f868a | ||
|
b6c062c451 | ||
|
12fc396101 | ||
|
c195772de1 | ||
|
d3c4f746a7 | ||
|
fae94a44a2 | ||
|
3a30ee75b9 | ||
|
3498b676a6 | ||
|
6157c700dd | ||
|
c90bf77566 | ||
|
6805ddf1b8 | ||
|
2761861b6b | ||
|
64569b15e5 | ||
|
0450e35d67 | ||
|
268055e549 | ||
|
aaa81092e7 | ||
|
3ee1d7909e | ||
|
32268a8135 | ||
|
f645187122 | ||
|
40c79235c0 | ||
|
c23909eecf | ||
|
936d1a0e68 | ||
|
0cbbb61962 | ||
|
fa66694f2e | ||
|
d92def4b60 | ||
|
26f633ed48 | ||
|
79bfbebfff | ||
|
f33da8817a | ||
|
ffe81a0206 | ||
|
0e00154fcc | ||
|
1ab89e6cbf | ||
|
14cff484f1 | ||
|
25cee8827a | ||
|
65ebbccb74 | ||
|
1188dfe7ee | ||
|
c45ba2967a | ||
|
f67c5a9e7b | ||
|
593a785ae2 | ||
|
94e823cc91 | ||
|
47e4d64973 | ||
|
a2d77cbe4e | ||
|
390b592dec | ||
|
9ab1fafdf1 | ||
|
94c016a04e | ||
|
97f3ada9c2 | ||
|
d7d4471072 | ||
|
d48a6f930d | ||
|
389a6ffa16 | ||
|
f1ead2df70 | ||
|
e5951acfb4 | ||
|
0e1414ef9d | ||
|
97be1eef87 | ||
|
0d9004a854 | ||
|
91c81ef9a1 | ||
|
e2f0778c14 | ||
|
63f8c58009 | ||
|
9ba208c1f5 | ||
|
6ce6b20993 | ||
|
5361073439 | ||
|
6c04465d3d | ||
|
089ce6744c | ||
|
17a5c6fa1a | ||
|
18c3f8f3f1 | ||
|
b42d5a2211 | ||
|
b20e777f53 | ||
|
659762736c | ||
|
ece3f678da | ||
|
5b5acbb343 | ||
|
a2930cd723 | ||
|
45a05b4726 | ||
|
8bdfe15de9 | ||
|
b3904b8da8 | ||
|
f2dae2a2d8 | ||
|
1706ce6f7f | ||
|
d0beaba7e8 | ||
|
c28784de5e | ||
|
d06390638d | ||
|
3a0edc561d | ||
|
f8ed578325 | ||
|
5babaf9a88 | ||
|
c0610b2c32 | ||
|
1db2224241 | ||
|
0323b03daf | ||
|
379f9f875b | ||
|
ab00bf7c8b | ||
|
64879c1e6a | ||
|
7c22e47629 | ||
|
e88f74bb9b | ||
|
384b3ea065 | ||
|
6a48bd0c88 | ||
|
d93e177a69 | ||
|
cef053386b | ||
|
cca1dd8e3e | ||
|
26c38e770e | ||
|
609452958c | ||
|
c954fca376 | ||
|
9be16d56ba | ||
|
2b732bc11a | ||
|
dcd00b0e68 | ||
|
ff3f35c5f4 | ||
|
93c4312cdd | ||
|
1a1e4e767b | ||
|
bb1b249e8a | ||
|
c778d7f5d1 | ||
|
bb9b12d67a | ||
|
767521ac52 | ||
|
df9bf60b05 | ||
|
466c3ab1c7 | ||
|
c68d406195 | ||
|
02c0835e9b | ||
|
c170800fbd | ||
|
7afe74a963 | ||
|
5d8767e656 | ||
|
54adb790f2 | ||
|
02531ca5a3 | ||
|
d24e4cb821 | ||
|
fb64314d1c | ||
|
4930b61a38 | ||
|
9cc2cf362f | ||
|
ed93fb34ab | ||
|
3d32474da8 | ||
|
3233c318ea | ||
|
32e1d195a0 | ||
|
3ca5292dc9 | ||
|
c541d58d2f | ||
|
3037b5b7ca | ||
|
c9a263c589 | ||
|
e4492afbad | ||
|
799beebd36 | ||
|
45ec5fd170 | ||
|
6e39ec6e26 | ||
|
d38a6cc7ea | ||
|
7f6ee7a46b | ||
|
34b77fcad4 | ||
|
3b3f45d49b | ||
|
3284a13fee | ||
|
fc9ffee2e3 | ||
|
3a672cb2a9 | ||
|
4b8e342309 | ||
|
5a2fa71b03 | ||
|
9a57ef9cbf | ||
|
59ca9bf480 | ||
|
7cce07bc99 | ||
|
0ae66f75ce | ||
|
5d1c758730 | ||
|
1b9323f105 | ||
|
cee7b5cb54 | ||
|
1585152341 | ||
|
8e699c2bfd | ||
|
904a3115a6 | ||
|
a506cf47ad | ||
|
7689c15413 | ||
|
f907113c19 | ||
|
140f962201 | ||
|
c23d907f12 | ||
|
ed782e7508 | ||
|
0732526465 | ||
|
39a4963782 | ||
|
37ce6b195a | ||
|
7aa6d36258 | ||
|
991017fc41 | ||
|
5f548c7679 | ||
|
d14aacf03e | ||
|
39ff4d22e9 | ||
|
196f8410ba | ||
|
8462493cbf | ||
|
47b8bd1728 | ||
|
89cdde7f1e | ||
|
657fd671ea | ||
|
315c475b79 | ||
|
5ef639fcbe | ||
|
b800b4ec1d | ||
|
208ac32fa2 | ||
|
641476d40f | ||
|
491c8ab4c1 | ||
|
5ef8a6e887 | ||
|
d694d47d22 | ||
|
ecdc1b9bb0 | ||
|
9c757108ca | ||
|
5243e54641 | ||
|
70e7454c21 | ||
|
2644a529f0 | ||
|
eaee98ee1f | ||
|
35a1d747b0 | ||
|
34974e3932 | ||
|
575a46d9d4 | ||
|
bcadbd1b10 | ||
|
ead9daf1ba | ||
|
22949e6dfd | ||
|
49986b05bc | ||
|
2fa7af4614 | ||
|
2d857c4b1b | ||
|
2b63f57b4c | ||
|
4085c72496 | ||
|
34f5d439ee | ||
|
901b456151 | ||
|
8813684040 | ||
|
58f36bb321 | ||
|
4c2f56626a | ||
|
416f03c05b | ||
|
ced74251e9 | ||
|
6c345f9fa2 |
@@ -77,6 +77,22 @@ jobs:
|
|||||||
paths:
|
paths:
|
||||||
- "bin/abci*"
|
- "bin/abci*"
|
||||||
|
|
||||||
|
build_slate:
|
||||||
|
<<: *defaults
|
||||||
|
steps:
|
||||||
|
- attach_workspace:
|
||||||
|
at: /tmp/workspace
|
||||||
|
- restore_cache:
|
||||||
|
key: v1-pkg-cache
|
||||||
|
- restore_cache:
|
||||||
|
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||||
|
- run:
|
||||||
|
name: slate docs
|
||||||
|
command: |
|
||||||
|
set -ex
|
||||||
|
export PATH="$GOBIN:$PATH"
|
||||||
|
make build-slate
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
steps:
|
steps:
|
||||||
@@ -117,31 +133,21 @@ jobs:
|
|||||||
key: v1-pkg-cache
|
key: v1-pkg-cache
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||||
|
- run: mkdir -p /tmp/logs
|
||||||
- run:
|
- run:
|
||||||
name: Run tests
|
name: Run tests
|
||||||
command: |
|
command: |
|
||||||
for pkg in $(go list github.com/tendermint/tendermint/... | grep -v /vendor/ | circleci tests split --split-by=timings); do
|
for pkg in $(go list github.com/tendermint/tendermint/... | grep -v /vendor/ | circleci tests split --split-by=timings); do
|
||||||
id=$(basename "$pkg")
|
id=$(basename "$pkg")
|
||||||
|
|
||||||
go test -timeout 5m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg"
|
GOCACHE=off go test -v -timeout 5m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg" | tee "/tmp/logs/$id-$RANDOM.log"
|
||||||
done
|
done
|
||||||
- persist_to_workspace:
|
- persist_to_workspace:
|
||||||
root: /tmp/workspace
|
root: /tmp/workspace
|
||||||
paths:
|
paths:
|
||||||
- "profiles/*"
|
- "profiles/*"
|
||||||
|
- store_artifacts:
|
||||||
test_libs:
|
path: /tmp/logs
|
||||||
<<: *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:
|
test_persistence:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
@@ -166,7 +172,7 @@ jobs:
|
|||||||
- checkout
|
- checkout
|
||||||
- run: mkdir -p $GOPATH/src/github.com/tendermint
|
- run: mkdir -p $GOPATH/src/github.com/tendermint
|
||||||
- run: ln -sf /home/circleci/project $GOPATH/src/github.com/tendermint/tendermint
|
- run: ln -sf /home/circleci/project $GOPATH/src/github.com/tendermint/tendermint
|
||||||
- run: bash test/circleci/p2p.sh
|
- run: bash test/p2p/circleci.sh
|
||||||
|
|
||||||
upload_coverage:
|
upload_coverage:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
@@ -205,14 +211,6 @@ workflows:
|
|||||||
- test_cover:
|
- test_cover:
|
||||||
requires:
|
requires:
|
||||||
- setup_dependencies
|
- setup_dependencies
|
||||||
- test_libs:
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- develop
|
|
||||||
- master
|
|
||||||
requires:
|
|
||||||
- setup_dependencies
|
|
||||||
- test_persistence:
|
- test_persistence:
|
||||||
requires:
|
requires:
|
||||||
- setup_abci
|
- setup_abci
|
||||||
|
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
@@ -1,4 +1,4 @@
|
|||||||
# CODEOWNERS: https://help.github.com/articles/about-codeowners/
|
# CODEOWNERS: https://help.github.com/articles/about-codeowners/
|
||||||
|
|
||||||
# Everything goes through Bucky and Anton. For now.
|
# Everything goes through Bucky, Anton, Alex. For now.
|
||||||
* @ebuchman @melekes
|
* @ebuchman @melekes @xla
|
||||||
|
4
.github/ISSUE_TEMPLATE
vendored
4
.github/ISSUE_TEMPLATE
vendored
@@ -19,10 +19,6 @@ in a case of bug.
|
|||||||
|
|
||||||
**ABCI app** (name for built-in, URL for self-written if it's publicly available):
|
**ABCI app** (name for built-in, URL for self-written if it's publicly available):
|
||||||
|
|
||||||
|
|
||||||
**Merkleeyes version** (use `git rev-parse --verify HEAD`, skip if you don't use it):
|
|
||||||
|
|
||||||
|
|
||||||
**Environment**:
|
**Environment**:
|
||||||
- **OS** (e.g. from /etc/os-release):
|
- **OS** (e.g. from /etc/os-release):
|
||||||
- **Install tools**:
|
- **Install tools**:
|
||||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@@ -5,7 +5,6 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
build/*
|
build/*
|
||||||
rpc/test/.tendermint
|
rpc/test/.tendermint
|
||||||
.debora
|
|
||||||
.tendermint
|
.tendermint
|
||||||
remote_dump
|
remote_dump
|
||||||
.revision
|
.revision
|
||||||
@@ -13,10 +12,10 @@ vendor
|
|||||||
.vagrant
|
.vagrant
|
||||||
test/p2p/data/
|
test/p2p/data/
|
||||||
test/logs
|
test/logs
|
||||||
.glide
|
|
||||||
coverage.txt
|
coverage.txt
|
||||||
docs/_build
|
docs/_build
|
||||||
docs/tools
|
docs/tools
|
||||||
|
docs/abci-spec.rst
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
scripts/wal2json/wal2json
|
scripts/wal2json/wal2json
|
||||||
@@ -24,3 +23,5 @@ scripts/cutWALUntil/cutWALUntil
|
|||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
*.iml
|
*.iml
|
||||||
|
|
||||||
|
libs/pubsub/query/fuzz_test/output
|
||||||
|
256
CHANGELOG.md
256
CHANGELOG.md
@@ -1,34 +1,252 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## Roadmap
|
## 0.20.0
|
||||||
|
|
||||||
BREAKING CHANGES:
|
*June 6th, 2018*
|
||||||
- Better support for injecting randomness
|
|
||||||
- Upgrade consensus for more real-time use of evidence
|
|
||||||
|
|
||||||
FEATURES:
|
This is the first in a series of breaking releases coming to Tendermint after
|
||||||
- Peer reputation management
|
soliciting developer feedback and conducting security audits.
|
||||||
- Use the chain as its own CA for nodes and validators
|
|
||||||
- Tooling to run multiple blockchains/apps, possibly in a single process
|
This release does not break any blockchain data structures or
|
||||||
- State syncing (without transaction replay)
|
protocols other than the ABCI messages between Tendermint and the application.
|
||||||
- Add authentication and rate-limitting to the RPC
|
|
||||||
|
Applications that upgrade for ABCI v0.11.0 should be able to continue running Tendermint
|
||||||
|
v0.20.0 on blockchains created with v0.19.X
|
||||||
|
|
||||||
|
BREAKING CHANGES
|
||||||
|
|
||||||
|
- [abci] Upgrade to
|
||||||
|
[v0.11.0](https://github.com/tendermint/abci/blob/master/CHANGELOG.md#0110)
|
||||||
|
- [abci] Change Query path for filtering peers by node ID from
|
||||||
|
`p2p/filter/pubkey/<id>` to `p2p/filter/id/<id>`
|
||||||
|
|
||||||
|
## 0.19.9
|
||||||
|
|
||||||
|
*June 5th, 2018*
|
||||||
|
|
||||||
|
BREAKING CHANGES
|
||||||
|
|
||||||
|
- [types/priv_validator] Moved to top level `privval` package
|
||||||
|
|
||||||
|
FEATURES
|
||||||
|
|
||||||
|
- [config] Collapse PeerConfig into P2PConfig
|
||||||
|
- [docs] Add quick-install script
|
||||||
|
- [docs/spec] Add table of Amino prefixes
|
||||||
|
|
||||||
|
BUG FIXES
|
||||||
|
|
||||||
|
- [rpc] Return 404 for unknown endpoints
|
||||||
|
- [consensus] Flush WAL on stop
|
||||||
|
- [evidence] Don't send evidence to peers that are behind
|
||||||
|
- [p2p] Fix memory leak on peer disconnects
|
||||||
|
- [rpc] Fix panic when `per_page=0`
|
||||||
|
|
||||||
|
## 0.19.8
|
||||||
|
|
||||||
|
*June 4th, 2018*
|
||||||
|
|
||||||
|
BREAKING:
|
||||||
|
|
||||||
|
- [p2p] Remove `auth_enc` config option, peer connections are always auth
|
||||||
|
encrypted. Technically a breaking change but seems no one was using it and
|
||||||
|
arguably a bug fix :)
|
||||||
|
|
||||||
|
BUG FIXES
|
||||||
|
|
||||||
|
- [mempool] Fix deadlock under high load when `skip_timeout_commit=true` and
|
||||||
|
`create_empty_blocks=false`
|
||||||
|
|
||||||
|
## 0.19.7
|
||||||
|
|
||||||
|
*May 31st, 2018*
|
||||||
|
|
||||||
|
BREAKING:
|
||||||
|
|
||||||
|
- [libs/pubsub] TagMap#Get returns a string value
|
||||||
|
- [libs/pubsub] NewTagMap accepts a map of strings
|
||||||
|
|
||||||
|
FEATURES
|
||||||
|
|
||||||
|
- [rpc] the RPC documentation is now published to https://tendermint.github.io/slate
|
||||||
|
- [p2p] AllowDuplicateIP config option to refuse connections from same IP.
|
||||||
|
- true by default for now, false by default in next breaking release
|
||||||
|
- [docs] Add docs for query, tx indexing, events, pubsub
|
||||||
|
- [docs] Add some notes about running Tendermint in production
|
||||||
|
|
||||||
IMPROVEMENTS:
|
IMPROVEMENTS:
|
||||||
- Improve subtleties around mempool caching and logic
|
|
||||||
- Consensus optimizations:
|
- [consensus] Consensus reactor now receives events from a separate synchronous event bus,
|
||||||
- cache block parts for faster agreement after round changes
|
which is not dependant on external RPC load
|
||||||
- propagate block parts rarest first
|
- [consensus/wal] do not look for height in older files if we've seen height - 1
|
||||||
- Better testing of the consensus state machine (ie. use a DSL)
|
- [docs] Various cleanup and link fixes
|
||||||
- Auto compiled serialization/deserialization code instead of go-wire reflection
|
|
||||||
|
## 0.19.6
|
||||||
|
|
||||||
|
*May 29th, 2018*
|
||||||
|
|
||||||
|
BUG FIXES
|
||||||
|
|
||||||
|
- [blockchain] Fix fast-sync deadlock during high peer turnover
|
||||||
|
|
||||||
|
BUG FIX:
|
||||||
|
|
||||||
|
- [evidence] Dont send peers evidence from heights they haven't synced to yet
|
||||||
|
- [p2p] Refuse connections to more than one peer with the same IP
|
||||||
|
- [docs] Various fixes
|
||||||
|
|
||||||
|
## 0.19.5
|
||||||
|
|
||||||
|
*May 20th, 2018*
|
||||||
|
|
||||||
|
BREAKING CHANGES
|
||||||
|
|
||||||
|
- [rpc/client] TxSearch and UnconfirmedTxs have new arguments (see below)
|
||||||
|
- [rpc/client] TxSearch returns ResultTxSearch
|
||||||
|
- [version] Breaking changes to Go APIs will not be reflected in breaking
|
||||||
|
version change, but will be included in changelog.
|
||||||
|
|
||||||
|
FEATURES
|
||||||
|
|
||||||
|
- [rpc] `/tx_search` takes `page` (starts at 1) and `per_page` (max 100, default 30) args to paginate results
|
||||||
|
- [rpc] `/unconfirmed_txs` takes `limit` (max 100, default 30) arg to limit the output
|
||||||
|
- [config] `mempool.size` and `mempool.cache_size` options
|
||||||
|
|
||||||
|
IMPROVEMENTS
|
||||||
|
|
||||||
|
- [docs] Lots of updates
|
||||||
|
- [consensus] Only Fsync() the WAL before executing msgs from ourselves
|
||||||
|
|
||||||
|
BUG FIXES
|
||||||
|
|
||||||
|
- [mempool] Enforce upper bound on number of transactions
|
||||||
|
|
||||||
|
## 0.19.4 (May 17th, 2018)
|
||||||
|
|
||||||
|
IMPROVEMENTS
|
||||||
|
|
||||||
|
- [state] Improve tx indexing by using batches
|
||||||
|
- [consensus, state] Improve logging (more consensus logs, fewer tx logs)
|
||||||
|
- [spec] Moved to `docs/spec` (TODO cleanup the rest of the docs ...)
|
||||||
|
|
||||||
|
BUG FIXES
|
||||||
|
|
||||||
|
- [consensus] Fix issue #1575 where a late proposer can get stuck
|
||||||
|
|
||||||
|
## 0.19.3 (May 14th, 2018)
|
||||||
|
|
||||||
|
FEATURES
|
||||||
|
|
||||||
|
- [rpc] New `/consensus_state` returns just the votes seen at the current height
|
||||||
|
|
||||||
|
IMPROVEMENTS
|
||||||
|
|
||||||
|
- [rpc] Add stringified votes and fraction of power voted to `/dump_consensus_state`
|
||||||
|
- [rpc] Add PeerStateStats to `/dump_consensus_state`
|
||||||
|
|
||||||
|
BUG FIXES
|
||||||
|
|
||||||
|
- [cmd] Set GenesisTime during `tendermint init`
|
||||||
|
- [consensus] fix ValidBlock rules
|
||||||
|
|
||||||
|
## 0.19.2 (April 30th, 2018)
|
||||||
|
|
||||||
|
FEATURES:
|
||||||
|
|
||||||
|
- [p2p] Allow peers with different Minor versions to connect
|
||||||
|
- [rpc] `/net_info` includes `n_peers`
|
||||||
|
|
||||||
|
IMPROVEMENTS:
|
||||||
|
|
||||||
|
- [p2p] Various code comments, cleanup, error types
|
||||||
|
- [p2p] Change some Error logs to Debug
|
||||||
|
|
||||||
BUG FIXES:
|
BUG FIXES:
|
||||||
- Graceful handling/recovery for apps that have non-determinism or fail to halt
|
|
||||||
- Graceful handling/recovery for violations of safety, or liveness
|
- [p2p] Fix reconnect to persistent peer when first dial fails
|
||||||
|
- [p2p] Validate NodeInfo.ListenAddr
|
||||||
|
- [p2p] Only allow (MaxNumPeers - MaxNumOutboundPeers) inbound peers
|
||||||
|
- [p2p/pex] Limit max msg size to 64kB
|
||||||
|
- [p2p] Fix panic when pex=false
|
||||||
|
- [p2p] Allow multiple IPs per ID in AddrBook
|
||||||
|
- [p2p] Fix before/after bugs in addrbook isBad()
|
||||||
|
|
||||||
|
## 0.19.1 (April 27th, 2018)
|
||||||
|
|
||||||
|
Note this release includes some small breaking changes in the RPC and one in the
|
||||||
|
config that are really bug fixes. v0.19.1 will work with existing chains, and make Tendermint
|
||||||
|
easier to use and debug. With <3
|
||||||
|
|
||||||
|
BREAKING (MINOR)
|
||||||
|
|
||||||
|
- [config] Removed `wal_light` setting. If you really needed this, let us know
|
||||||
|
|
||||||
|
FEATURES:
|
||||||
|
|
||||||
|
- [networks] moved in tooling from devops repo: terraform and ansible scripts for deploying testnets !
|
||||||
|
- [cmd] Added `gen_node_key` command
|
||||||
|
|
||||||
|
BUG FIXES
|
||||||
|
|
||||||
|
Some of these are breaking in the RPC response, but they're really bugs!
|
||||||
|
|
||||||
|
- [spec] Document address format and pubkey encoding pre and post Amino
|
||||||
|
- [rpc] Lower case JSON field names
|
||||||
|
- [rpc] Fix missing entries, improve, and lower case the fields in `/dump_consensus_state`
|
||||||
|
- [rpc] Fix NodeInfo.Channels format to hex
|
||||||
|
- [rpc] Add Validator address to `/status`
|
||||||
|
- [rpc] Fix `prove` in ABCIQuery
|
||||||
|
- [cmd] MarshalJSONIndent on init
|
||||||
|
|
||||||
|
## 0.19.0 (April 13th, 2018)
|
||||||
|
|
||||||
|
BREAKING:
|
||||||
|
- [cmd] improved `testnet` command; now it can fill in `persistent_peers` for you in the config file and much more (see `tendermint testnet --help` for details)
|
||||||
|
- [cmd] `show_node_id` now returns an error if there is no node key
|
||||||
|
- [rpc]: changed the output format for the `/status` endpoint (see https://godoc.org/github.com/tendermint/tendermint/rpc/core#Status)
|
||||||
|
|
||||||
|
Upgrade from go-wire to go-amino. This is a sweeping change that breaks everything that is
|
||||||
|
serialized to disk or over the network.
|
||||||
|
|
||||||
|
See github.com/tendermint/go-amino for details on the new format.
|
||||||
|
|
||||||
|
See `scripts/wire2amino.go` for a tool to upgrade
|
||||||
|
genesis/priv_validator/node_key JSON files.
|
||||||
|
|
||||||
|
FEATURES
|
||||||
|
|
||||||
|
- [test] docker-compose for local testnet setup (thanks Greg!)
|
||||||
|
|
||||||
|
## 0.18.0 (April 6th, 2018)
|
||||||
|
|
||||||
|
BREAKING:
|
||||||
|
|
||||||
|
- [types] Merkle tree uses different encoding for varints (see tmlibs v0.8.0)
|
||||||
|
- [types] ValidtorSet.GetByAddress returns -1 if no validator found
|
||||||
|
- [p2p] require all addresses come with an ID no matter what
|
||||||
|
- [rpc] Listening address must contain tcp:// or unix:// prefix
|
||||||
|
|
||||||
|
FEATURES:
|
||||||
|
|
||||||
|
- [rpc] StartHTTPAndTLSServer (not used yet)
|
||||||
|
- [rpc] Include validator's voting power in `/status`
|
||||||
|
- [rpc] `/tx` and `/tx_search` responses now include the transaction hash
|
||||||
|
- [rpc] Include peer NodeIDs in `/net_info`
|
||||||
|
|
||||||
|
IMPROVEMENTS:
|
||||||
|
- [config] trim whitespace from elements of lists (like `persistent_peers`)
|
||||||
|
- [rpc] `/tx_search` results are sorted by height
|
||||||
|
- [p2p] do not try to connect to ourselves (ok, maybe only once)
|
||||||
|
- [p2p] seeds respond with a bias towards good peers
|
||||||
|
|
||||||
|
BUG FIXES:
|
||||||
|
- [rpc] fix subscribing using an abci.ResponseDeliverTx tag
|
||||||
|
- [rpc] fix tx_indexers matchRange
|
||||||
|
- [rpc] fix unsubscribing (see tmlibs v0.8.0)
|
||||||
|
|
||||||
## 0.17.1 (March 27th, 2018)
|
## 0.17.1 (March 27th, 2018)
|
||||||
|
|
||||||
BUG FIXES:
|
BUG FIXES:
|
||||||
|
|
||||||
- [types] Actually support `app_state` in genesis as `AppStateJSON`
|
- [types] Actually support `app_state` in genesis as `AppStateJSON`
|
||||||
|
|
||||||
## 0.17.0 (March 27th, 2018)
|
## 0.17.0 (March 27th, 2018)
|
||||||
|
1
DOCKER/.gitignore
vendored
Normal file
1
DOCKER/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
tendermint
|
@@ -1,45 +1,39 @@
|
|||||||
FROM alpine:3.6
|
FROM alpine:3.7
|
||||||
|
MAINTAINER Greg Szabo <greg@tendermint.com>
|
||||||
|
|
||||||
# This is the release of tendermint to pull in.
|
# Tendermint will be looking for the genesis file in /tendermint/config/genesis.json
|
||||||
ENV TM_VERSION 0.15.0
|
# (unless you change `genesis_file` in config.toml). You can put your config.toml and
|
||||||
ENV TM_SHA256SUM 71cc271c67eca506ca492c8b90b090132f104bf5dbfe0af2702a50886e88de17
|
# private validator file into /tendermint/config.
|
||||||
|
|
||||||
# Tendermint will be looking for genesis file in /tendermint (unless you change
|
|
||||||
# `genesis_file` in config.toml). You can put your config.toml and private
|
|
||||||
# validator file into /tendermint.
|
|
||||||
#
|
#
|
||||||
# The /tendermint/data dir is used by tendermint to store state.
|
# The /tendermint/data dir is used by tendermint to store state.
|
||||||
ENV DATA_ROOT /tendermint
|
ENV TMHOME /tendermint
|
||||||
ENV TMHOME $DATA_ROOT
|
|
||||||
|
|
||||||
# Set user right away for determinism
|
|
||||||
RUN addgroup tmuser && \
|
|
||||||
adduser -S -G tmuser tmuser
|
|
||||||
|
|
||||||
# Create directory for persistence and give our user ownership
|
|
||||||
RUN mkdir -p $DATA_ROOT && \
|
|
||||||
chown -R tmuser:tmuser $DATA_ROOT
|
|
||||||
|
|
||||||
|
# OS environment setup
|
||||||
|
# Set user right away for determinism, create directory for persistence and give our user ownership
|
||||||
# jq and curl used for extracting `pub_key` from private validator while
|
# jq and curl used for extracting `pub_key` from private validator while
|
||||||
# deploying tendermint with Kubernetes. It is nice to have bash so the users
|
# deploying tendermint with Kubernetes. It is nice to have bash so the users
|
||||||
# could execute bash commands.
|
# could execute bash commands.
|
||||||
RUN apk add --no-cache bash curl jq
|
RUN apk update && \
|
||||||
|
apk upgrade && \
|
||||||
|
apk --no-cache add curl jq bash && \
|
||||||
|
addgroup tmuser && \
|
||||||
|
adduser -S -G tmuser tmuser -h "$TMHOME"
|
||||||
|
|
||||||
RUN apk add --no-cache openssl && \
|
# Run the container with tmuser by default. (UID=100, GID=1000)
|
||||||
wget https://s3-us-west-2.amazonaws.com/tendermint/binaries/tendermint/v${TM_VERSION}/tendermint_${TM_VERSION}_linux_amd64.zip && \
|
USER tmuser
|
||||||
echo "${TM_SHA256SUM} tendermint_${TM_VERSION}_linux_amd64.zip" | sha256sum -c && \
|
|
||||||
unzip -d /bin tendermint_${TM_VERSION}_linux_amd64.zip && \
|
|
||||||
apk del openssl && \
|
|
||||||
rm -f tendermint_${TM_VERSION}_linux_amd64.zip
|
|
||||||
|
|
||||||
# Expose the data directory as a volume since there's mutable state in there
|
# Expose the data directory as a volume since there's mutable state in there
|
||||||
VOLUME $DATA_ROOT
|
VOLUME [ $TMHOME ]
|
||||||
|
|
||||||
# p2p port
|
WORKDIR $TMHOME
|
||||||
EXPOSE 46656
|
|
||||||
# rpc port
|
|
||||||
EXPOSE 46657
|
|
||||||
|
|
||||||
ENTRYPOINT ["tendermint"]
|
# p2p and rpc port
|
||||||
|
EXPOSE 46656 46657
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/tendermint"]
|
||||||
CMD ["node", "--moniker=`hostname`"]
|
CMD ["node", "--moniker=`hostname`"]
|
||||||
|
STOPSIGNAL SIGTERM
|
||||||
|
|
||||||
|
ARG BINARY=tendermint
|
||||||
|
COPY $BINARY /usr/bin/tendermint
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
FROM alpine:3.6
|
FROM alpine:3.7
|
||||||
|
|
||||||
ENV DATA_ROOT /tendermint
|
ENV DATA_ROOT /tendermint
|
||||||
ENV TMHOME $DATA_ROOT
|
ENV TMHOME $DATA_ROOT
|
||||||
|
18
DOCKER/Dockerfile.testing
Normal file
18
DOCKER/Dockerfile.testing
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
FROM golang:1.10.1
|
||||||
|
|
||||||
|
|
||||||
|
# Grab deps (jq, hexdump, xxd, killall)
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
jq bsdmainutils vim-common psmisc netcat
|
||||||
|
|
||||||
|
# Add testing deps for curl
|
||||||
|
RUN echo 'deb http://httpredir.debian.org/debian testing main non-free contrib' >> /etc/apt/sources.list && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends curl
|
||||||
|
|
||||||
|
VOLUME /go
|
||||||
|
|
||||||
|
EXPOSE 46656
|
||||||
|
EXPOSE 46657
|
||||||
|
|
@@ -7,6 +7,9 @@ push:
|
|||||||
build_develop:
|
build_develop:
|
||||||
docker build -t "tendermint/tendermint:develop" -f Dockerfile.develop .
|
docker build -t "tendermint/tendermint:develop" -f Dockerfile.develop .
|
||||||
|
|
||||||
|
build_testing:
|
||||||
|
docker build --tag tendermint/testing -f ./Dockerfile.testing .
|
||||||
|
|
||||||
push_develop:
|
push_develop:
|
||||||
docker push "tendermint/tendermint:develop"
|
docker push "tendermint/tendermint:develop"
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
# Supported tags and respective `Dockerfile` links
|
# Supported tags and respective `Dockerfile` links
|
||||||
|
|
||||||
- `0.15.0`, `latest` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/170777300ea92dc21a8aec1abc16cb51812513a4/DOCKER/Dockerfile)
|
- `0.17.1`, `latest` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/208ac32fa266657bd6c304e84ec828aa252bb0b8/DOCKER/Dockerfile)
|
||||||
|
- `0.15.0` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/170777300ea92dc21a8aec1abc16cb51812513a4/DOCKER/Dockerfile)
|
||||||
- `0.13.0` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/a28b3fff49dce2fb31f90abb2fc693834e0029c2/DOCKER/Dockerfile)
|
- `0.13.0` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/a28b3fff49dce2fb31f90abb2fc693834e0029c2/DOCKER/Dockerfile)
|
||||||
- `0.12.1` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/457c688346b565e90735431619ca3ca597ef9007/DOCKER/Dockerfile)
|
- `0.12.1` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/457c688346b565e90735431619ca3ca597ef9007/DOCKER/Dockerfile)
|
||||||
- `0.12.0` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/70d8afa6e952e24c573ece345560a5971bf2cc0e/DOCKER/Dockerfile)
|
- `0.12.0` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/70d8afa6e952e24c573ece345560a5971bf2cc0e/DOCKER/Dockerfile)
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
# Quick reference
|
# Quick reference
|
||||||
|
|
||||||
* **Where to get help:**
|
* **Where to get help:**
|
||||||
https://tendermint.com/community
|
https://cosmos.network/community
|
||||||
|
|
||||||
* **Where to file issues:**
|
* **Where to file issues:**
|
||||||
https://github.com/tendermint/tendermint/issues
|
https://github.com/tendermint/tendermint/issues
|
||||||
@@ -36,25 +37,29 @@ To get started developing applications, see the [application developers guide](h
|
|||||||
|
|
||||||
## Start one instance of the Tendermint core with the `kvstore` 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.
|
A quick 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 init
|
||||||
docker run -it --rm -v "/tmp:/tendermint" tendermint/tendermint node --proxy_app=kvstore
|
docker run -it --rm -v "/tmp:/tendermint" tendermint/tendermint node --proxy_app=kvstore
|
||||||
```
|
```
|
||||||
|
|
||||||
## mintnet-kubernetes
|
# Local cluster
|
||||||
|
|
||||||
If you want to see many containers talking to each other, consider using [mintnet-kubernetes](https://github.com/tendermint/tools/tree/master/mintnet-kubernetes), which is a tool for running Tendermint-based applications on a Kubernetes cluster.
|
To run a 4-node network, see the `Makefile` in the root of [the repo](https://github.com/tendermint/tendermint/master/Makefile) and run:
|
||||||
|
|
||||||
|
```
|
||||||
|
make build-linux
|
||||||
|
make build-docker-localnode
|
||||||
|
make localnet-start
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that this will build and use a different image than the ones provided here.
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
View [license information](https://raw.githubusercontent.com/tendermint/tendermint/master/LICENSE) for the software contained in this image.
|
- Tendermint's license is [Apache 2.0](https://github.com/tendermint/tendermint/master/LICENSE).
|
||||||
|
|
||||||
# User Feedback
|
# Contributing
|
||||||
|
|
||||||
## Contributing
|
Contributions are most welcome! See the [contributing file](https://github.com/tendermint/tendermint/blob/master/CONTRIBUTING.md) for more information.
|
||||||
|
|
||||||
You are invited to contribute new features, fixes, or updates, large or small; we are always thrilled to receive pull requests, and do our best to process them as fast as we can.
|
|
||||||
|
|
||||||
Before you start to code, we recommend discussing your plans through a [GitHub](https://github.com/tendermint/tendermint/issues) issue, especially for more ambitious contributions. This gives other contributors a chance to point you in the right direction, give you feedback on your design, and help you find out if someone else is working on the same thing.
|
|
||||||
|
77
Gopkg.lock
generated
77
Gopkg.lock
generated
@@ -5,7 +5,7 @@
|
|||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/btcsuite/btcd"
|
name = "github.com/btcsuite/btcd"
|
||||||
packages = ["btcec"]
|
packages = ["btcec"]
|
||||||
revision = "2be2f12b358dc57d70b8f501b00be450192efbc3"
|
revision = "86fed781132ac890ee03e906e4ecd5d6fa180c64"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/davecgh/go-spew"
|
name = "github.com/davecgh/go-spew"
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/golang/snappy"
|
name = "github.com/golang/snappy"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "553a641470496b2327abcac10b36396bd98e45c9"
|
revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/gorilla/websocket"
|
name = "github.com/gorilla/websocket"
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
"json/scanner",
|
"json/scanner",
|
||||||
"json/token"
|
"json/token"
|
||||||
]
|
]
|
||||||
revision = "f40e974e75af4e271d97ce0fc917af5898ae7bda"
|
revision = "ef8a98b0bbce4a65b5aa4c368430a80ddc533168"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/inconshreveable/mousetrap"
|
name = "github.com/inconshreveable/mousetrap"
|
||||||
@@ -128,20 +128,20 @@
|
|||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/magiconair/properties"
|
name = "github.com/magiconair/properties"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "c3beff4c2358b44d0493c7dda585e7db7ff28ae6"
|
revision = "c2353362d570a7bfa228149c62842019201cfb71"
|
||||||
version = "v1.7.6"
|
version = "v1.8.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/mitchellh/mapstructure"
|
name = "github.com/mitchellh/mapstructure"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "00c29f56e2386353d58c599509e8dc3801b0d716"
|
revision = "bb74f1db0675b241733089d5a1faa5dd8b0ef57b"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/pelletier/go-toml"
|
name = "github.com/pelletier/go-toml"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "acdc4509485b587f5e675510c4f2c63e90ff68a8"
|
revision = "c01d1270ff3e442a8a57cddc1c92dc1138598194"
|
||||||
version = "v1.1.0"
|
version = "v1.2.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/pkg/errors"
|
name = "github.com/pkg/errors"
|
||||||
@@ -159,7 +159,7 @@
|
|||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/rcrowley/go-metrics"
|
name = "github.com/rcrowley/go-metrics"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "8732c616f52954686704c8645fe1a9d59e9df7c1"
|
revision = "e2704e165165ec55d062f5919b4b29494e9fa790"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/spf13/afero"
|
name = "github.com/spf13/afero"
|
||||||
@@ -167,8 +167,8 @@
|
|||||||
".",
|
".",
|
||||||
"mem"
|
"mem"
|
||||||
]
|
]
|
||||||
revision = "bb8f1927f2a9d3ab41c9340aa034f6b803f4359c"
|
revision = "63644898a8da0bc22138abf860edaf5277b6102e"
|
||||||
version = "v1.0.2"
|
version = "v1.1.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/spf13/cast"
|
name = "github.com/spf13/cast"
|
||||||
@@ -179,8 +179,8 @@
|
|||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/spf13/cobra"
|
name = "github.com/spf13/cobra"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b"
|
revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385"
|
||||||
version = "v0.0.1"
|
version = "v0.0.3"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
@@ -191,8 +191,8 @@
|
|||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/spf13/pflag"
|
name = "github.com/spf13/pflag"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66"
|
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
|
||||||
version = "v1.0.0"
|
version = "v1.0.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/spf13/viper"
|
name = "github.com/spf13/viper"
|
||||||
@@ -226,7 +226,7 @@
|
|||||||
"leveldb/table",
|
"leveldb/table",
|
||||||
"leveldb/util"
|
"leveldb/util"
|
||||||
]
|
]
|
||||||
revision = "169b1b37be738edb2813dab48c97a549bcf99bb5"
|
revision = "5d6fca44a948d2be89a9702de7717f0168403d3d"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/tendermint/abci"
|
name = "github.com/tendermint/abci"
|
||||||
@@ -238,8 +238,8 @@
|
|||||||
"server",
|
"server",
|
||||||
"types"
|
"types"
|
||||||
]
|
]
|
||||||
revision = "46686763ba8ea595ede16530ed4a40fb38f49f94"
|
revision = "ebee2fe114020aa49c70bbbae50b7079fc7e7b90"
|
||||||
version = "v0.10.2"
|
version = "v0.11.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
@@ -252,20 +252,16 @@
|
|||||||
revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057"
|
revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/tendermint/go-crypto"
|
name = "github.com/tendermint/go-amino"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "c3e19f3ea26f5c3357e0bcbb799b0761ef923755"
|
revision = "ed62928576cfcaf887209dc96142cd79cdfff389"
|
||||||
version = "v0.5.0"
|
version = "0.9.9"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/tendermint/go-wire"
|
name = "github.com/tendermint/go-crypto"
|
||||||
packages = [
|
packages = ["."]
|
||||||
".",
|
revision = "915416979bf70efa4bcbf1c6cd5d64c5fff9fc19"
|
||||||
"data"
|
version = "v0.6.2"
|
||||||
]
|
|
||||||
revision = "fa721242b042ecd4c6ed1a934ee740db4f74e45c"
|
|
||||||
source = "github.com/tendermint/go-amino"
|
|
||||||
version = "v0.7.3"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/tendermint/tmlibs"
|
name = "github.com/tendermint/tmlibs"
|
||||||
@@ -279,12 +275,10 @@
|
|||||||
"flowrate",
|
"flowrate",
|
||||||
"log",
|
"log",
|
||||||
"merkle",
|
"merkle",
|
||||||
"pubsub",
|
|
||||||
"pubsub/query",
|
|
||||||
"test"
|
"test"
|
||||||
]
|
]
|
||||||
revision = "24da7009c3d8c019b40ba4287495749e3160caca"
|
revision = "692f1d86a6e2c0efa698fd1e4541b68c74ffaf38"
|
||||||
version = "v0.7.1"
|
version = "v0.8.4"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
@@ -299,27 +293,27 @@
|
|||||||
"ripemd160",
|
"ripemd160",
|
||||||
"salsa20/salsa"
|
"salsa20/salsa"
|
||||||
]
|
]
|
||||||
revision = "88942b9c40a4c9d203b82b3731787b672d6e809b"
|
revision = "b47b1587369238182299fe4dad77d05b8b461e06"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/net"
|
name = "golang.org/x/net"
|
||||||
packages = [
|
packages = [
|
||||||
"context",
|
"context",
|
||||||
|
"http/httpguts",
|
||||||
"http2",
|
"http2",
|
||||||
"http2/hpack",
|
"http2/hpack",
|
||||||
"idna",
|
"idna",
|
||||||
"internal/timeseries",
|
"internal/timeseries",
|
||||||
"lex/httplex",
|
|
||||||
"trace"
|
"trace"
|
||||||
]
|
]
|
||||||
revision = "6078986fec03a1dcc236c34816c71b0e05018fda"
|
revision = "1e491301e022f8f977054da4c2d852decd59571f"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/sys"
|
name = "golang.org/x/sys"
|
||||||
packages = ["unix"]
|
packages = ["unix"]
|
||||||
revision = "91ee8cde435411ca3f1cd365e8f20131aed4d0a1"
|
revision = "9527bec2660bd847c050fda93a0f0c6dee0800bb"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "golang.org/x/text"
|
name = "golang.org/x/text"
|
||||||
@@ -343,10 +337,9 @@
|
|||||||
version = "v0.3.0"
|
version = "v0.3.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
|
||||||
name = "google.golang.org/genproto"
|
name = "google.golang.org/genproto"
|
||||||
packages = ["googleapis/rpc/status"]
|
packages = ["googleapis/rpc/status"]
|
||||||
revision = "f8c8703595236ae70fdf8789ecb656ea0bcdcf46"
|
revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "google.golang.org/grpc"
|
name = "google.golang.org/grpc"
|
||||||
@@ -375,12 +368,12 @@
|
|||||||
[[projects]]
|
[[projects]]
|
||||||
name = "gopkg.in/yaml.v2"
|
name = "gopkg.in/yaml.v2"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "7f97868eec74b32b0982dd158a51a446d1da7eb5"
|
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
|
||||||
version = "v2.1.1"
|
version = "v2.2.1"
|
||||||
|
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "4dca5dbd2d280d093d7c8fc423606ab86d6ad1b241b076a7716c2093b5a09231"
|
inputs-digest = "ae6792578b0664708339f4c05e020687d6b799ad6f8282394b919de69e403d1f"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
24
Gopkg.toml
24
Gopkg.toml
@@ -26,12 +26,12 @@
|
|||||||
|
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
branch = "master"
|
|
||||||
name = "github.com/ebuchman/fail-test"
|
name = "github.com/ebuchman/fail-test"
|
||||||
|
branch = "master"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
branch = "master"
|
|
||||||
name = "github.com/fortytw2/leaktest"
|
name = "github.com/fortytw2/leaktest"
|
||||||
|
branch = "master"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/go-kit/kit"
|
name = "github.com/go-kit/kit"
|
||||||
@@ -54,8 +54,8 @@
|
|||||||
version = "~0.8.0"
|
version = "~0.8.0"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
branch = "master"
|
|
||||||
name = "github.com/rcrowley/go-metrics"
|
name = "github.com/rcrowley/go-metrics"
|
||||||
|
branch = "master"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/spf13/cobra"
|
name = "github.com/spf13/cobra"
|
||||||
@@ -71,25 +71,29 @@
|
|||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/tendermint/abci"
|
name = "github.com/tendermint/abci"
|
||||||
version = "~0.10.2"
|
version = "~0.11.0"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/tendermint/go-crypto"
|
name = "github.com/tendermint/go-crypto"
|
||||||
version = "~0.5.0"
|
version = "~0.6.2"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/tendermint/go-wire"
|
name = "github.com/tendermint/go-amino"
|
||||||
source = "github.com/tendermint/go-amino"
|
version = "=0.9.9"
|
||||||
version = "~0.7.3"
|
|
||||||
|
|
||||||
[[constraint]]
|
[[override]]
|
||||||
name = "github.com/tendermint/tmlibs"
|
name = "github.com/tendermint/tmlibs"
|
||||||
version = "~0.7.1"
|
version = "~0.8.4"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "google.golang.org/grpc"
|
name = "google.golang.org/grpc"
|
||||||
version = "~1.7.3"
|
version = "~1.7.3"
|
||||||
|
|
||||||
|
# this got updated and broke, so locked to an old working commit ...
|
||||||
|
[[override]]
|
||||||
|
name = "google.golang.org/genproto"
|
||||||
|
revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200"
|
||||||
|
|
||||||
[prune]
|
[prune]
|
||||||
go-tests = true
|
go-tests = true
|
||||||
unused-packages = true
|
unused-packages = true
|
||||||
|
65
Makefile
Normal file → Executable file
65
Makefile
Normal file → Executable file
@@ -14,13 +14,13 @@ check: check_tools ensure_deps
|
|||||||
### Build
|
### Build
|
||||||
|
|
||||||
build:
|
build:
|
||||||
go build $(BUILD_FLAGS) -tags '$(BUILD_TAGS)' -o build/tendermint ./cmd/tendermint/
|
CGO_ENABLED=0 go build $(BUILD_FLAGS) -tags '$(BUILD_TAGS)' -o build/tendermint ./cmd/tendermint/
|
||||||
|
|
||||||
build_race:
|
build_race:
|
||||||
go build -race $(BUILD_FLAGS) -tags '$(BUILD_TAGS)' -o build/tendermint ./cmd/tendermint
|
CGO_ENABLED=0 go build -race $(BUILD_FLAGS) -tags '$(BUILD_TAGS)' -o build/tendermint ./cmd/tendermint
|
||||||
|
|
||||||
install:
|
install:
|
||||||
go install $(BUILD_FLAGS) -tags '$(BUILD_TAGS)' ./cmd/tendermint
|
CGO_ENABLED=0 go install $(BUILD_FLAGS) -tags '$(BUILD_TAGS)' ./cmd/tendermint
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
### Distribution
|
### Distribution
|
||||||
@@ -119,11 +119,6 @@ test_integrations:
|
|||||||
make test_persistence
|
make test_persistence
|
||||||
make test_p2p
|
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:
|
test_release:
|
||||||
@go test -tags release $(PACKAGES)
|
@go test -tags release $(PACKAGES)
|
||||||
|
|
||||||
@@ -183,7 +178,59 @@ metalinter_all:
|
|||||||
@echo "--> Running linter (all)"
|
@echo "--> Running linter (all)"
|
||||||
gometalinter.v2 --vendor --deadline=600s --enable-all --disable=lll ./...
|
gometalinter.v2 --vendor --deadline=600s --enable-all --disable=lll ./...
|
||||||
|
|
||||||
|
###########################################################
|
||||||
|
### Docker image
|
||||||
|
|
||||||
|
build-docker:
|
||||||
|
cp build/tendermint DOCKER/tendermint
|
||||||
|
docker build --label=tendermint --tag="tendermint/tendermint" DOCKER
|
||||||
|
rm -rf DOCKER/tendermint
|
||||||
|
|
||||||
|
###########################################################
|
||||||
|
### Local testnet using docker
|
||||||
|
|
||||||
|
# Build linux binary on other platforms
|
||||||
|
build-linux:
|
||||||
|
GOOS=linux GOARCH=amd64 $(MAKE) build
|
||||||
|
|
||||||
|
build-docker-localnode:
|
||||||
|
cd networks/local
|
||||||
|
make
|
||||||
|
|
||||||
|
# Run a 4-node testnet locally
|
||||||
|
localnet-start: localnet-stop
|
||||||
|
@if ! [ -f build/node0/config/genesis.json ]; then docker run --rm -v $(CURDIR)/build:/tendermint:Z tendermint/localnode testnet --v 4 --o . --populate-persistent-peers --starting-ip-address 192.167.10.2 ; fi
|
||||||
|
docker-compose up
|
||||||
|
|
||||||
|
# Stop testnet
|
||||||
|
localnet-stop:
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
###########################################################
|
||||||
|
### Remote full-nodes (sentry) using terraform and ansible
|
||||||
|
|
||||||
|
# Server management
|
||||||
|
sentry-start:
|
||||||
|
@if [ -z "$(DO_API_TOKEN)" ]; then echo "DO_API_TOKEN environment variable not set." ; false ; fi
|
||||||
|
@if ! [ -f $(HOME)/.ssh/id_rsa.pub ]; then ssh-keygen ; fi
|
||||||
|
cd networks/remote/terraform && terraform init && terraform apply -var DO_API_TOKEN="$(DO_API_TOKEN)" -var SSH_KEY_FILE="$(HOME)/.ssh/id_rsa.pub"
|
||||||
|
@if ! [ -f $(CURDIR)/build/node0/config/genesis.json ]; then docker run --rm -v $(CURDIR)/build:/tendermint:Z tendermint/localnode testnet --v 0 --n 4 --o . ; fi
|
||||||
|
cd networks/remote/ansible && ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -i inventory/digital_ocean.py -l sentrynet install.yml
|
||||||
|
@echo "Next step: Add your validator setup in the genesis.json and config.tml files and run \"make sentry-config\". (Public key of validator, chain ID, peer IP and node ID.)"
|
||||||
|
|
||||||
|
# Configuration management
|
||||||
|
sentry-config:
|
||||||
|
cd networks/remote/ansible && ansible-playbook -i inventory/digital_ocean.py -l sentrynet config.yml -e BINARY=$(CURDIR)/build/tendermint -e CONFIGDIR=$(CURDIR)/build
|
||||||
|
|
||||||
|
sentry-stop:
|
||||||
|
@if [ -z "$(DO_API_TOKEN)" ]; then echo "DO_API_TOKEN environment variable not set." ; false ; fi
|
||||||
|
cd networks/remote/terraform && terraform destroy -var DO_API_TOKEN="$(DO_API_TOKEN)" -var SSH_KEY_FILE="$(HOME)/.ssh/id_rsa.pub"
|
||||||
|
|
||||||
|
# meant for the CI, inspect script & adapt accordingly
|
||||||
|
build-slate:
|
||||||
|
bash scripts/slate.sh
|
||||||
|
|
||||||
# To avoid unintended conflicts with file names, always add to .PHONY
|
# To avoid unintended conflicts with file names, always add to .PHONY
|
||||||
# unless there is a reason not to.
|
# unless there is a reason not to.
|
||||||
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
|
# 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_cover test_apps test_persistence test_p2p test test_race test_libs test_integrations test_release test100 vagrant_test fmt
|
.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_integrations test_release test100 vagrant_test fmt build-linux localnet-start localnet-stop build-docker build-docker-localnode sentry-start sentry-config sentry-stop build-slate
|
||||||
|
31
README.md
31
README.md
@@ -24,7 +24,14 @@ _NOTE: This is alpha software. Please contact us if you intend to run it in prod
|
|||||||
Tendermint Core is Byzantine Fault Tolerant (BFT) middleware that takes a state transition machine - written in any programming language -
|
Tendermint Core is Byzantine Fault Tolerant (BFT) middleware that takes a state transition machine - written in any programming language -
|
||||||
and securely replicates it on many machines.
|
and securely replicates it on many machines.
|
||||||
|
|
||||||
For more information, from introduction to install to application development, [Read The Docs](https://tendermint.readthedocs.io/en/master/).
|
For protocol details, see [the specification](/docs/spec).
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
To report a security vulnerability, see our [bug bounty
|
||||||
|
program](https://tendermint.com/security).
|
||||||
|
|
||||||
|
For examples of the kinds of bugs we're looking for, see [SECURITY.md](SECURITY.md)
|
||||||
|
|
||||||
## Minimum requirements
|
## Minimum requirements
|
||||||
|
|
||||||
@@ -34,19 +41,21 @@ Go version | Go1.9 or higher
|
|||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
To download pre-built binaries, see our [downloads page](https://tendermint.com/downloads).
|
See the [install instructions](/docs/install.rst)
|
||||||
|
|
||||||
To install from source, you should be able to:
|
## Quick Start
|
||||||
|
|
||||||
`go get -u github.com/tendermint/tendermint/cmd/tendermint`
|
- [Single node](/docs/using-tendermint.rst)
|
||||||
|
- [Local cluster using docker-compose](/networks/local)
|
||||||
For more details (or if it fails), [read the docs](https://tendermint.readthedocs.io/en/master/install.html).
|
- [Remote cluster using terraform and ansible](/docs/terraform-and-ansible.rst)
|
||||||
|
- [Join the public testnet](https://cosmos.network/testnet)
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
### Tendermint Core
|
### Tendermint Core
|
||||||
|
|
||||||
All resources involving the use of, building application on, or developing for, tendermint, can be found at [Read The Docs](https://tendermint.readthedocs.io/en/master/). Additional information about some - and eventually all - of the sub-projects below, can be found at Read The Docs.
|
For more, [Read The Docs](https://tendermint.readthedocs.io/en/master/).
|
||||||
|
Additional information about some - and eventually all - of the sub-projects below, can be found at Read The Docs.
|
||||||
|
|
||||||
### Sub-projects
|
### Sub-projects
|
||||||
|
|
||||||
@@ -61,8 +70,8 @@ All resources involving the use of, building application on, or developing for,
|
|||||||
|
|
||||||
### Applications
|
### Applications
|
||||||
|
|
||||||
* [Ethermint](http://github.com/tendermint/ethermint); Ethereum on Tendermint
|
|
||||||
* [Cosmos SDK](http://github.com/cosmos/cosmos-sdk); a cryptocurrency application framework
|
* [Cosmos SDK](http://github.com/cosmos/cosmos-sdk); a cryptocurrency application framework
|
||||||
|
* [Ethermint](http://github.com/tendermint/ethermint); Ethereum on Tendermint
|
||||||
* [Many more](https://tendermint.readthedocs.io/en/master/ecosystem.html#abci-applications)
|
* [Many more](https://tendermint.readthedocs.io/en/master/ecosystem.html#abci-applications)
|
||||||
|
|
||||||
### More
|
### More
|
||||||
@@ -85,7 +94,11 @@ According to SemVer, anything in the public API can change at any time before ve
|
|||||||
|
|
||||||
To provide some stability to Tendermint users in these 0.X.X days, the MINOR version is used
|
To provide some stability to Tendermint users in these 0.X.X days, the MINOR version is used
|
||||||
to signal breaking changes across a subset of the total public API. This subset includes all
|
to signal breaking changes across a subset of the total public API. This subset includes all
|
||||||
interfaces exposed to other processes (cli, rpc, p2p, etc.), as well as parts of the following packages:
|
interfaces exposed to other processes (cli, rpc, p2p, etc.), but does not
|
||||||
|
include the in-process Go APIs.
|
||||||
|
|
||||||
|
That said, breaking changes in the following packages will be documented in the
|
||||||
|
CHANGELOG even if they don't lead to MINOR version bumps:
|
||||||
|
|
||||||
- types
|
- types
|
||||||
- rpc/client
|
- rpc/client
|
||||||
|
23
ROADMAP.md
Normal file
23
ROADMAP.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Roadmap
|
||||||
|
|
||||||
|
BREAKING CHANGES:
|
||||||
|
- Better support for injecting randomness
|
||||||
|
- Upgrade consensus for more real-time use of evidence
|
||||||
|
|
||||||
|
FEATURES:
|
||||||
|
- Use the chain as its own CA for nodes and validators
|
||||||
|
- Tooling to run multiple blockchains/apps, possibly in a single process
|
||||||
|
- State syncing (without transaction replay)
|
||||||
|
- Add authentication and rate-limitting to the RPC
|
||||||
|
|
||||||
|
IMPROVEMENTS:
|
||||||
|
- Improve subtleties around mempool caching and logic
|
||||||
|
- Consensus optimizations:
|
||||||
|
- cache block parts for faster agreement after round changes
|
||||||
|
- propagate block parts rarest first
|
||||||
|
- Better testing of the consensus state machine (ie. use a DSL)
|
||||||
|
- Auto compiled serialization/deserialization code instead of go-wire reflection
|
||||||
|
|
||||||
|
BUG FIXES:
|
||||||
|
- Graceful handling/recovery for apps that have non-determinism or fail to halt
|
||||||
|
- Graceful handling/recovery for violations of safety, or liveness
|
71
SECURITY.md
Normal file
71
SECURITY.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# Security
|
||||||
|
|
||||||
|
As part of our [Coordinated Vulnerability Disclosure
|
||||||
|
Policy](https://tendermint.com/security), we operate a bug bounty.
|
||||||
|
See the policy for more details on submissions and rewards.
|
||||||
|
|
||||||
|
Here is a list of examples of the kinds of bugs we're most interested in:
|
||||||
|
|
||||||
|
## Specification
|
||||||
|
|
||||||
|
- Conceptual flaws
|
||||||
|
- Ambiguities, inconsistencies, or incorrect statements
|
||||||
|
- Mis-match between specification and implementation of any component
|
||||||
|
|
||||||
|
## Consensus
|
||||||
|
|
||||||
|
Assuming less than 1/3 of the voting power is Byzantine (malicious):
|
||||||
|
|
||||||
|
- Validation of blockchain data structures, including blocks, block parts,
|
||||||
|
votes, and so on
|
||||||
|
- Execution of blocks
|
||||||
|
- Validator set changes
|
||||||
|
- Proposer round robin
|
||||||
|
- Two nodes committing conflicting blocks for the same height (safety failure)
|
||||||
|
- A correct node signing conflicting votes
|
||||||
|
- A node halting (liveness failure)
|
||||||
|
- Syncing new and old nodes
|
||||||
|
|
||||||
|
## Networking
|
||||||
|
|
||||||
|
- Authenticated encryption (MITM, information leakage)
|
||||||
|
- Eclipse attacks
|
||||||
|
- Sybil attacks
|
||||||
|
- Long-range attacks
|
||||||
|
- Denial-of-Service
|
||||||
|
|
||||||
|
## RPC
|
||||||
|
|
||||||
|
- Write-access to anything besides sending transactions
|
||||||
|
- Denial-of-Service
|
||||||
|
- Leakage of secrets
|
||||||
|
|
||||||
|
## Denial-of-Service
|
||||||
|
|
||||||
|
Attacks may come through the P2P network or the RPC:
|
||||||
|
|
||||||
|
- Amplification attacks
|
||||||
|
- Resource abuse
|
||||||
|
- Deadlocks and race conditions
|
||||||
|
- Panics and unhandled errors
|
||||||
|
|
||||||
|
## Libraries
|
||||||
|
|
||||||
|
- Serialization (Amino)
|
||||||
|
- Reading/Writing files and databases
|
||||||
|
- Logging and monitoring
|
||||||
|
|
||||||
|
## Cryptography
|
||||||
|
|
||||||
|
- Elliptic curves for validator signatures
|
||||||
|
- Hash algorithms and Merkle trees for block validation
|
||||||
|
- Authenticated encryption for P2P connections
|
||||||
|
|
||||||
|
## Light Client
|
||||||
|
|
||||||
|
- Validation of blockchain data structures
|
||||||
|
- Correctly validating an incorrect proof
|
||||||
|
- Incorrectly validating a correct proof
|
||||||
|
- Syncing validator set changes
|
||||||
|
|
||||||
|
|
34
Vagrantfile
vendored
34
Vagrantfile
vendored
@@ -10,31 +10,37 @@ Vagrant.configure("2") do |config|
|
|||||||
end
|
end
|
||||||
|
|
||||||
config.vm.provision "shell", inline: <<-SHELL
|
config.vm.provision "shell", inline: <<-SHELL
|
||||||
# add docker repo
|
apt-get update
|
||||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
|
|
||||||
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable"
|
|
||||||
|
|
||||||
# and golang 1.9 support
|
|
||||||
# official repo doesn't have race detection runtime...
|
|
||||||
# add-apt-repository ppa:gophers/archive
|
|
||||||
add-apt-repository ppa:longsleep/golang-backports
|
|
||||||
|
|
||||||
# install base requirements
|
# install base requirements
|
||||||
apt-get update
|
|
||||||
apt-get install -y --no-install-recommends wget curl jq zip \
|
apt-get install -y --no-install-recommends wget curl jq zip \
|
||||||
make shellcheck bsdmainutils psmisc
|
make shellcheck bsdmainutils psmisc
|
||||||
apt-get install -y docker-ce golang-1.9-go
|
|
||||||
apt-get install -y language-pack-en
|
apt-get install -y language-pack-en
|
||||||
|
|
||||||
|
# install docker
|
||||||
|
apt-get install -y --no-install-recommends apt-transport-https \
|
||||||
|
ca-certificates curl software-properties-common
|
||||||
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
|
||||||
|
add-apt-repository \
|
||||||
|
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
|
||||||
|
$(lsb_release -cs) \
|
||||||
|
stable"
|
||||||
|
apt-get install -y docker-ce
|
||||||
|
usermod -a -G docker vagrant
|
||||||
|
|
||||||
|
# install go
|
||||||
|
wget -q https://dl.google.com/go/go1.10.1.linux-amd64.tar.gz
|
||||||
|
tar -xvf go1.10.1.linux-amd64.tar.gz
|
||||||
|
mv go /usr/local
|
||||||
|
rm -f go1.10.1.linux-amd64.tar.gz
|
||||||
|
|
||||||
# cleanup
|
# cleanup
|
||||||
apt-get autoremove -y
|
apt-get autoremove -y
|
||||||
|
|
||||||
# needed for docker
|
|
||||||
usermod -a -G docker vagrant
|
|
||||||
|
|
||||||
# set env variables
|
# set env variables
|
||||||
echo 'export PATH=$PATH:/usr/lib/go-1.9/bin:/home/vagrant/go/bin' >> /home/vagrant/.bash_profile
|
echo 'export GOROOT=/usr/local/go' >> /home/vagrant/.bash_profile
|
||||||
echo 'export GOPATH=/home/vagrant/go' >> /home/vagrant/.bash_profile
|
echo 'export GOPATH=/home/vagrant/go' >> /home/vagrant/.bash_profile
|
||||||
|
echo 'export PATH=$PATH:$GOROOT/bin:$GOPATH/bin' >> /home/vagrant/.bash_profile
|
||||||
echo 'export LC_ALL=en_US.UTF-8' >> /home/vagrant/.bash_profile
|
echo 'export LC_ALL=en_US.UTF-8' >> /home/vagrant/.bash_profile
|
||||||
echo 'cd go/src/github.com/tendermint/tendermint' >> /home/vagrant/.bash_profile
|
echo 'cd go/src/github.com/tendermint/tendermint' >> /home/vagrant/.bash_profile
|
||||||
|
|
||||||
|
@@ -4,8 +4,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/tendermint/go-amino"
|
||||||
"github.com/tendermint/go-crypto"
|
"github.com/tendermint/go-crypto"
|
||||||
"github.com/tendermint/go-wire"
|
|
||||||
|
|
||||||
proto "github.com/tendermint/tendermint/benchmarks/proto"
|
proto "github.com/tendermint/tendermint/benchmarks/proto"
|
||||||
"github.com/tendermint/tendermint/p2p"
|
"github.com/tendermint/tendermint/p2p"
|
||||||
@@ -14,26 +14,35 @@ import (
|
|||||||
|
|
||||||
func BenchmarkEncodeStatusWire(b *testing.B) {
|
func BenchmarkEncodeStatusWire(b *testing.B) {
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
pubKey := crypto.GenPrivKeyEd25519().PubKey()
|
cdc := amino.NewCodec()
|
||||||
|
ctypes.RegisterAmino(cdc)
|
||||||
|
nodeKey := p2p.NodeKey{PrivKey: crypto.GenPrivKeyEd25519()}
|
||||||
status := &ctypes.ResultStatus{
|
status := &ctypes.ResultStatus{
|
||||||
NodeInfo: p2p.NodeInfo{
|
NodeInfo: p2p.NodeInfo{
|
||||||
PubKey: pubKey,
|
ID: nodeKey.ID(),
|
||||||
Moniker: "SOMENAME",
|
Moniker: "SOMENAME",
|
||||||
Network: "SOMENAME",
|
Network: "SOMENAME",
|
||||||
ListenAddr: "SOMEADDR",
|
ListenAddr: "SOMEADDR",
|
||||||
Version: "SOMEVER",
|
Version: "SOMEVER",
|
||||||
Other: []string{"SOMESTRING", "OTHERSTRING"},
|
Other: []string{"SOMESTRING", "OTHERSTRING"},
|
||||||
},
|
},
|
||||||
PubKey: pubKey,
|
SyncInfo: ctypes.SyncInfo{
|
||||||
LatestBlockHash: []byte("SOMEBYTES"),
|
LatestBlockHash: []byte("SOMEBYTES"),
|
||||||
LatestBlockHeight: 123,
|
LatestBlockHeight: 123,
|
||||||
LatestBlockTime: time.Unix(0, 1234),
|
LatestBlockTime: time.Unix(0, 1234),
|
||||||
|
},
|
||||||
|
ValidatorInfo: ctypes.ValidatorInfo{
|
||||||
|
PubKey: nodeKey.PubKey(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
b.StartTimer()
|
b.StartTimer()
|
||||||
|
|
||||||
counter := 0
|
counter := 0
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
jsonBytes := wire.JSONBytes(status)
|
jsonBytes, err := cdc.MarshalJSON(status)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
counter += len(jsonBytes)
|
counter += len(jsonBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,9 +50,11 @@ func BenchmarkEncodeStatusWire(b *testing.B) {
|
|||||||
|
|
||||||
func BenchmarkEncodeNodeInfoWire(b *testing.B) {
|
func BenchmarkEncodeNodeInfoWire(b *testing.B) {
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
pubKey := crypto.GenPrivKeyEd25519().PubKey()
|
cdc := amino.NewCodec()
|
||||||
|
ctypes.RegisterAmino(cdc)
|
||||||
|
nodeKey := p2p.NodeKey{PrivKey: crypto.GenPrivKeyEd25519()}
|
||||||
nodeInfo := p2p.NodeInfo{
|
nodeInfo := p2p.NodeInfo{
|
||||||
PubKey: pubKey,
|
ID: nodeKey.ID(),
|
||||||
Moniker: "SOMENAME",
|
Moniker: "SOMENAME",
|
||||||
Network: "SOMENAME",
|
Network: "SOMENAME",
|
||||||
ListenAddr: "SOMEADDR",
|
ListenAddr: "SOMEADDR",
|
||||||
@@ -54,16 +65,21 @@ func BenchmarkEncodeNodeInfoWire(b *testing.B) {
|
|||||||
|
|
||||||
counter := 0
|
counter := 0
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
jsonBytes := wire.JSONBytes(nodeInfo)
|
jsonBytes, err := cdc.MarshalJSON(nodeInfo)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
counter += len(jsonBytes)
|
counter += len(jsonBytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkEncodeNodeInfoBinary(b *testing.B) {
|
func BenchmarkEncodeNodeInfoBinary(b *testing.B) {
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
pubKey := crypto.GenPrivKeyEd25519().PubKey()
|
cdc := amino.NewCodec()
|
||||||
|
ctypes.RegisterAmino(cdc)
|
||||||
|
nodeKey := p2p.NodeKey{PrivKey: crypto.GenPrivKeyEd25519()}
|
||||||
nodeInfo := p2p.NodeInfo{
|
nodeInfo := p2p.NodeInfo{
|
||||||
PubKey: pubKey,
|
ID: nodeKey.ID(),
|
||||||
Moniker: "SOMENAME",
|
Moniker: "SOMENAME",
|
||||||
Network: "SOMENAME",
|
Network: "SOMENAME",
|
||||||
ListenAddr: "SOMEADDR",
|
ListenAddr: "SOMEADDR",
|
||||||
@@ -74,7 +90,7 @@ func BenchmarkEncodeNodeInfoBinary(b *testing.B) {
|
|||||||
|
|
||||||
counter := 0
|
counter := 0
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
jsonBytes := wire.BinaryBytes(nodeInfo)
|
jsonBytes := cdc.MustMarshalBinaryBare(nodeInfo)
|
||||||
counter += len(jsonBytes)
|
counter += len(jsonBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,15 +98,20 @@ func BenchmarkEncodeNodeInfoBinary(b *testing.B) {
|
|||||||
|
|
||||||
func BenchmarkEncodeNodeInfoProto(b *testing.B) {
|
func BenchmarkEncodeNodeInfoProto(b *testing.B) {
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
pubKey := crypto.GenPrivKeyEd25519().PubKey().Unwrap().(crypto.PubKeyEd25519)
|
nodeKey := p2p.NodeKey{PrivKey: crypto.GenPrivKeyEd25519()}
|
||||||
pubKey2 := &proto.PubKey{Ed25519: &proto.PubKeyEd25519{Bytes: pubKey[:]}}
|
nodeID := string(nodeKey.ID())
|
||||||
|
someName := "SOMENAME"
|
||||||
|
someAddr := "SOMEADDR"
|
||||||
|
someVer := "SOMEVER"
|
||||||
|
someString := "SOMESTRING"
|
||||||
|
otherString := "OTHERSTRING"
|
||||||
nodeInfo := proto.NodeInfo{
|
nodeInfo := proto.NodeInfo{
|
||||||
PubKey: pubKey2,
|
Id: &proto.ID{Id: &nodeID},
|
||||||
Moniker: "SOMENAME",
|
Moniker: &someName,
|
||||||
Network: "SOMENAME",
|
Network: &someName,
|
||||||
ListenAddr: "SOMEADDR",
|
ListenAddr: &someAddr,
|
||||||
Version: "SOMEVER",
|
Version: &someVer,
|
||||||
Other: []string{"SOMESTRING", "OTHERSTRING"},
|
Other: []string{someString, otherString},
|
||||||
}
|
}
|
||||||
b.StartTimer()
|
b.StartTimer()
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ message ResultStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message NodeInfo {
|
message NodeInfo {
|
||||||
required PubKey pubKey = 1;
|
required ID id = 1;
|
||||||
required string moniker = 2;
|
required string moniker = 2;
|
||||||
required string network = 3;
|
required string network = 3;
|
||||||
required string remoteAddr = 4;
|
required string remoteAddr = 4;
|
||||||
@@ -16,6 +16,10 @@ message NodeInfo {
|
|||||||
repeated string other = 7;
|
repeated string other = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ID {
|
||||||
|
required string id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message PubKey {
|
message PubKey {
|
||||||
optional PubKeyEd25519 ed25519 = 1;
|
optional PubKeyEd25519 ed25519 = 1;
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
@@ -66,11 +67,13 @@ type BlockPool struct {
|
|||||||
// block requests
|
// block requests
|
||||||
requesters map[int64]*bpRequester
|
requesters map[int64]*bpRequester
|
||||||
height int64 // the lowest key in requesters.
|
height int64 // the lowest key in requesters.
|
||||||
numPending int32 // number of requests pending assignment or block response
|
|
||||||
// peers
|
// peers
|
||||||
peers map[p2p.ID]*bpPeer
|
peers map[p2p.ID]*bpPeer
|
||||||
maxPeerHeight int64
|
maxPeerHeight int64
|
||||||
|
|
||||||
|
// atomic
|
||||||
|
numPending int32 // number of requests pending assignment or block response
|
||||||
|
|
||||||
requestsCh chan<- BlockRequest
|
requestsCh chan<- BlockRequest
|
||||||
errorsCh chan<- peerError
|
errorsCh chan<- peerError
|
||||||
}
|
}
|
||||||
@@ -151,7 +154,7 @@ func (pool *BlockPool) GetStatus() (height int64, numPending int32, lenRequester
|
|||||||
pool.mtx.Lock()
|
pool.mtx.Lock()
|
||||||
defer pool.mtx.Unlock()
|
defer pool.mtx.Unlock()
|
||||||
|
|
||||||
return pool.height, pool.numPending, len(pool.requesters)
|
return pool.height, atomic.LoadInt32(&pool.numPending), len(pool.requesters)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: relax conditions, prevent abuse.
|
// TODO: relax conditions, prevent abuse.
|
||||||
@@ -245,7 +248,7 @@ func (pool *BlockPool) AddBlock(peerID p2p.ID, block *types.Block, blockSize int
|
|||||||
}
|
}
|
||||||
|
|
||||||
if requester.setBlock(block, peerID) {
|
if requester.setBlock(block, peerID) {
|
||||||
pool.numPending--
|
atomic.AddInt32(&pool.numPending, -1)
|
||||||
peer := pool.peers[peerID]
|
peer := pool.peers[peerID]
|
||||||
if peer != nil {
|
if peer != nil {
|
||||||
peer.decrPending(blockSize)
|
peer.decrPending(blockSize)
|
||||||
@@ -291,10 +294,7 @@ func (pool *BlockPool) RemovePeer(peerID p2p.ID) {
|
|||||||
func (pool *BlockPool) removePeer(peerID p2p.ID) {
|
func (pool *BlockPool) removePeer(peerID p2p.ID) {
|
||||||
for _, requester := range pool.requesters {
|
for _, requester := range pool.requesters {
|
||||||
if requester.getPeerID() == peerID {
|
if requester.getPeerID() == peerID {
|
||||||
if requester.getBlock() != nil {
|
requester.redo()
|
||||||
pool.numPending++
|
|
||||||
}
|
|
||||||
go requester.redo() // pick another peer and ...
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete(pool.peers, peerID)
|
delete(pool.peers, peerID)
|
||||||
@@ -332,7 +332,7 @@ func (pool *BlockPool) makeNextRequester() {
|
|||||||
// request.SetLogger(pool.Logger.With("height", nextHeight))
|
// request.SetLogger(pool.Logger.With("height", nextHeight))
|
||||||
|
|
||||||
pool.requesters[nextHeight] = request
|
pool.requesters[nextHeight] = request
|
||||||
pool.numPending++
|
atomic.AddInt32(&pool.numPending, 1)
|
||||||
|
|
||||||
err := request.Start()
|
err := request.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -360,7 +360,7 @@ func (pool *BlockPool) sendError(err error, peerID p2p.ID) {
|
|||||||
|
|
||||||
// unused by tendermint; left for debugging purposes
|
// unused by tendermint; left for debugging purposes
|
||||||
func (pool *BlockPool) debug() string {
|
func (pool *BlockPool) debug() string {
|
||||||
pool.mtx.Lock() // Lock
|
pool.mtx.Lock()
|
||||||
defer pool.mtx.Unlock()
|
defer pool.mtx.Unlock()
|
||||||
|
|
||||||
str := ""
|
str := ""
|
||||||
@@ -466,8 +466,8 @@ func newBPRequester(pool *BlockPool, height int64) *bpRequester {
|
|||||||
bpr := &bpRequester{
|
bpr := &bpRequester{
|
||||||
pool: pool,
|
pool: pool,
|
||||||
height: height,
|
height: height,
|
||||||
gotBlockCh: make(chan struct{}),
|
gotBlockCh: make(chan struct{}, 1),
|
||||||
redoCh: make(chan struct{}),
|
redoCh: make(chan struct{}, 1),
|
||||||
|
|
||||||
peerID: "",
|
peerID: "",
|
||||||
block: nil,
|
block: nil,
|
||||||
@@ -481,7 +481,7 @@ func (bpr *bpRequester) OnStart() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the peer matches
|
// Returns true if the peer matches and block doesn't already exist.
|
||||||
func (bpr *bpRequester) setBlock(block *types.Block, peerID p2p.ID) bool {
|
func (bpr *bpRequester) setBlock(block *types.Block, peerID p2p.ID) bool {
|
||||||
bpr.mtx.Lock()
|
bpr.mtx.Lock()
|
||||||
if bpr.block != nil || bpr.peerID != peerID {
|
if bpr.block != nil || bpr.peerID != peerID {
|
||||||
@@ -491,7 +491,10 @@ func (bpr *bpRequester) setBlock(block *types.Block, peerID p2p.ID) bool {
|
|||||||
bpr.block = block
|
bpr.block = block
|
||||||
bpr.mtx.Unlock()
|
bpr.mtx.Unlock()
|
||||||
|
|
||||||
bpr.gotBlockCh <- struct{}{}
|
select {
|
||||||
|
case bpr.gotBlockCh <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -507,17 +510,27 @@ func (bpr *bpRequester) getPeerID() p2p.ID {
|
|||||||
return bpr.peerID
|
return bpr.peerID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is called from the requestRoutine, upon redo().
|
||||||
func (bpr *bpRequester) reset() {
|
func (bpr *bpRequester) reset() {
|
||||||
bpr.mtx.Lock()
|
bpr.mtx.Lock()
|
||||||
|
defer bpr.mtx.Unlock()
|
||||||
|
|
||||||
|
if bpr.block != nil {
|
||||||
|
atomic.AddInt32(&bpr.pool.numPending, 1)
|
||||||
|
}
|
||||||
|
|
||||||
bpr.peerID = ""
|
bpr.peerID = ""
|
||||||
bpr.block = nil
|
bpr.block = nil
|
||||||
bpr.mtx.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tells bpRequester to pick another peer and try again.
|
// Tells bpRequester to pick another peer and try again.
|
||||||
// NOTE: blocking
|
// NOTE: Nonblocking, and does nothing if another redo
|
||||||
|
// was already requested.
|
||||||
func (bpr *bpRequester) redo() {
|
func (bpr *bpRequester) redo() {
|
||||||
bpr.redoCh <- struct{}{}
|
select {
|
||||||
|
case bpr.redoCh <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Responsible for making more requests as necessary
|
// Responsible for making more requests as necessary
|
||||||
@@ -546,17 +559,8 @@ OUTER_LOOP:
|
|||||||
|
|
||||||
// Send request and wait.
|
// Send request and wait.
|
||||||
bpr.pool.sendRequest(bpr.height, peer.id)
|
bpr.pool.sendRequest(bpr.height, peer.id)
|
||||||
select {
|
WAIT_LOOP:
|
||||||
case <-bpr.pool.Quit():
|
for {
|
||||||
bpr.Stop()
|
|
||||||
return
|
|
||||||
case <-bpr.Quit():
|
|
||||||
return
|
|
||||||
case <-bpr.redoCh:
|
|
||||||
bpr.reset()
|
|
||||||
continue OUTER_LOOP // When peer is removed
|
|
||||||
case <-bpr.gotBlockCh:
|
|
||||||
// We got the block, now see if it's good.
|
|
||||||
select {
|
select {
|
||||||
case <-bpr.pool.Quit():
|
case <-bpr.pool.Quit():
|
||||||
bpr.Stop()
|
bpr.Stop()
|
||||||
@@ -566,6 +570,10 @@ OUTER_LOOP:
|
|||||||
case <-bpr.redoCh:
|
case <-bpr.redoCh:
|
||||||
bpr.reset()
|
bpr.reset()
|
||||||
continue OUTER_LOOP
|
continue OUTER_LOOP
|
||||||
|
case <-bpr.gotBlockCh:
|
||||||
|
// We got a block!
|
||||||
|
// Continue the for-loop and wait til Quit.
|
||||||
|
continue WAIT_LOOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,21 +1,16 @@
|
|||||||
package blockchain
|
package blockchain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
wire "github.com/tendermint/go-wire"
|
"github.com/tendermint/go-amino"
|
||||||
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
|
||||||
"github.com/tendermint/tmlibs/log"
|
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/p2p"
|
"github.com/tendermint/tendermint/p2p"
|
||||||
sm "github.com/tendermint/tendermint/state"
|
sm "github.com/tendermint/tendermint/state"
|
||||||
"github.com/tendermint/tendermint/types"
|
"github.com/tendermint/tendermint/types"
|
||||||
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
|
"github.com/tendermint/tmlibs/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -31,6 +26,13 @@ const (
|
|||||||
statusUpdateIntervalSeconds = 10
|
statusUpdateIntervalSeconds = 10
|
||||||
// check if we should switch to consensus reactor
|
// check if we should switch to consensus reactor
|
||||||
switchToConsensusIntervalSeconds = 1
|
switchToConsensusIntervalSeconds = 1
|
||||||
|
|
||||||
|
// NOTE: keep up to date with bcBlockResponseMessage
|
||||||
|
bcBlockResponseMessagePrefixSize = 4
|
||||||
|
bcBlockResponseMessageFieldKeySize = 1
|
||||||
|
maxMsgSize = types.MaxBlockSizeBytes +
|
||||||
|
bcBlockResponseMessagePrefixSize +
|
||||||
|
bcBlockResponseMessageFieldKeySize
|
||||||
)
|
)
|
||||||
|
|
||||||
type consensusReactor interface {
|
type consensusReactor interface {
|
||||||
@@ -52,9 +54,6 @@ func (e peerError) Error() string {
|
|||||||
type BlockchainReactor struct {
|
type BlockchainReactor struct {
|
||||||
p2p.BaseReactor
|
p2p.BaseReactor
|
||||||
|
|
||||||
mtx sync.Mutex
|
|
||||||
params types.ConsensusParams
|
|
||||||
|
|
||||||
// immutable
|
// immutable
|
||||||
initialState sm.State
|
initialState sm.State
|
||||||
|
|
||||||
@@ -87,7 +86,6 @@ func NewBlockchainReactor(state sm.State, blockExec *sm.BlockExecutor, store *Bl
|
|||||||
)
|
)
|
||||||
|
|
||||||
bcR := &BlockchainReactor{
|
bcR := &BlockchainReactor{
|
||||||
params: state.ConsensusParams,
|
|
||||||
initialState: state,
|
initialState: state,
|
||||||
blockExec: blockExec,
|
blockExec: blockExec,
|
||||||
store: store,
|
store: store,
|
||||||
@@ -131,17 +129,19 @@ func (bcR *BlockchainReactor) OnStop() {
|
|||||||
func (bcR *BlockchainReactor) GetChannels() []*p2p.ChannelDescriptor {
|
func (bcR *BlockchainReactor) GetChannels() []*p2p.ChannelDescriptor {
|
||||||
return []*p2p.ChannelDescriptor{
|
return []*p2p.ChannelDescriptor{
|
||||||
{
|
{
|
||||||
ID: BlockchainChannel,
|
ID: BlockchainChannel,
|
||||||
Priority: 10,
|
Priority: 10,
|
||||||
SendQueueCapacity: 1000,
|
SendQueueCapacity: 1000,
|
||||||
|
RecvBufferCapacity: 50 * 4096,
|
||||||
|
RecvMessageCapacity: maxMsgSize,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddPeer implements Reactor by sending our state to peer.
|
// AddPeer implements Reactor by sending our state to peer.
|
||||||
func (bcR *BlockchainReactor) AddPeer(peer p2p.Peer) {
|
func (bcR *BlockchainReactor) AddPeer(peer p2p.Peer) {
|
||||||
if !peer.Send(BlockchainChannel,
|
msgBytes := cdc.MustMarshalBinaryBare(&bcStatusResponseMessage{bcR.store.Height()})
|
||||||
struct{ BlockchainMessage }{&bcStatusResponseMessage{bcR.store.Height()}}) {
|
if !peer.Send(BlockchainChannel, msgBytes) {
|
||||||
// doing nothing, will try later in `poolRoutine`
|
// doing nothing, will try later in `poolRoutine`
|
||||||
}
|
}
|
||||||
// peer is added to the pool once we receive the first
|
// peer is added to the pool once we receive the first
|
||||||
@@ -162,20 +162,19 @@ func (bcR *BlockchainReactor) respondToPeer(msg *bcBlockRequestMessage,
|
|||||||
|
|
||||||
block := bcR.store.LoadBlock(msg.Height)
|
block := bcR.store.LoadBlock(msg.Height)
|
||||||
if block != nil {
|
if block != nil {
|
||||||
msg := &bcBlockResponseMessage{Block: block}
|
msgBytes := cdc.MustMarshalBinaryBare(&bcBlockResponseMessage{Block: block})
|
||||||
return src.TrySend(BlockchainChannel, struct{ BlockchainMessage }{msg})
|
return src.TrySend(BlockchainChannel, msgBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
bcR.Logger.Info("Peer asking for a block we don't have", "src", src, "height", msg.Height)
|
bcR.Logger.Info("Peer asking for a block we don't have", "src", src, "height", msg.Height)
|
||||||
|
|
||||||
return src.TrySend(BlockchainChannel, struct{ BlockchainMessage }{
|
msgBytes := cdc.MustMarshalBinaryBare(&bcNoBlockResponseMessage{Height: msg.Height})
|
||||||
&bcNoBlockResponseMessage{Height: msg.Height},
|
return src.TrySend(BlockchainChannel, msgBytes)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receive implements Reactor by handling 4 types of messages (look below).
|
// Receive implements Reactor by handling 4 types of messages (look below).
|
||||||
func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) {
|
func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) {
|
||||||
_, msg, err := DecodeMessage(msgBytes, bcR.maxMsgSize())
|
msg, err := DecodeMessage(msgBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
bcR.Logger.Error("Error decoding message", "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes)
|
bcR.Logger.Error("Error decoding message", "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes)
|
||||||
bcR.Switch.StopPeerForError(src, err)
|
bcR.Switch.StopPeerForError(src, err)
|
||||||
@@ -194,8 +193,8 @@ func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte)
|
|||||||
bcR.pool.AddBlock(src.ID(), msg.Block, len(msgBytes))
|
bcR.pool.AddBlock(src.ID(), msg.Block, len(msgBytes))
|
||||||
case *bcStatusRequestMessage:
|
case *bcStatusRequestMessage:
|
||||||
// Send peer our state.
|
// Send peer our state.
|
||||||
queued := src.TrySend(BlockchainChannel,
|
msgBytes := cdc.MustMarshalBinaryBare(&bcStatusResponseMessage{bcR.store.Height()})
|
||||||
struct{ BlockchainMessage }{&bcStatusResponseMessage{bcR.store.Height()}})
|
queued := src.TrySend(BlockchainChannel, msgBytes)
|
||||||
if !queued {
|
if !queued {
|
||||||
// sorry
|
// sorry
|
||||||
}
|
}
|
||||||
@@ -207,21 +206,6 @@ func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// maxMsgSize returns the maximum allowable size of a
|
|
||||||
// message on the blockchain reactor.
|
|
||||||
func (bcR *BlockchainReactor) maxMsgSize() int {
|
|
||||||
bcR.mtx.Lock()
|
|
||||||
defer bcR.mtx.Unlock()
|
|
||||||
return bcR.params.BlockSize.MaxBytes + 2
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateConsensusParams updates the internal consensus params
|
|
||||||
func (bcR *BlockchainReactor) updateConsensusParams(params types.ConsensusParams) {
|
|
||||||
bcR.mtx.Lock()
|
|
||||||
defer bcR.mtx.Unlock()
|
|
||||||
bcR.params = params
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle messages from the poolReactor telling the reactor what to do.
|
// Handle messages from the poolReactor telling the reactor what to do.
|
||||||
// NOTE: Don't sleep in the FOR_LOOP or otherwise slow it down!
|
// NOTE: Don't sleep in the FOR_LOOP or otherwise slow it down!
|
||||||
// (Except for the SYNC_LOOP, which is the primary purpose and must be synchronous.)
|
// (Except for the SYNC_LOOP, which is the primary purpose and must be synchronous.)
|
||||||
@@ -247,8 +231,8 @@ FOR_LOOP:
|
|||||||
if peer == nil {
|
if peer == nil {
|
||||||
continue FOR_LOOP // Peer has since been disconnected.
|
continue FOR_LOOP // Peer has since been disconnected.
|
||||||
}
|
}
|
||||||
msg := &bcBlockRequestMessage{request.Height}
|
msgBytes := cdc.MustMarshalBinaryBare(&bcBlockRequestMessage{request.Height})
|
||||||
queued := peer.TrySend(BlockchainChannel, struct{ BlockchainMessage }{msg})
|
queued := peer.TrySend(BlockchainChannel, msgBytes)
|
||||||
if !queued {
|
if !queued {
|
||||||
// We couldn't make the request, send-queue full.
|
// We couldn't make the request, send-queue full.
|
||||||
// The pool handles timeouts, just let it go.
|
// The pool handles timeouts, just let it go.
|
||||||
@@ -321,9 +305,6 @@ FOR_LOOP:
|
|||||||
}
|
}
|
||||||
blocksSynced++
|
blocksSynced++
|
||||||
|
|
||||||
// update the consensus params
|
|
||||||
bcR.updateConsensusParams(state.ConsensusParams)
|
|
||||||
|
|
||||||
if blocksSynced%100 == 0 {
|
if blocksSynced%100 == 0 {
|
||||||
lastRate = 0.9*lastRate + 0.1*(100/time.Since(lastHundred).Seconds())
|
lastRate = 0.9*lastRate + 0.1*(100/time.Since(lastHundred).Seconds())
|
||||||
bcR.Logger.Info("Fast Sync Rate", "height", bcR.pool.height,
|
bcR.Logger.Info("Fast Sync Rate", "height", bcR.pool.height,
|
||||||
@@ -341,43 +322,36 @@ FOR_LOOP:
|
|||||||
|
|
||||||
// BroadcastStatusRequest broadcasts `BlockStore` height.
|
// BroadcastStatusRequest broadcasts `BlockStore` height.
|
||||||
func (bcR *BlockchainReactor) BroadcastStatusRequest() error {
|
func (bcR *BlockchainReactor) BroadcastStatusRequest() error {
|
||||||
bcR.Switch.Broadcast(BlockchainChannel,
|
msgBytes := cdc.MustMarshalBinaryBare(&bcStatusRequestMessage{bcR.store.Height()})
|
||||||
struct{ BlockchainMessage }{&bcStatusRequestMessage{bcR.store.Height()}})
|
bcR.Switch.Broadcast(BlockchainChannel, msgBytes)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Messages
|
// Messages
|
||||||
|
|
||||||
const (
|
|
||||||
msgTypeBlockRequest = byte(0x10)
|
|
||||||
msgTypeBlockResponse = byte(0x11)
|
|
||||||
msgTypeNoBlockResponse = byte(0x12)
|
|
||||||
msgTypeStatusResponse = byte(0x20)
|
|
||||||
msgTypeStatusRequest = byte(0x21)
|
|
||||||
)
|
|
||||||
|
|
||||||
// BlockchainMessage is a generic message for this reactor.
|
// BlockchainMessage is a generic message for this reactor.
|
||||||
type BlockchainMessage interface{}
|
type BlockchainMessage interface{}
|
||||||
|
|
||||||
var _ = wire.RegisterInterface(
|
func RegisterBlockchainMessages(cdc *amino.Codec) {
|
||||||
struct{ BlockchainMessage }{},
|
cdc.RegisterInterface((*BlockchainMessage)(nil), nil)
|
||||||
wire.ConcreteType{&bcBlockRequestMessage{}, msgTypeBlockRequest},
|
cdc.RegisterConcrete(&bcBlockRequestMessage{}, "tendermint/mempool/BlockRequest", nil)
|
||||||
wire.ConcreteType{&bcBlockResponseMessage{}, msgTypeBlockResponse},
|
cdc.RegisterConcrete(&bcBlockResponseMessage{}, "tendermint/mempool/BlockResponse", nil)
|
||||||
wire.ConcreteType{&bcNoBlockResponseMessage{}, msgTypeNoBlockResponse},
|
cdc.RegisterConcrete(&bcNoBlockResponseMessage{}, "tendermint/mempool/NoBlockResponse", nil)
|
||||||
wire.ConcreteType{&bcStatusResponseMessage{}, msgTypeStatusResponse},
|
cdc.RegisterConcrete(&bcStatusResponseMessage{}, "tendermint/mempool/StatusResponse", nil)
|
||||||
wire.ConcreteType{&bcStatusRequestMessage{}, msgTypeStatusRequest},
|
cdc.RegisterConcrete(&bcStatusRequestMessage{}, "tendermint/mempool/StatusRequest", nil)
|
||||||
)
|
}
|
||||||
|
|
||||||
// DecodeMessage decodes BlockchainMessage.
|
// DecodeMessage decodes BlockchainMessage.
|
||||||
// TODO: ensure that bz is completely read.
|
// TODO: ensure that bz is completely read.
|
||||||
func DecodeMessage(bz []byte, maxSize int) (msgType byte, msg BlockchainMessage, err error) {
|
func DecodeMessage(bz []byte) (msg BlockchainMessage, err error) {
|
||||||
msgType = bz[0]
|
if len(bz) > maxMsgSize {
|
||||||
n := int(0)
|
return msg, fmt.Errorf("Msg exceeds max size (%d > %d)",
|
||||||
r := bytes.NewReader(bz)
|
len(bz), maxMsgSize)
|
||||||
msg = wire.ReadBinary(struct{ BlockchainMessage }{}, r, maxSize, &n, &err).(struct{ BlockchainMessage }).BlockchainMessage
|
}
|
||||||
if err != nil && n != len(bz) {
|
err = cdc.UnmarshalBinaryBare(bz, &msg)
|
||||||
err = errors.New("DecodeMessage() had bytes left over")
|
if err != nil {
|
||||||
|
err = cmn.ErrorWrap(err, "DecodeMessage() had bytes left over")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -402,7 +376,6 @@ func (brm *bcNoBlockResponseMessage) String() string {
|
|||||||
|
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
|
|
||||||
// NOTE: keep up-to-date with maxBlockchainResponseSize
|
|
||||||
type bcBlockResponseMessage struct {
|
type bcBlockResponseMessage struct {
|
||||||
Block *types.Block
|
Block *types.Block
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,9 @@
|
|||||||
package blockchain
|
package blockchain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
wire "github.com/tendermint/go-wire"
|
|
||||||
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
dbm "github.com/tendermint/tmlibs/db"
|
dbm "github.com/tendermint/tmlibs/db"
|
||||||
"github.com/tendermint/tmlibs/log"
|
"github.com/tendermint/tmlibs/log"
|
||||||
@@ -18,8 +17,15 @@ import (
|
|||||||
|
|
||||||
func makeStateAndBlockStore(logger log.Logger) (sm.State, *BlockStore) {
|
func makeStateAndBlockStore(logger log.Logger) (sm.State, *BlockStore) {
|
||||||
config := cfg.ResetTestRoot("blockchain_reactor_test")
|
config := cfg.ResetTestRoot("blockchain_reactor_test")
|
||||||
blockStore := NewBlockStore(dbm.NewMemDB())
|
// blockDB := dbm.NewDebugDB("blockDB", dbm.NewMemDB())
|
||||||
state, _ := sm.LoadStateFromDBOrGenesisFile(dbm.NewMemDB(), config.GenesisFile())
|
// stateDB := dbm.NewDebugDB("stateDB", dbm.NewMemDB())
|
||||||
|
blockDB := dbm.NewMemDB()
|
||||||
|
stateDB := dbm.NewMemDB()
|
||||||
|
blockStore := NewBlockStore(blockDB)
|
||||||
|
state, err := sm.LoadStateFromDBOrGenesisFile(stateDB, config.GenesisFile())
|
||||||
|
if err != nil {
|
||||||
|
panic(cmn.ErrorWrap(err, "error constructing state from genesis file"))
|
||||||
|
}
|
||||||
return state, blockStore
|
return state, blockStore
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +36,7 @@ func newBlockchainReactor(logger log.Logger, maxBlockHeight int64) *BlockchainRe
|
|||||||
fastSync := true
|
fastSync := true
|
||||||
var nilApp proxy.AppConnConsensus
|
var nilApp proxy.AppConnConsensus
|
||||||
blockExec := sm.NewBlockExecutor(dbm.NewMemDB(), log.TestingLogger(), nilApp,
|
blockExec := sm.NewBlockExecutor(dbm.NewMemDB(), log.TestingLogger(), nilApp,
|
||||||
types.MockMempool{}, types.MockEvidencePool{})
|
sm.MockMempool{}, sm.MockEvidencePool{})
|
||||||
|
|
||||||
bcReactor := NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync)
|
bcReactor := NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync)
|
||||||
bcReactor.SetLogger(logger.With("module", "blockchain"))
|
bcReactor.SetLogger(logger.With("module", "blockchain"))
|
||||||
@@ -76,10 +82,9 @@ func TestNoBlockResponse(t *testing.T) {
|
|||||||
// wait for our response to be received on the peer
|
// wait for our response to be received on the peer
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
reqBlockMsg := &bcBlockRequestMessage{tt.height}
|
reqBlockMsg := &bcBlockRequestMessage{tt.height}
|
||||||
reqBlockBytes := wire.BinaryBytes(struct{ BlockchainMessage }{reqBlockMsg})
|
reqBlockBytes := cdc.MustMarshalBinaryBare(reqBlockMsg)
|
||||||
bcr.Receive(chID, peer, reqBlockBytes)
|
bcr.Receive(chID, peer, reqBlockBytes)
|
||||||
value := peer.lastValue()
|
msg := peer.lastBlockchainMessage()
|
||||||
msg := value.(struct{ BlockchainMessage }).BlockchainMessage
|
|
||||||
|
|
||||||
if tt.existent {
|
if tt.existent {
|
||||||
if blockMsg, ok := msg.(*bcBlockResponseMessage); !ok {
|
if blockMsg, ok := msg.(*bcBlockResponseMessage); !ok {
|
||||||
@@ -173,26 +178,31 @@ func newbcrTestPeer(id p2p.ID) *bcrTestPeer {
|
|||||||
return bcr
|
return bcr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tp *bcrTestPeer) lastValue() interface{} { return <-tp.ch }
|
func (tp *bcrTestPeer) lastBlockchainMessage() interface{} { return <-tp.ch }
|
||||||
|
|
||||||
func (tp *bcrTestPeer) TrySend(chID byte, value interface{}) bool {
|
func (tp *bcrTestPeer) TrySend(chID byte, msgBytes []byte) bool {
|
||||||
if _, ok := value.(struct{ BlockchainMessage }).
|
var msg BlockchainMessage
|
||||||
BlockchainMessage.(*bcStatusResponseMessage); ok {
|
err := cdc.UnmarshalBinaryBare(msgBytes, &msg)
|
||||||
|
if err != nil {
|
||||||
|
panic(cmn.ErrorWrap(err, "Error while trying to parse a BlockchainMessage"))
|
||||||
|
}
|
||||||
|
if _, ok := msg.(*bcStatusResponseMessage); ok {
|
||||||
// Discard status response messages since they skew our results
|
// Discard status response messages since they skew our results
|
||||||
// We only want to deal with:
|
// We only want to deal with:
|
||||||
// + bcBlockResponseMessage
|
// + bcBlockResponseMessage
|
||||||
// + bcNoBlockResponseMessage
|
// + bcNoBlockResponseMessage
|
||||||
} else {
|
} else {
|
||||||
tp.ch <- value
|
tp.ch <- msg
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tp *bcrTestPeer) Send(chID byte, data interface{}) bool { return tp.TrySend(chID, data) }
|
func (tp *bcrTestPeer) Send(chID byte, msgBytes []byte) bool { return tp.TrySend(chID, msgBytes) }
|
||||||
func (tp *bcrTestPeer) NodeInfo() p2p.NodeInfo { return p2p.NodeInfo{} }
|
func (tp *bcrTestPeer) NodeInfo() p2p.NodeInfo { return p2p.NodeInfo{} }
|
||||||
func (tp *bcrTestPeer) Status() p2p.ConnectionStatus { return p2p.ConnectionStatus{} }
|
func (tp *bcrTestPeer) Status() p2p.ConnectionStatus { return p2p.ConnectionStatus{} }
|
||||||
func (tp *bcrTestPeer) ID() p2p.ID { return tp.id }
|
func (tp *bcrTestPeer) ID() p2p.ID { return tp.id }
|
||||||
func (tp *bcrTestPeer) IsOutbound() bool { return false }
|
func (tp *bcrTestPeer) IsOutbound() bool { return false }
|
||||||
func (tp *bcrTestPeer) IsPersistent() bool { return true }
|
func (tp *bcrTestPeer) IsPersistent() bool { return true }
|
||||||
func (tp *bcrTestPeer) Get(s string) interface{} { return s }
|
func (tp *bcrTestPeer) Get(s string) interface{} { return s }
|
||||||
func (tp *bcrTestPeer) Set(string, interface{}) {}
|
func (tp *bcrTestPeer) Set(string, interface{}) {}
|
||||||
|
func (tp *bcrTestPeer) RemoteIP() net.IP { return []byte{127, 0, 0, 1} }
|
||||||
|
@@ -1,14 +1,9 @@
|
|||||||
package blockchain
|
package blockchain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
wire "github.com/tendermint/go-wire"
|
|
||||||
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
dbm "github.com/tendermint/tmlibs/db"
|
dbm "github.com/tendermint/tmlibs/db"
|
||||||
|
|
||||||
@@ -54,38 +49,25 @@ func (bs *BlockStore) Height() int64 {
|
|||||||
return bs.height
|
return bs.height
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetReader returns the value associated with the given key wrapped in an io.Reader.
|
|
||||||
// If no value is found, it returns nil.
|
|
||||||
// It's mainly for use with wire.ReadBinary.
|
|
||||||
func (bs *BlockStore) GetReader(key []byte) io.Reader {
|
|
||||||
bytez := bs.db.Get(key)
|
|
||||||
if bytez == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return bytes.NewReader(bytez)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadBlock returns the block with the given height.
|
// LoadBlock returns the block with the given height.
|
||||||
// If no block is found for that height, it returns nil.
|
// If no block is found for that height, it returns nil.
|
||||||
func (bs *BlockStore) LoadBlock(height int64) *types.Block {
|
func (bs *BlockStore) LoadBlock(height int64) *types.Block {
|
||||||
var n int
|
var blockMeta = bs.LoadBlockMeta(height)
|
||||||
var err error
|
if blockMeta == nil {
|
||||||
r := bs.GetReader(calcBlockMetaKey(height))
|
|
||||||
if r == nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
blockMeta := wire.ReadBinary(&types.BlockMeta{}, r, 0, &n, &err).(*types.BlockMeta)
|
|
||||||
if err != nil {
|
var block = new(types.Block)
|
||||||
panic(fmt.Sprintf("Error reading block meta: %v", err))
|
buf := []byte{}
|
||||||
}
|
|
||||||
bytez := []byte{}
|
|
||||||
for i := 0; i < blockMeta.BlockID.PartsHeader.Total; i++ {
|
for i := 0; i < blockMeta.BlockID.PartsHeader.Total; i++ {
|
||||||
part := bs.LoadBlockPart(height, i)
|
part := bs.LoadBlockPart(height, i)
|
||||||
bytez = append(bytez, part.Bytes...)
|
buf = append(buf, part.Bytes...)
|
||||||
}
|
}
|
||||||
block := wire.ReadBinary(&types.Block{}, bytes.NewReader(bytez), 0, &n, &err).(*types.Block)
|
err := cdc.UnmarshalBinary(buf, block)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("Error reading block: %v", err))
|
// NOTE: The existence of meta should imply the existence of the
|
||||||
|
// block. So, make sure meta is only saved after blocks are saved.
|
||||||
|
panic(cmn.ErrorWrap(err, "Error reading block"))
|
||||||
}
|
}
|
||||||
return block
|
return block
|
||||||
}
|
}
|
||||||
@@ -94,15 +76,14 @@ func (bs *BlockStore) LoadBlock(height int64) *types.Block {
|
|||||||
// from the block at the given height.
|
// from the block at the given height.
|
||||||
// If no part is found for the given height and index, it returns nil.
|
// If no part is found for the given height and index, it returns nil.
|
||||||
func (bs *BlockStore) LoadBlockPart(height int64, index int) *types.Part {
|
func (bs *BlockStore) LoadBlockPart(height int64, index int) *types.Part {
|
||||||
var n int
|
var part = new(types.Part)
|
||||||
var err error
|
bz := bs.db.Get(calcBlockPartKey(height, index))
|
||||||
r := bs.GetReader(calcBlockPartKey(height, index))
|
if len(bz) == 0 {
|
||||||
if r == nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
part := wire.ReadBinary(&types.Part{}, r, 0, &n, &err).(*types.Part)
|
err := cdc.UnmarshalBinaryBare(bz, part)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("Error reading block part: %v", err))
|
panic(cmn.ErrorWrap(err, "Error reading block part"))
|
||||||
}
|
}
|
||||||
return part
|
return part
|
||||||
}
|
}
|
||||||
@@ -110,15 +91,14 @@ func (bs *BlockStore) LoadBlockPart(height int64, index int) *types.Part {
|
|||||||
// LoadBlockMeta returns the BlockMeta for the given height.
|
// LoadBlockMeta returns the BlockMeta for the given height.
|
||||||
// If no block is found for the given height, it returns nil.
|
// If no block is found for the given height, it returns nil.
|
||||||
func (bs *BlockStore) LoadBlockMeta(height int64) *types.BlockMeta {
|
func (bs *BlockStore) LoadBlockMeta(height int64) *types.BlockMeta {
|
||||||
var n int
|
var blockMeta = new(types.BlockMeta)
|
||||||
var err error
|
bz := bs.db.Get(calcBlockMetaKey(height))
|
||||||
r := bs.GetReader(calcBlockMetaKey(height))
|
if len(bz) == 0 {
|
||||||
if r == nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
blockMeta := wire.ReadBinary(&types.BlockMeta{}, r, 0, &n, &err).(*types.BlockMeta)
|
err := cdc.UnmarshalBinaryBare(bz, blockMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("Error reading block meta: %v", err))
|
panic(cmn.ErrorWrap(err, "Error reading block meta"))
|
||||||
}
|
}
|
||||||
return blockMeta
|
return blockMeta
|
||||||
}
|
}
|
||||||
@@ -128,15 +108,14 @@ func (bs *BlockStore) LoadBlockMeta(height int64) *types.BlockMeta {
|
|||||||
// and it comes from the block.LastCommit for `height+1`.
|
// and it comes from the block.LastCommit for `height+1`.
|
||||||
// If no commit is found for the given height, it returns nil.
|
// If no commit is found for the given height, it returns nil.
|
||||||
func (bs *BlockStore) LoadBlockCommit(height int64) *types.Commit {
|
func (bs *BlockStore) LoadBlockCommit(height int64) *types.Commit {
|
||||||
var n int
|
var commit = new(types.Commit)
|
||||||
var err error
|
bz := bs.db.Get(calcBlockCommitKey(height))
|
||||||
r := bs.GetReader(calcBlockCommitKey(height))
|
if len(bz) == 0 {
|
||||||
if r == nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
commit := wire.ReadBinary(&types.Commit{}, r, 0, &n, &err).(*types.Commit)
|
err := cdc.UnmarshalBinaryBare(bz, commit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("Error reading commit: %v", err))
|
panic(cmn.ErrorWrap(err, "Error reading block commit"))
|
||||||
}
|
}
|
||||||
return commit
|
return commit
|
||||||
}
|
}
|
||||||
@@ -145,15 +124,14 @@ func (bs *BlockStore) LoadBlockCommit(height int64) *types.Commit {
|
|||||||
// This is useful when we've seen a commit, but there has not yet been
|
// This is useful when we've seen a commit, but there has not yet been
|
||||||
// a new block at `height + 1` that includes this commit in its block.LastCommit.
|
// a new block at `height + 1` that includes this commit in its block.LastCommit.
|
||||||
func (bs *BlockStore) LoadSeenCommit(height int64) *types.Commit {
|
func (bs *BlockStore) LoadSeenCommit(height int64) *types.Commit {
|
||||||
var n int
|
var commit = new(types.Commit)
|
||||||
var err error
|
bz := bs.db.Get(calcSeenCommitKey(height))
|
||||||
r := bs.GetReader(calcSeenCommitKey(height))
|
if len(bz) == 0 {
|
||||||
if r == nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
commit := wire.ReadBinary(&types.Commit{}, r, 0, &n, &err).(*types.Commit)
|
err := cdc.UnmarshalBinaryBare(bz, commit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("Error reading commit: %v", err))
|
panic(cmn.ErrorWrap(err, "Error reading block seen commit"))
|
||||||
}
|
}
|
||||||
return commit
|
return commit
|
||||||
}
|
}
|
||||||
@@ -178,21 +156,22 @@ func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, s
|
|||||||
|
|
||||||
// Save block meta
|
// Save block meta
|
||||||
blockMeta := types.NewBlockMeta(block, blockParts)
|
blockMeta := types.NewBlockMeta(block, blockParts)
|
||||||
metaBytes := wire.BinaryBytes(blockMeta)
|
metaBytes := cdc.MustMarshalBinaryBare(blockMeta)
|
||||||
bs.db.Set(calcBlockMetaKey(height), metaBytes)
|
bs.db.Set(calcBlockMetaKey(height), metaBytes)
|
||||||
|
|
||||||
// Save block parts
|
// Save block parts
|
||||||
for i := 0; i < blockParts.Total(); i++ {
|
for i := 0; i < blockParts.Total(); i++ {
|
||||||
bs.saveBlockPart(height, i, blockParts.GetPart(i))
|
part := blockParts.GetPart(i)
|
||||||
|
bs.saveBlockPart(height, i, part)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save block commit (duplicate and separate from the Block)
|
// Save block commit (duplicate and separate from the Block)
|
||||||
blockCommitBytes := wire.BinaryBytes(block.LastCommit)
|
blockCommitBytes := cdc.MustMarshalBinaryBare(block.LastCommit)
|
||||||
bs.db.Set(calcBlockCommitKey(height-1), blockCommitBytes)
|
bs.db.Set(calcBlockCommitKey(height-1), blockCommitBytes)
|
||||||
|
|
||||||
// Save seen commit (seen +2/3 precommits for block)
|
// Save seen commit (seen +2/3 precommits for block)
|
||||||
// NOTE: we can delete this at a later height
|
// NOTE: we can delete this at a later height
|
||||||
seenCommitBytes := wire.BinaryBytes(seenCommit)
|
seenCommitBytes := cdc.MustMarshalBinaryBare(seenCommit)
|
||||||
bs.db.Set(calcSeenCommitKey(height), seenCommitBytes)
|
bs.db.Set(calcSeenCommitKey(height), seenCommitBytes)
|
||||||
|
|
||||||
// Save new BlockStoreStateJSON descriptor
|
// Save new BlockStoreStateJSON descriptor
|
||||||
@@ -211,7 +190,7 @@ func (bs *BlockStore) saveBlockPart(height int64, index int, part *types.Part) {
|
|||||||
if height != bs.Height()+1 {
|
if height != bs.Height()+1 {
|
||||||
cmn.PanicSanity(cmn.Fmt("BlockStore can only save contiguous blocks. Wanted %v, got %v", bs.Height()+1, height))
|
cmn.PanicSanity(cmn.Fmt("BlockStore can only save contiguous blocks. Wanted %v, got %v", bs.Height()+1, height))
|
||||||
}
|
}
|
||||||
partBytes := wire.BinaryBytes(part)
|
partBytes := cdc.MustMarshalBinaryBare(part)
|
||||||
bs.db.Set(calcBlockPartKey(height, index), partBytes)
|
bs.db.Set(calcBlockPartKey(height, index), partBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,12 +217,12 @@ func calcSeenCommitKey(height int64) []byte {
|
|||||||
var blockStoreKey = []byte("blockStore")
|
var blockStoreKey = []byte("blockStore")
|
||||||
|
|
||||||
type BlockStoreStateJSON struct {
|
type BlockStoreStateJSON struct {
|
||||||
Height int64
|
Height int64 `json:"height"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save persists the blockStore state to the database as JSON.
|
// Save persists the blockStore state to the database as JSON.
|
||||||
func (bsj BlockStoreStateJSON) Save(db dbm.DB) {
|
func (bsj BlockStoreStateJSON) Save(db dbm.DB) {
|
||||||
bytes, err := json.Marshal(bsj)
|
bytes, err := cdc.MarshalJSON(bsj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmn.PanicSanity(cmn.Fmt("Could not marshal state bytes: %v", err))
|
cmn.PanicSanity(cmn.Fmt("Could not marshal state bytes: %v", err))
|
||||||
}
|
}
|
||||||
@@ -260,7 +239,7 @@ func LoadBlockStoreStateJSON(db dbm.DB) BlockStoreStateJSON {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
bsj := BlockStoreStateJSON{}
|
bsj := BlockStoreStateJSON{}
|
||||||
err := json.Unmarshal(bytes, &bsj)
|
err := cdc.UnmarshalJSON(bytes, &bsj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("Could not unmarshal bytes: %X", bytes))
|
panic(fmt.Sprintf("Could not unmarshal bytes: %X", bytes))
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@ package blockchain
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -11,9 +10,6 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
wire "github.com/tendermint/go-wire"
|
|
||||||
|
|
||||||
"github.com/tendermint/tmlibs/db"
|
"github.com/tendermint/tmlibs/db"
|
||||||
"github.com/tendermint/tmlibs/log"
|
"github.com/tendermint/tmlibs/log"
|
||||||
|
|
||||||
@@ -35,7 +31,7 @@ func TestNewBlockStore(t *testing.T) {
|
|||||||
db := db.NewMemDB()
|
db := db.NewMemDB()
|
||||||
db.Set(blockStoreKey, []byte(`{"height": 10000}`))
|
db.Set(blockStoreKey, []byte(`{"height": 10000}`))
|
||||||
bs := NewBlockStore(db)
|
bs := NewBlockStore(db)
|
||||||
assert.Equal(t, bs.Height(), int64(10000), "failed to properly parse blockstore")
|
require.Equal(t, int64(10000), bs.Height(), "failed to properly parse blockstore")
|
||||||
|
|
||||||
panicCausers := []struct {
|
panicCausers := []struct {
|
||||||
data []byte
|
data []byte
|
||||||
@@ -61,38 +57,6 @@ func TestNewBlockStore(t *testing.T) {
|
|||||||
assert.Equal(t, bs.Height(), int64(0), "expecting nil bytes to be unmarshaled alright")
|
assert.Equal(t, bs.Height(), int64(0), "expecting nil bytes to be unmarshaled alright")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBlockStoreGetReader(t *testing.T) {
|
|
||||||
db := db.NewMemDB()
|
|
||||||
// Initial setup
|
|
||||||
db.Set([]byte("Foo"), []byte("Bar"))
|
|
||||||
db.Set([]byte("Foo1"), nil)
|
|
||||||
|
|
||||||
bs := NewBlockStore(db)
|
|
||||||
|
|
||||||
tests := [...]struct {
|
|
||||||
key []byte
|
|
||||||
want []byte
|
|
||||||
}{
|
|
||||||
0: {key: []byte("Foo"), want: []byte("Bar")},
|
|
||||||
1: {key: []byte("KnoxNonExistent"), want: nil},
|
|
||||||
2: {key: []byte("Foo1"), want: []byte{}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
r := bs.GetReader(tt.key)
|
|
||||||
if r == nil {
|
|
||||||
assert.Nil(t, tt.want, "#%d: expected a non-nil reader", i)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
slurp, err := ioutil.ReadAll(r)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("#%d: unexpected Read err: %v", i, err)
|
|
||||||
} else {
|
|
||||||
assert.Equal(t, slurp, tt.want, "#%d: mismatch", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func freshBlockStore() (*BlockStore, db.DB) {
|
func freshBlockStore() (*BlockStore, db.DB) {
|
||||||
db := db.NewMemDB()
|
db := db.NewMemDB()
|
||||||
return NewBlockStore(db), db
|
return NewBlockStore(db), db
|
||||||
@@ -133,7 +97,7 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) {
|
|||||||
|
|
||||||
incompletePartSet := types.NewPartSetFromHeader(types.PartSetHeader{Total: 2})
|
incompletePartSet := types.NewPartSetFromHeader(types.PartSetHeader{Total: 2})
|
||||||
uncontiguousPartSet := types.NewPartSetFromHeader(types.PartSetHeader{Total: 0})
|
uncontiguousPartSet := types.NewPartSetFromHeader(types.PartSetHeader{Total: 0})
|
||||||
uncontiguousPartSet.AddPart(part2, false)
|
uncontiguousPartSet.AddPart(part2)
|
||||||
|
|
||||||
header1 := types.Header{
|
header1 := types.Header{
|
||||||
Height: 1,
|
Height: 1,
|
||||||
@@ -189,14 +153,14 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) {
|
|||||||
parts: validPartSet,
|
parts: validPartSet,
|
||||||
seenCommit: seenCommit1,
|
seenCommit: seenCommit1,
|
||||||
corruptCommitInDB: true, // Corrupt the DB's commit entry
|
corruptCommitInDB: true, // Corrupt the DB's commit entry
|
||||||
wantPanic: "rror reading commit",
|
wantPanic: "Error reading block commit",
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
block: newBlock(&header1, commitAtH10),
|
block: newBlock(&header1, commitAtH10),
|
||||||
parts: validPartSet,
|
parts: validPartSet,
|
||||||
seenCommit: seenCommit1,
|
seenCommit: seenCommit1,
|
||||||
wantPanic: "rror reading block",
|
wantPanic: "Error reading block",
|
||||||
corruptBlockInDB: true, // Corrupt the DB's block entry
|
corruptBlockInDB: true, // Corrupt the DB's block entry
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -215,7 +179,7 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) {
|
|||||||
seenCommit: seenCommit1,
|
seenCommit: seenCommit1,
|
||||||
|
|
||||||
corruptSeenCommitInDB: true,
|
corruptSeenCommitInDB: true,
|
||||||
wantPanic: "rror reading commit",
|
wantPanic: "Error reading block seen commit",
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -305,14 +269,6 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func binarySerializeIt(v interface{}) []byte {
|
|
||||||
var n int
|
|
||||||
var err error
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
wire.WriteBinary(v, buf, &n, &err)
|
|
||||||
return buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadBlockPart(t *testing.T) {
|
func TestLoadBlockPart(t *testing.T) {
|
||||||
bs, db := freshBlockStore()
|
bs, db := freshBlockStore()
|
||||||
height, index := int64(10), 1
|
height, index := int64(10), 1
|
||||||
@@ -334,7 +290,7 @@ func TestLoadBlockPart(t *testing.T) {
|
|||||||
require.Contains(t, panicErr.Error(), "Error reading block part")
|
require.Contains(t, panicErr.Error(), "Error reading block part")
|
||||||
|
|
||||||
// 3. A good block serialized and saved to the DB should be retrievable
|
// 3. A good block serialized and saved to the DB should be retrievable
|
||||||
db.Set(calcBlockPartKey(height, index), binarySerializeIt(part1))
|
db.Set(calcBlockPartKey(height, index), cdc.MustMarshalBinaryBare(part1))
|
||||||
gotPart, _, panicErr := doFn(loadPart)
|
gotPart, _, panicErr := doFn(loadPart)
|
||||||
require.Nil(t, panicErr, "an existent and proper block should not panic")
|
require.Nil(t, panicErr, "an existent and proper block should not panic")
|
||||||
require.Nil(t, res, "a properly saved block should return a proper block")
|
require.Nil(t, res, "a properly saved block should return a proper block")
|
||||||
@@ -364,11 +320,11 @@ func TestLoadBlockMeta(t *testing.T) {
|
|||||||
|
|
||||||
// 3. A good blockMeta serialized and saved to the DB should be retrievable
|
// 3. A good blockMeta serialized and saved to the DB should be retrievable
|
||||||
meta := &types.BlockMeta{}
|
meta := &types.BlockMeta{}
|
||||||
db.Set(calcBlockMetaKey(height), binarySerializeIt(meta))
|
db.Set(calcBlockMetaKey(height), cdc.MustMarshalBinaryBare(meta))
|
||||||
gotMeta, _, panicErr := doFn(loadMeta)
|
gotMeta, _, panicErr := doFn(loadMeta)
|
||||||
require.Nil(t, panicErr, "an existent and proper block should not panic")
|
require.Nil(t, panicErr, "an existent and proper block should not panic")
|
||||||
require.Nil(t, res, "a properly saved blockMeta should return a proper blocMeta ")
|
require.Nil(t, res, "a properly saved blockMeta should return a proper blocMeta ")
|
||||||
require.Equal(t, binarySerializeIt(meta), binarySerializeIt(gotMeta),
|
require.Equal(t, cdc.MustMarshalBinaryBare(meta), cdc.MustMarshalBinaryBare(gotMeta),
|
||||||
"expecting successful retrieval of previously saved blockMeta")
|
"expecting successful retrieval of previously saved blockMeta")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,6 +341,9 @@ func TestBlockFetchAtHeight(t *testing.T) {
|
|||||||
require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed")
|
require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed")
|
||||||
|
|
||||||
blockAtHeight := bs.LoadBlock(bs.Height())
|
blockAtHeight := bs.LoadBlock(bs.Height())
|
||||||
|
bz1 := cdc.MustMarshalBinaryBare(block)
|
||||||
|
bz2 := cdc.MustMarshalBinaryBare(blockAtHeight)
|
||||||
|
require.Equal(t, bz1, bz2)
|
||||||
require.Equal(t, block.Hash(), blockAtHeight.Hash(),
|
require.Equal(t, block.Hash(), blockAtHeight.Hash(),
|
||||||
"expecting a successful load of the last saved block")
|
"expecting a successful load of the last saved block")
|
||||||
|
|
||||||
|
13
blockchain/wire.go
Normal file
13
blockchain/wire.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package blockchain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tendermint/go-amino"
|
||||||
|
"github.com/tendermint/go-crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cdc = amino.NewCodec()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterBlockchainMessages(cdc)
|
||||||
|
crypto.RegisterAmino(cdc)
|
||||||
|
}
|
@@ -8,7 +8,7 @@ import (
|
|||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
"github.com/tendermint/tmlibs/log"
|
"github.com/tendermint/tmlibs/log"
|
||||||
|
|
||||||
priv_val "github.com/tendermint/tendermint/types/priv_validator"
|
"github.com/tendermint/tendermint/privval"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -30,13 +30,13 @@ func main() {
|
|||||||
"privPath", *privValPath,
|
"privPath", *privValPath,
|
||||||
)
|
)
|
||||||
|
|
||||||
privVal := priv_val.LoadPrivValidatorJSON(*privValPath)
|
pv := privval.LoadFilePV(*privValPath)
|
||||||
|
|
||||||
rs := priv_val.NewRemoteSigner(
|
rs := privval.NewRemoteSigner(
|
||||||
logger,
|
logger,
|
||||||
*chainID,
|
*chainID,
|
||||||
*addr,
|
*addr,
|
||||||
privVal,
|
pv,
|
||||||
crypto.GenPrivKeyEd25519(),
|
crypto.GenPrivKeyEd25519(),
|
||||||
)
|
)
|
||||||
err := rs.Start()
|
err := rs.Start()
|
||||||
|
32
cmd/tendermint/commands/gen_node_key.go
Normal file
32
cmd/tendermint/commands/gen_node_key.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/tendermint/tendermint/p2p"
|
||||||
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenNodeKeyCmd allows the generation of a node key. It prints node's ID to
|
||||||
|
// the standard output.
|
||||||
|
var GenNodeKeyCmd = &cobra.Command{
|
||||||
|
Use: "gen_node_key",
|
||||||
|
Short: "Generate a node key for this node and print its ID",
|
||||||
|
RunE: genNodeKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
func genNodeKey(cmd *cobra.Command, args []string) error {
|
||||||
|
nodeKeyFile := config.NodeKeyFile()
|
||||||
|
if cmn.FileExists(nodeKeyFile) {
|
||||||
|
return fmt.Errorf("node key at %s already exists", nodeKeyFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeKey, err := p2p.LoadOrGenNodeKey(nodeKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println(nodeKey.ID())
|
||||||
|
return nil
|
||||||
|
}
|
@@ -1,12 +1,11 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/types"
|
"github.com/tendermint/tendermint/privval"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenValidatorCmd allows the generation of a keypair for a
|
// GenValidatorCmd allows the generation of a keypair for a
|
||||||
@@ -18,11 +17,11 @@ var GenValidatorCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func genValidator(cmd *cobra.Command, args []string) {
|
func genValidator(cmd *cobra.Command, args []string) {
|
||||||
privValidator := types.GenPrivValidatorFS("")
|
pv := privval.GenFilePV("")
|
||||||
privValidatorJSONBytes, err := json.MarshalIndent(privValidator, "", "\t")
|
jsbz, err := cdc.MarshalJSON(pv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
fmt.Printf(`%v
|
fmt.Printf(`%v
|
||||||
`, string(privValidatorJSONBytes))
|
`, string(jsbz))
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,13 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
cfg "github.com/tendermint/tendermint/config"
|
||||||
|
"github.com/tendermint/tendermint/p2p"
|
||||||
|
"github.com/tendermint/tendermint/privval"
|
||||||
"github.com/tendermint/tendermint/types"
|
"github.com/tendermint/tendermint/types"
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
)
|
)
|
||||||
@@ -11,38 +16,55 @@ import (
|
|||||||
var InitFilesCmd = &cobra.Command{
|
var InitFilesCmd = &cobra.Command{
|
||||||
Use: "init",
|
Use: "init",
|
||||||
Short: "Initialize Tendermint",
|
Short: "Initialize Tendermint",
|
||||||
Run: initFiles,
|
RunE: initFiles,
|
||||||
}
|
}
|
||||||
|
|
||||||
func initFiles(cmd *cobra.Command, args []string) {
|
func initFiles(cmd *cobra.Command, args []string) error {
|
||||||
|
return initFilesWithConfig(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initFilesWithConfig(config *cfg.Config) error {
|
||||||
// private validator
|
// private validator
|
||||||
privValFile := config.PrivValidatorFile()
|
privValFile := config.PrivValidatorFile()
|
||||||
var privValidator *types.PrivValidatorFS
|
var pv *privval.FilePV
|
||||||
if cmn.FileExists(privValFile) {
|
if cmn.FileExists(privValFile) {
|
||||||
privValidator = types.LoadPrivValidatorFS(privValFile)
|
pv = privval.LoadFilePV(privValFile)
|
||||||
logger.Info("Found private validator", "path", privValFile)
|
logger.Info("Found private validator", "path", privValFile)
|
||||||
} else {
|
} else {
|
||||||
privValidator = types.GenPrivValidatorFS(privValFile)
|
pv = privval.GenFilePV(privValFile)
|
||||||
privValidator.Save()
|
pv.Save()
|
||||||
logger.Info("Generated private validator", "path", privValFile)
|
logger.Info("Generated private validator", "path", privValFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nodeKeyFile := config.NodeKeyFile()
|
||||||
|
if cmn.FileExists(nodeKeyFile) {
|
||||||
|
logger.Info("Found node key", "path", nodeKeyFile)
|
||||||
|
} else {
|
||||||
|
if _, err := p2p.LoadOrGenNodeKey(nodeKeyFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Info("Generated node key", "path", nodeKeyFile)
|
||||||
|
}
|
||||||
|
|
||||||
// genesis file
|
// genesis file
|
||||||
genFile := config.GenesisFile()
|
genFile := config.GenesisFile()
|
||||||
if cmn.FileExists(genFile) {
|
if cmn.FileExists(genFile) {
|
||||||
logger.Info("Found genesis file", "path", genFile)
|
logger.Info("Found genesis file", "path", genFile)
|
||||||
} else {
|
} else {
|
||||||
genDoc := types.GenesisDoc{
|
genDoc := types.GenesisDoc{
|
||||||
ChainID: cmn.Fmt("test-chain-%v", cmn.RandStr(6)),
|
ChainID: cmn.Fmt("test-chain-%v", cmn.RandStr(6)),
|
||||||
|
GenesisTime: time.Now(),
|
||||||
}
|
}
|
||||||
genDoc.Validators = []types.GenesisValidator{{
|
genDoc.Validators = []types.GenesisValidator{{
|
||||||
PubKey: privValidator.GetPubKey(),
|
PubKey: pv.GetPubKey(),
|
||||||
Power: 10,
|
Power: 10,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
if err := genDoc.SaveAs(genFile); err != nil {
|
if err := genDoc.SaveAs(genFile); err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
logger.Info("Generated genesis file", "path", genFile)
|
logger.Info("Generated genesis file", "path", genFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -34,14 +34,14 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
LiteCmd.Flags().StringVar(&listenAddr, "laddr", ":8888", "Serve the proxy on the given port")
|
LiteCmd.Flags().StringVar(&listenAddr, "laddr", "tcp://localhost:8888", "Serve the proxy on the given address")
|
||||||
LiteCmd.Flags().StringVar(&nodeAddr, "node", "tcp://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(&chainID, "chain-id", "tendermint", "Specify the Tendermint chain ID")
|
||||||
LiteCmd.Flags().StringVar(&home, "home-dir", ".tendermint-lite", "Specify the home directory")
|
LiteCmd.Flags().StringVar(&home, "home-dir", ".tendermint-lite", "Specify the home directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureAddrHasSchemeOrDefaultToTCP(addr string) (string, error) {
|
func ensureAddrHasSchemeOrDefaultToTCP(addr string) (string, error) {
|
||||||
u, err := url.Parse(nodeAddr)
|
u, err := url.Parse(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -22,7 +21,7 @@ func probeUpnp(cmd *cobra.Command, args []string) error {
|
|||||||
fmt.Println("Probe failed: ", err)
|
fmt.Println("Probe failed: ", err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("Probe success!")
|
fmt.Println("Probe success!")
|
||||||
jsonBytes, err := json.Marshal(capabilities)
|
jsonBytes, err := cdc.MarshalJSON(capabilities)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/types"
|
"github.com/tendermint/tendermint/privval"
|
||||||
"github.com/tendermint/tmlibs/log"
|
"github.com/tendermint/tmlibs/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,26 +13,26 @@ import (
|
|||||||
// instance.
|
// instance.
|
||||||
var ResetAllCmd = &cobra.Command{
|
var ResetAllCmd = &cobra.Command{
|
||||||
Use: "unsafe_reset_all",
|
Use: "unsafe_reset_all",
|
||||||
Short: "(unsafe) Remove all the data and WAL, reset this node's validator",
|
Short: "(unsafe) Remove all the data and WAL, reset this node's validator to genesis state",
|
||||||
Run: resetAll,
|
Run: resetAll,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetPrivValidatorCmd resets the private validator files.
|
// ResetPrivValidatorCmd resets the private validator files.
|
||||||
var ResetPrivValidatorCmd = &cobra.Command{
|
var ResetPrivValidatorCmd = &cobra.Command{
|
||||||
Use: "unsafe_reset_priv_validator",
|
Use: "unsafe_reset_priv_validator",
|
||||||
Short: "(unsafe) Reset this node's validator",
|
Short: "(unsafe) Reset this node's validator to genesis state",
|
||||||
Run: resetPrivValidator,
|
Run: resetPrivValidator,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetAll removes the privValidator files.
|
// ResetAll removes the privValidator files.
|
||||||
// Exported so other CLI tools can use it.
|
// Exported so other CLI tools can use it.
|
||||||
func ResetAll(dbDir, privValFile string, logger log.Logger) {
|
func ResetAll(dbDir, privValFile string, logger log.Logger) {
|
||||||
resetPrivValidatorFS(privValFile, logger)
|
resetFilePV(privValFile, logger)
|
||||||
if err := os.RemoveAll(dbDir); err != nil {
|
if err := os.RemoveAll(dbDir); err != nil {
|
||||||
logger.Error("Error removing directory", "err", err)
|
logger.Error("Error removing directory", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Info("Removed all data", "dir", dbDir)
|
logger.Info("Removed all blockchain history", "dir", dbDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: this is totally unsafe.
|
// XXX: this is totally unsafe.
|
||||||
@@ -44,18 +44,18 @@ func resetAll(cmd *cobra.Command, args []string) {
|
|||||||
// XXX: this is totally unsafe.
|
// XXX: this is totally unsafe.
|
||||||
// it's only suitable for testnets.
|
// it's only suitable for testnets.
|
||||||
func resetPrivValidator(cmd *cobra.Command, args []string) {
|
func resetPrivValidator(cmd *cobra.Command, args []string) {
|
||||||
resetPrivValidatorFS(config.PrivValidatorFile(), logger)
|
resetFilePV(config.PrivValidatorFile(), logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resetPrivValidatorFS(privValFile string, logger log.Logger) {
|
func resetFilePV(privValFile string, logger log.Logger) {
|
||||||
// Get PrivValidator
|
// Get PrivValidator
|
||||||
if _, err := os.Stat(privValFile); err == nil {
|
if _, err := os.Stat(privValFile); err == nil {
|
||||||
privValidator := types.LoadPrivValidatorFS(privValFile)
|
pv := privval.LoadFilePV(privValFile)
|
||||||
privValidator.Reset()
|
pv.Reset()
|
||||||
logger.Info("Reset PrivValidator", "file", privValFile)
|
logger.Info("Reset PrivValidator to genesis state", "file", privValFile)
|
||||||
} else {
|
} else {
|
||||||
privValidator := types.GenPrivValidatorFS(privValFile)
|
pv := privval.GenFilePV(privValFile)
|
||||||
privValidator.Save()
|
pv.Save()
|
||||||
logger.Info("Generated PrivValidator", "file", privValFile)
|
logger.Info("Generated PrivValidator", "file", privValFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -57,9 +57,8 @@ func NewRunNodeCmd(nodeProvider nm.NodeProvider) *cobra.Command {
|
|||||||
|
|
||||||
if err := n.Start(); err != nil {
|
if err := n.Start(); err != nil {
|
||||||
return fmt.Errorf("Failed to start node: %v", err)
|
return fmt.Errorf("Failed to start node: %v", err)
|
||||||
} else {
|
|
||||||
logger.Info("Started node", "nodeInfo", n.Switch().NodeInfo())
|
|
||||||
}
|
}
|
||||||
|
logger.Info("Started node", "nodeInfo", n.Switch().NodeInfo())
|
||||||
|
|
||||||
// Trap signal, run forever.
|
// Trap signal, run forever.
|
||||||
n.RunForever()
|
n.RunForever()
|
||||||
|
@@ -16,10 +16,12 @@ var ShowNodeIDCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func showNodeID(cmd *cobra.Command, args []string) error {
|
func showNodeID(cmd *cobra.Command, args []string) error {
|
||||||
nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
|
|
||||||
|
nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println(nodeKey.ID())
|
fmt.Println(nodeKey.ID())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -5,8 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/tendermint/go-wire/data"
|
"github.com/tendermint/tendermint/privval"
|
||||||
"github.com/tendermint/tendermint/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ShowValidatorCmd adds capabilities for showing the validator info.
|
// ShowValidatorCmd adds capabilities for showing the validator info.
|
||||||
@@ -17,7 +16,7 @@ var ShowValidatorCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func showValidator(cmd *cobra.Command, args []string) {
|
func showValidator(cmd *cobra.Command, args []string) {
|
||||||
privValidator := types.LoadOrGenPrivValidatorFS(config.PrivValidatorFile())
|
privValidator := privval.LoadOrGenFilePV(config.PrivValidatorFile())
|
||||||
pubKeyJSONBytes, _ := data.ToJSON(privValidator.PubKey)
|
pubKeyJSONBytes, _ := cdc.MarshalJSON(privValidator.GetPubKey())
|
||||||
fmt.Println(string(pubKeyJSONBytes))
|
fmt.Println(string(pubKeyJSONBytes))
|
||||||
}
|
}
|
||||||
|
@@ -2,59 +2,114 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
cfg "github.com/tendermint/tendermint/config"
|
cfg "github.com/tendermint/tendermint/config"
|
||||||
|
"github.com/tendermint/tendermint/p2p"
|
||||||
|
"github.com/tendermint/tendermint/privval"
|
||||||
"github.com/tendermint/tendermint/types"
|
"github.com/tendermint/tendermint/types"
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
//flags
|
|
||||||
var (
|
var (
|
||||||
nValidators int
|
nValidators int
|
||||||
dataDir string
|
nNonValidators int
|
||||||
|
outputDir string
|
||||||
|
nodeDirPrefix string
|
||||||
|
|
||||||
|
populatePersistentPeers bool
|
||||||
|
hostnamePrefix string
|
||||||
|
startingIPAddress string
|
||||||
|
p2pPort int
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
nodeDirPerm = 0755
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
TestnetFilesCmd.Flags().IntVar(&nValidators, "n", 4,
|
TestnetFilesCmd.Flags().IntVar(&nValidators, "v", 4,
|
||||||
"Number of validators to initialize the testnet with")
|
"Number of validators to initialize the testnet with")
|
||||||
TestnetFilesCmd.Flags().StringVar(&dataDir, "dir", "mytestnet",
|
TestnetFilesCmd.Flags().IntVar(&nNonValidators, "n", 0,
|
||||||
|
"Number of non-validators to initialize the testnet with")
|
||||||
|
TestnetFilesCmd.Flags().StringVar(&outputDir, "o", "./mytestnet",
|
||||||
"Directory to store initialization data for the testnet")
|
"Directory to store initialization data for the testnet")
|
||||||
|
TestnetFilesCmd.Flags().StringVar(&nodeDirPrefix, "node-dir-prefix", "node",
|
||||||
|
"Prefix the directory name for each node with (node results in node0, node1, ...)")
|
||||||
|
|
||||||
|
TestnetFilesCmd.Flags().BoolVar(&populatePersistentPeers, "populate-persistent-peers", true,
|
||||||
|
"Update config of each node with the list of persistent peers build using either hostname-prefix or starting-ip-address")
|
||||||
|
TestnetFilesCmd.Flags().StringVar(&hostnamePrefix, "hostname-prefix", "node",
|
||||||
|
"Hostname prefix (node results in persistent peers list ID0@node0:46656, ID1@node1:46656, ...)")
|
||||||
|
TestnetFilesCmd.Flags().StringVar(&startingIPAddress, "starting-ip-address", "",
|
||||||
|
"Starting IP address (192.168.0.1 results in persistent peers list ID0@192.168.0.1:46656, ID1@192.168.0.2:46656, ...)")
|
||||||
|
TestnetFilesCmd.Flags().IntVar(&p2pPort, "p2p-port", 46656,
|
||||||
|
"P2P Port")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestnetFilesCmd allows initialisation of files for a
|
// TestnetFilesCmd allows initialisation of files for a Tendermint testnet.
|
||||||
// Tendermint testnet.
|
|
||||||
var TestnetFilesCmd = &cobra.Command{
|
var TestnetFilesCmd = &cobra.Command{
|
||||||
Use: "testnet",
|
Use: "testnet",
|
||||||
Short: "Initialize files for a Tendermint testnet",
|
Short: "Initialize files for a Tendermint testnet",
|
||||||
Run: testnetFiles,
|
Long: `testnet will create "v" + "n" number of directories and populate each with
|
||||||
|
necessary files (private validator, genesis, config, etc.).
|
||||||
|
|
||||||
|
Note, strict routability for addresses is turned off in the config file.
|
||||||
|
|
||||||
|
Optionally, it will fill in persistent_peers list in config file using either hostnames or IPs.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
tendermint testnet --v 4 --o ./output --populate-persistent-peers --starting-ip-address 192.168.10.2
|
||||||
|
`,
|
||||||
|
RunE: testnetFiles,
|
||||||
}
|
}
|
||||||
|
|
||||||
func testnetFiles(cmd *cobra.Command, args []string) {
|
func testnetFiles(cmd *cobra.Command, args []string) error {
|
||||||
|
config := cfg.DefaultConfig()
|
||||||
genVals := make([]types.GenesisValidator, nValidators)
|
genVals := make([]types.GenesisValidator, nValidators)
|
||||||
defaultConfig := cfg.DefaultBaseConfig()
|
|
||||||
|
|
||||||
// Initialize core dir and priv_validator.json's
|
|
||||||
for i := 0; i < nValidators; i++ {
|
for i := 0; i < nValidators; i++ {
|
||||||
mach := cmn.Fmt("mach%d", i)
|
nodeDirName := cmn.Fmt("%s%d", nodeDirPrefix, i)
|
||||||
err := initMachCoreDirectory(dataDir, mach)
|
nodeDir := filepath.Join(outputDir, nodeDirName)
|
||||||
|
config.SetRoot(nodeDir)
|
||||||
|
|
||||||
|
err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmn.Exit(err.Error())
|
_ = os.RemoveAll(outputDir)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
// Read priv_validator.json to populate vals
|
|
||||||
privValFile := filepath.Join(dataDir, mach, defaultConfig.PrivValidator)
|
initFilesWithConfig(config)
|
||||||
privVal := types.LoadPrivValidatorFS(privValFile)
|
|
||||||
|
pvFile := filepath.Join(nodeDir, config.BaseConfig.PrivValidator)
|
||||||
|
pv := privval.LoadFilePV(pvFile)
|
||||||
genVals[i] = types.GenesisValidator{
|
genVals[i] = types.GenesisValidator{
|
||||||
PubKey: privVal.GetPubKey(),
|
PubKey: pv.GetPubKey(),
|
||||||
Power: 1,
|
Power: 1,
|
||||||
Name: mach,
|
Name: nodeDirName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i := 0; i < nNonValidators; i++ {
|
||||||
|
nodeDir := filepath.Join(outputDir, cmn.Fmt("%s%d", nodeDirPrefix, i+nValidators))
|
||||||
|
config.SetRoot(nodeDir)
|
||||||
|
|
||||||
|
err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm)
|
||||||
|
if err != nil {
|
||||||
|
_ = os.RemoveAll(outputDir)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
initFilesWithConfig(config)
|
||||||
|
}
|
||||||
|
|
||||||
// Generate genesis doc from generated validators
|
// Generate genesis doc from generated validators
|
||||||
genDoc := &types.GenesisDoc{
|
genDoc := &types.GenesisDoc{
|
||||||
GenesisTime: time.Now(),
|
GenesisTime: time.Now(),
|
||||||
@@ -63,36 +118,66 @@ func testnetFiles(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write genesis file.
|
// Write genesis file.
|
||||||
for i := 0; i < nValidators; i++ {
|
for i := 0; i < nValidators+nNonValidators; i++ {
|
||||||
mach := cmn.Fmt("mach%d", i)
|
nodeDir := filepath.Join(outputDir, cmn.Fmt("%s%d", nodeDirPrefix, i))
|
||||||
if err := genDoc.SaveAs(filepath.Join(dataDir, mach, defaultConfig.Genesis)); err != nil {
|
if err := genDoc.SaveAs(filepath.Join(nodeDir, config.BaseConfig.Genesis)); err != nil {
|
||||||
panic(err)
|
_ = os.RemoveAll(outputDir)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(cmn.Fmt("Successfully initialized %v node directories", nValidators))
|
if populatePersistentPeers {
|
||||||
}
|
err := populatePersistentPeersInConfigAndWriteIt(config)
|
||||||
|
if err != nil {
|
||||||
// Initialize per-machine core directory
|
_ = os.RemoveAll(outputDir)
|
||||||
func initMachCoreDirectory(base, mach string) error {
|
return err
|
||||||
// Create priv_validator.json file if not present
|
}
|
||||||
defaultConfig := cfg.DefaultBaseConfig()
|
|
||||||
dir := filepath.Join(base, mach)
|
|
||||||
privValPath := filepath.Join(dir, defaultConfig.PrivValidator)
|
|
||||||
dir = filepath.Dir(privValPath)
|
|
||||||
err := cmn.EnsureDir(dir, 0700)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
ensurePrivValidator(privValPath)
|
|
||||||
|
fmt.Printf("Successfully initialized %v node directories\n", nValidators+nNonValidators)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensurePrivValidator(file string) {
|
func hostnameOrIP(i int) string {
|
||||||
if cmn.FileExists(file) {
|
if startingIPAddress != "" {
|
||||||
return
|
ip := net.ParseIP(startingIPAddress)
|
||||||
|
ip = ip.To4()
|
||||||
|
if ip == nil {
|
||||||
|
fmt.Printf("%v: non ipv4 address\n", startingIPAddress)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := 0; j < i; j++ {
|
||||||
|
ip[3]++
|
||||||
|
}
|
||||||
|
return ip.String()
|
||||||
}
|
}
|
||||||
privValidator := types.GenPrivValidatorFS(file)
|
|
||||||
privValidator.Save()
|
return fmt.Sprintf("%s%d", hostnamePrefix, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func populatePersistentPeersInConfigAndWriteIt(config *cfg.Config) error {
|
||||||
|
persistentPeers := make([]string, nValidators+nNonValidators)
|
||||||
|
for i := 0; i < nValidators+nNonValidators; i++ {
|
||||||
|
nodeDir := filepath.Join(outputDir, cmn.Fmt("%s%d", nodeDirPrefix, i))
|
||||||
|
config.SetRoot(nodeDir)
|
||||||
|
nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
persistentPeers[i] = p2p.IDAddressString(nodeKey.ID(), fmt.Sprintf("%s:%d", hostnameOrIP(i), p2pPort))
|
||||||
|
}
|
||||||
|
persistentPeersList := strings.Join(persistentPeers, ",")
|
||||||
|
|
||||||
|
for i := 0; i < nValidators+nNonValidators; i++ {
|
||||||
|
nodeDir := filepath.Join(outputDir, cmn.Fmt("%s%d", nodeDirPrefix, i))
|
||||||
|
config.SetRoot(nodeDir)
|
||||||
|
config.P2P.PersistentPeers = persistentPeersList
|
||||||
|
config.P2P.AddrBookStrict = false
|
||||||
|
|
||||||
|
// overwrite default config
|
||||||
|
cfg.WriteConfigFile(filepath.Join(nodeDir, "config", "config.toml"), config)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
12
cmd/tendermint/commands/wire.go
Normal file
12
cmd/tendermint/commands/wire.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tendermint/go-amino"
|
||||||
|
"github.com/tendermint/go-crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cdc = amino.NewCodec()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
crypto.RegisterAmino(cdc)
|
||||||
|
}
|
@@ -25,6 +25,7 @@ func main() {
|
|||||||
cmd.ShowValidatorCmd,
|
cmd.ShowValidatorCmd,
|
||||||
cmd.TestnetFilesCmd,
|
cmd.TestnetFilesCmd,
|
||||||
cmd.ShowNodeIDCmd,
|
cmd.ShowNodeIDCmd,
|
||||||
|
cmd.GenNodeKeyCmd,
|
||||||
cmd.VersionCmd)
|
cmd.VersionCmd)
|
||||||
|
|
||||||
// NOTE:
|
// NOTE:
|
||||||
|
@@ -16,3 +16,8 @@ comment:
|
|||||||
require_changes: no
|
require_changes: no
|
||||||
require_base: no
|
require_base: no
|
||||||
require_head: yes
|
require_head: yes
|
||||||
|
|
||||||
|
ignore:
|
||||||
|
- "docs"
|
||||||
|
- "DOCKER"
|
||||||
|
- "scripts"
|
||||||
|
225
config/config.go
225
config/config.go
@@ -7,6 +7,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// FuzzModeDrop is a mode in which we randomly drop reads/writes, connections or sleep
|
||||||
|
FuzzModeDrop = iota
|
||||||
|
// FuzzModeDelay is a mode in which we randomly sleep
|
||||||
|
FuzzModeDelay
|
||||||
|
)
|
||||||
|
|
||||||
// NOTE: Most of the structs & relevant comments + the
|
// NOTE: Most of the structs & relevant comments + the
|
||||||
// default configuration options were used to manually
|
// default configuration options were used to manually
|
||||||
// generate the config.toml. Please reflect any changes
|
// generate the config.toml. Please reflect any changes
|
||||||
@@ -137,10 +144,6 @@ type BaseConfig struct {
|
|||||||
DBPath string `mapstructure:"db_dir"`
|
DBPath string `mapstructure:"db_dir"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c BaseConfig) ChainID() string {
|
|
||||||
return c.chainID
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultBaseConfig returns a default base configuration for a Tendermint node
|
// DefaultBaseConfig returns a default base configuration for a Tendermint node
|
||||||
func DefaultBaseConfig() BaseConfig {
|
func DefaultBaseConfig() BaseConfig {
|
||||||
return BaseConfig{
|
return BaseConfig{
|
||||||
@@ -161,32 +164,36 @@ func DefaultBaseConfig() BaseConfig {
|
|||||||
|
|
||||||
// TestBaseConfig returns a base configuration for testing a Tendermint node
|
// TestBaseConfig returns a base configuration for testing a Tendermint node
|
||||||
func TestBaseConfig() BaseConfig {
|
func TestBaseConfig() BaseConfig {
|
||||||
conf := DefaultBaseConfig()
|
cfg := DefaultBaseConfig()
|
||||||
conf.chainID = "tendermint_test"
|
cfg.chainID = "tendermint_test"
|
||||||
conf.ProxyApp = "kvstore"
|
cfg.ProxyApp = "kvstore"
|
||||||
conf.FastSync = false
|
cfg.FastSync = false
|
||||||
conf.DBBackend = "memdb"
|
cfg.DBBackend = "memdb"
|
||||||
return conf
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg BaseConfig) ChainID() string {
|
||||||
|
return cfg.chainID
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenesisFile returns the full path to the genesis.json file
|
// GenesisFile returns the full path to the genesis.json file
|
||||||
func (b BaseConfig) GenesisFile() string {
|
func (cfg BaseConfig) GenesisFile() string {
|
||||||
return rootify(b.Genesis, b.RootDir)
|
return rootify(cfg.Genesis, cfg.RootDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrivValidatorFile returns the full path to the priv_validator.json file
|
// PrivValidatorFile returns the full path to the priv_validator.json file
|
||||||
func (b BaseConfig) PrivValidatorFile() string {
|
func (cfg BaseConfig) PrivValidatorFile() string {
|
||||||
return rootify(b.PrivValidator, b.RootDir)
|
return rootify(cfg.PrivValidator, cfg.RootDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeKeyFile returns the full path to the node_key.json file
|
// NodeKeyFile returns the full path to the node_key.json file
|
||||||
func (b BaseConfig) NodeKeyFile() string {
|
func (cfg BaseConfig) NodeKeyFile() string {
|
||||||
return rootify(b.NodeKey, b.RootDir)
|
return rootify(cfg.NodeKey, cfg.RootDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DBDir returns the full path to the database directory
|
// DBDir returns the full path to the database directory
|
||||||
func (b BaseConfig) DBDir() string {
|
func (cfg BaseConfig) DBDir() string {
|
||||||
return rootify(b.DBPath, b.RootDir)
|
return rootify(cfg.DBPath, cfg.RootDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultLogLevel returns a default log level of "error"
|
// DefaultLogLevel returns a default log level of "error"
|
||||||
@@ -229,11 +236,11 @@ func DefaultRPCConfig() *RPCConfig {
|
|||||||
|
|
||||||
// TestRPCConfig returns a configuration for testing the RPC server
|
// TestRPCConfig returns a configuration for testing the RPC server
|
||||||
func TestRPCConfig() *RPCConfig {
|
func TestRPCConfig() *RPCConfig {
|
||||||
conf := DefaultRPCConfig()
|
cfg := DefaultRPCConfig()
|
||||||
conf.ListenAddress = "tcp://0.0.0.0:36657"
|
cfg.ListenAddress = "tcp://0.0.0.0:36657"
|
||||||
conf.GRPCListenAddress = "tcp://0.0.0.0:36658"
|
cfg.GRPCListenAddress = "tcp://0.0.0.0:36658"
|
||||||
conf.Unsafe = true
|
cfg.Unsafe = true
|
||||||
return conf
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
@@ -270,7 +277,7 @@ type P2PConfig struct {
|
|||||||
FlushThrottleTimeout int `mapstructure:"flush_throttle_timeout"`
|
FlushThrottleTimeout int `mapstructure:"flush_throttle_timeout"`
|
||||||
|
|
||||||
// Maximum size of a message packet payload, in bytes
|
// Maximum size of a message packet payload, in bytes
|
||||||
MaxMsgPacketPayloadSize int `mapstructure:"max_msg_packet_payload_size"`
|
MaxPacketMsgPayloadSize int `mapstructure:"max_packet_msg_payload_size"`
|
||||||
|
|
||||||
// Rate at which packets can be sent, in bytes/second
|
// Rate at which packets can be sent, in bytes/second
|
||||||
SendRate int64 `mapstructure:"send_rate"`
|
SendRate int64 `mapstructure:"send_rate"`
|
||||||
@@ -287,11 +294,23 @@ type P2PConfig struct {
|
|||||||
// Does not work if the peer-exchange reactor is disabled.
|
// Does not work if the peer-exchange reactor is disabled.
|
||||||
SeedMode bool `mapstructure:"seed_mode"`
|
SeedMode bool `mapstructure:"seed_mode"`
|
||||||
|
|
||||||
// Authenticated encryption
|
// Comma separated list of peer IDs to keep private (will not be gossiped to
|
||||||
AuthEnc bool `mapstructure:"auth_enc"`
|
// other peers)
|
||||||
|
|
||||||
// Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
|
||||||
PrivatePeerIDs string `mapstructure:"private_peer_ids"`
|
PrivatePeerIDs string `mapstructure:"private_peer_ids"`
|
||||||
|
|
||||||
|
// Toggle to disable guard against peers connecting from the same ip.
|
||||||
|
AllowDuplicateIP bool `mapstructure:"allow_duplicate_ip"`
|
||||||
|
|
||||||
|
// Peer connection configuration.
|
||||||
|
HandshakeTimeout time.Duration `mapstructure:"handshake_timeout"`
|
||||||
|
DialTimeout time.Duration `mapstructure:"dial_timeout"`
|
||||||
|
|
||||||
|
// Testing params.
|
||||||
|
// Force dial to fail
|
||||||
|
TestDialFail bool `mapstructure:"test_dial_fail"`
|
||||||
|
// FUzz connection
|
||||||
|
TestFuzz bool `mapstructure:"test_fuzz"`
|
||||||
|
TestFuzzConfig *FuzzConnConfig `mapstructure:"test_fuzz_config"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultP2PConfig returns a default configuration for the peer-to-peer layer
|
// DefaultP2PConfig returns a default configuration for the peer-to-peer layer
|
||||||
@@ -302,27 +321,53 @@ func DefaultP2PConfig() *P2PConfig {
|
|||||||
AddrBookStrict: true,
|
AddrBookStrict: true,
|
||||||
MaxNumPeers: 50,
|
MaxNumPeers: 50,
|
||||||
FlushThrottleTimeout: 100,
|
FlushThrottleTimeout: 100,
|
||||||
MaxMsgPacketPayloadSize: 1024, // 1 kB
|
MaxPacketMsgPayloadSize: 1024, // 1 kB
|
||||||
SendRate: 512000, // 500 kB/s
|
SendRate: 512000, // 500 kB/s
|
||||||
RecvRate: 512000, // 500 kB/s
|
RecvRate: 512000, // 500 kB/s
|
||||||
PexReactor: true,
|
PexReactor: true,
|
||||||
SeedMode: false,
|
SeedMode: false,
|
||||||
AuthEnc: true,
|
AllowDuplicateIP: true, // so non-breaking yet
|
||||||
|
HandshakeTimeout: 20 * time.Second,
|
||||||
|
DialTimeout: 3 * time.Second,
|
||||||
|
TestDialFail: false,
|
||||||
|
TestFuzz: false,
|
||||||
|
TestFuzzConfig: DefaultFuzzConnConfig(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestP2PConfig returns a configuration for testing the peer-to-peer layer
|
// TestP2PConfig returns a configuration for testing the peer-to-peer layer
|
||||||
func TestP2PConfig() *P2PConfig {
|
func TestP2PConfig() *P2PConfig {
|
||||||
conf := DefaultP2PConfig()
|
cfg := DefaultP2PConfig()
|
||||||
conf.ListenAddress = "tcp://0.0.0.0:36656"
|
cfg.ListenAddress = "tcp://0.0.0.0:36656"
|
||||||
conf.SkipUPNP = true
|
cfg.SkipUPNP = true
|
||||||
conf.FlushThrottleTimeout = 10
|
cfg.FlushThrottleTimeout = 10
|
||||||
return conf
|
cfg.AllowDuplicateIP = true
|
||||||
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddrBookFile returns the full path to the address book
|
// AddrBookFile returns the full path to the address book
|
||||||
func (p *P2PConfig) AddrBookFile() string {
|
func (cfg *P2PConfig) AddrBookFile() string {
|
||||||
return rootify(p.AddrBook, p.RootDir)
|
return rootify(cfg.AddrBook, cfg.RootDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuzzConnConfig is a FuzzedConnection configuration.
|
||||||
|
type FuzzConnConfig struct {
|
||||||
|
Mode int
|
||||||
|
MaxDelay time.Duration
|
||||||
|
ProbDropRW float64
|
||||||
|
ProbDropConn float64
|
||||||
|
ProbSleep float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultFuzzConnConfig returns the default config.
|
||||||
|
func DefaultFuzzConnConfig() *FuzzConnConfig {
|
||||||
|
return &FuzzConnConfig{
|
||||||
|
Mode: FuzzModeDrop,
|
||||||
|
MaxDelay: 3 * time.Second,
|
||||||
|
ProbDropRW: 0.2,
|
||||||
|
ProbDropConn: 0.00,
|
||||||
|
ProbSleep: 0.00,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
@@ -335,6 +380,7 @@ type MempoolConfig struct {
|
|||||||
RecheckEmpty bool `mapstructure:"recheck_empty"`
|
RecheckEmpty bool `mapstructure:"recheck_empty"`
|
||||||
Broadcast bool `mapstructure:"broadcast"`
|
Broadcast bool `mapstructure:"broadcast"`
|
||||||
WalPath string `mapstructure:"wal_dir"`
|
WalPath string `mapstructure:"wal_dir"`
|
||||||
|
Size int `mapstructure:"size"`
|
||||||
CacheSize int `mapstructure:"cache_size"`
|
CacheSize int `mapstructure:"cache_size"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,20 +391,21 @@ func DefaultMempoolConfig() *MempoolConfig {
|
|||||||
RecheckEmpty: true,
|
RecheckEmpty: true,
|
||||||
Broadcast: true,
|
Broadcast: true,
|
||||||
WalPath: filepath.Join(defaultDataDir, "mempool.wal"),
|
WalPath: filepath.Join(defaultDataDir, "mempool.wal"),
|
||||||
|
Size: 100000,
|
||||||
CacheSize: 100000,
|
CacheSize: 100000,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestMempoolConfig returns a configuration for testing the Tendermint mempool
|
// TestMempoolConfig returns a configuration for testing the Tendermint mempool
|
||||||
func TestMempoolConfig() *MempoolConfig {
|
func TestMempoolConfig() *MempoolConfig {
|
||||||
config := DefaultMempoolConfig()
|
cfg := DefaultMempoolConfig()
|
||||||
config.CacheSize = 1000
|
cfg.CacheSize = 1000
|
||||||
return config
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
// WalDir returns the full path to the mempool's write-ahead log
|
// WalDir returns the full path to the mempool's write-ahead log
|
||||||
func (m *MempoolConfig) WalDir() string {
|
func (cfg *MempoolConfig) WalDir() string {
|
||||||
return rootify(m.WalPath, m.RootDir)
|
return rootify(cfg.WalPath, cfg.RootDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
@@ -367,10 +414,9 @@ func (m *MempoolConfig) WalDir() string {
|
|||||||
// ConsensusConfig defines the confuguration for the Tendermint consensus service,
|
// ConsensusConfig defines the confuguration for the Tendermint consensus service,
|
||||||
// including timeouts and details about the WAL and the block structure.
|
// including timeouts and details about the WAL and the block structure.
|
||||||
type ConsensusConfig struct {
|
type ConsensusConfig struct {
|
||||||
RootDir string `mapstructure:"home"`
|
RootDir string `mapstructure:"home"`
|
||||||
WalPath string `mapstructure:"wal_file"`
|
WalPath string `mapstructure:"wal_file"`
|
||||||
WalLight bool `mapstructure:"wal_light"`
|
walFile string // overrides WalPath if set
|
||||||
walFile string // overrides WalPath if set
|
|
||||||
|
|
||||||
// All timeouts are in milliseconds
|
// All timeouts are in milliseconds
|
||||||
TimeoutPropose int `mapstructure:"timeout_propose"`
|
TimeoutPropose int `mapstructure:"timeout_propose"`
|
||||||
@@ -397,6 +443,43 @@ type ConsensusConfig struct {
|
|||||||
PeerQueryMaj23SleepDuration int `mapstructure:"peer_query_maj23_sleep_duration"`
|
PeerQueryMaj23SleepDuration int `mapstructure:"peer_query_maj23_sleep_duration"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultConsensusConfig returns a default configuration for the consensus service
|
||||||
|
func DefaultConsensusConfig() *ConsensusConfig {
|
||||||
|
return &ConsensusConfig{
|
||||||
|
WalPath: filepath.Join(defaultDataDir, "cs.wal", "wal"),
|
||||||
|
TimeoutPropose: 3000,
|
||||||
|
TimeoutProposeDelta: 500,
|
||||||
|
TimeoutPrevote: 1000,
|
||||||
|
TimeoutPrevoteDelta: 500,
|
||||||
|
TimeoutPrecommit: 1000,
|
||||||
|
TimeoutPrecommitDelta: 500,
|
||||||
|
TimeoutCommit: 1000,
|
||||||
|
SkipTimeoutCommit: false,
|
||||||
|
MaxBlockSizeTxs: 10000,
|
||||||
|
MaxBlockSizeBytes: 1, // TODO
|
||||||
|
CreateEmptyBlocks: true,
|
||||||
|
CreateEmptyBlocksInterval: 0,
|
||||||
|
PeerGossipSleepDuration: 100,
|
||||||
|
PeerQueryMaj23SleepDuration: 2000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestConsensusConfig returns a configuration for testing the consensus service
|
||||||
|
func TestConsensusConfig() *ConsensusConfig {
|
||||||
|
cfg := DefaultConsensusConfig()
|
||||||
|
cfg.TimeoutPropose = 100
|
||||||
|
cfg.TimeoutProposeDelta = 1
|
||||||
|
cfg.TimeoutPrevote = 10
|
||||||
|
cfg.TimeoutPrevoteDelta = 1
|
||||||
|
cfg.TimeoutPrecommit = 10
|
||||||
|
cfg.TimeoutPrecommitDelta = 1
|
||||||
|
cfg.TimeoutCommit = 10
|
||||||
|
cfg.SkipTimeoutCommit = true
|
||||||
|
cfg.PeerGossipSleepDuration = 5
|
||||||
|
cfg.PeerQueryMaj23SleepDuration = 250
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
// WaitForTxs returns true if the consensus should wait for transactions before entering the propose step
|
// WaitForTxs returns true if the consensus should wait for transactions before entering the propose step
|
||||||
func (cfg *ConsensusConfig) WaitForTxs() bool {
|
func (cfg *ConsensusConfig) WaitForTxs() bool {
|
||||||
return !cfg.CreateEmptyBlocks || cfg.CreateEmptyBlocksInterval > 0
|
return !cfg.CreateEmptyBlocks || cfg.CreateEmptyBlocksInterval > 0
|
||||||
@@ -437,55 +520,17 @@ func (cfg *ConsensusConfig) PeerQueryMaj23Sleep() time.Duration {
|
|||||||
return time.Duration(cfg.PeerQueryMaj23SleepDuration) * time.Millisecond
|
return time.Duration(cfg.PeerQueryMaj23SleepDuration) * time.Millisecond
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultConsensusConfig returns a default configuration for the consensus service
|
|
||||||
func DefaultConsensusConfig() *ConsensusConfig {
|
|
||||||
return &ConsensusConfig{
|
|
||||||
WalPath: filepath.Join(defaultDataDir, "cs.wal", "wal"),
|
|
||||||
WalLight: false,
|
|
||||||
TimeoutPropose: 3000,
|
|
||||||
TimeoutProposeDelta: 500,
|
|
||||||
TimeoutPrevote: 1000,
|
|
||||||
TimeoutPrevoteDelta: 500,
|
|
||||||
TimeoutPrecommit: 1000,
|
|
||||||
TimeoutPrecommitDelta: 500,
|
|
||||||
TimeoutCommit: 1000,
|
|
||||||
SkipTimeoutCommit: false,
|
|
||||||
MaxBlockSizeTxs: 10000,
|
|
||||||
MaxBlockSizeBytes: 1, // TODO
|
|
||||||
CreateEmptyBlocks: true,
|
|
||||||
CreateEmptyBlocksInterval: 0,
|
|
||||||
PeerGossipSleepDuration: 100,
|
|
||||||
PeerQueryMaj23SleepDuration: 2000,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestConsensusConfig returns a configuration for testing the consensus service
|
|
||||||
func TestConsensusConfig() *ConsensusConfig {
|
|
||||||
config := DefaultConsensusConfig()
|
|
||||||
config.TimeoutPropose = 100
|
|
||||||
config.TimeoutProposeDelta = 1
|
|
||||||
config.TimeoutPrevote = 10
|
|
||||||
config.TimeoutPrevoteDelta = 1
|
|
||||||
config.TimeoutPrecommit = 10
|
|
||||||
config.TimeoutPrecommitDelta = 1
|
|
||||||
config.TimeoutCommit = 10
|
|
||||||
config.SkipTimeoutCommit = true
|
|
||||||
config.PeerGossipSleepDuration = 5
|
|
||||||
config.PeerQueryMaj23SleepDuration = 250
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
// WalFile returns the full path to the write-ahead log file
|
// WalFile returns the full path to the write-ahead log file
|
||||||
func (c *ConsensusConfig) WalFile() string {
|
func (cfg *ConsensusConfig) WalFile() string {
|
||||||
if c.walFile != "" {
|
if cfg.walFile != "" {
|
||||||
return c.walFile
|
return cfg.walFile
|
||||||
}
|
}
|
||||||
return rootify(c.WalPath, c.RootDir)
|
return rootify(cfg.WalPath, cfg.RootDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetWalFile sets the path to the write-ahead log file
|
// SetWalFile sets the path to the write-ahead log file
|
||||||
func (c *ConsensusConfig) SetWalFile(walFile string) {
|
func (cfg *ConsensusConfig) SetWalFile(walFile string) {
|
||||||
c.walFile = walFile
|
cfg.walFile = walFile
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@@ -37,16 +37,21 @@ func EnsureRoot(rootDir string) {
|
|||||||
|
|
||||||
// Write default config file if missing.
|
// Write default config file if missing.
|
||||||
if !cmn.FileExists(configFilePath) {
|
if !cmn.FileExists(configFilePath) {
|
||||||
writeConfigFile(configFilePath)
|
writeDefaultConfigFile(configFilePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: this func should probably be called by cmd/tendermint/commands/init.go
|
// XXX: this func should probably be called by cmd/tendermint/commands/init.go
|
||||||
// alongside the writing of the genesis.json and priv_validator.json
|
// alongside the writing of the genesis.json and priv_validator.json
|
||||||
func writeConfigFile(configFilePath string) {
|
func writeDefaultConfigFile(configFilePath string) {
|
||||||
|
WriteConfigFile(configFilePath, DefaultConfig())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteConfigFile renders config using the template and writes it to configFilePath.
|
||||||
|
func WriteConfigFile(configFilePath string, config *Config) {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
if err := configTemplate.Execute(&buffer, DefaultConfig()); err != nil {
|
if err := configTemplate.Execute(&buffer, config); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,11 +129,11 @@ unsafe = {{ .RPC.Unsafe }}
|
|||||||
laddr = "{{ .P2P.ListenAddress }}"
|
laddr = "{{ .P2P.ListenAddress }}"
|
||||||
|
|
||||||
# Comma separated list of seed nodes to connect to
|
# Comma separated list of seed nodes to connect to
|
||||||
seeds = ""
|
seeds = "{{ .P2P.Seeds }}"
|
||||||
|
|
||||||
# Comma separated list of nodes to keep persistent connections to
|
# 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
|
# Do not add private peers to this list if you don't want them advertised
|
||||||
persistent_peers = ""
|
persistent_peers = "{{ .P2P.PersistentPeers }}"
|
||||||
|
|
||||||
# Path to address book
|
# Path to address book
|
||||||
addr_book_file = "{{ .P2P.AddrBook }}"
|
addr_book_file = "{{ .P2P.AddrBook }}"
|
||||||
@@ -143,7 +148,7 @@ flush_throttle_timeout = {{ .P2P.FlushThrottleTimeout }}
|
|||||||
max_num_peers = {{ .P2P.MaxNumPeers }}
|
max_num_peers = {{ .P2P.MaxNumPeers }}
|
||||||
|
|
||||||
# Maximum size of a message packet payload, in bytes
|
# Maximum size of a message packet payload, in bytes
|
||||||
max_msg_packet_payload_size = {{ .P2P.MaxMsgPacketPayloadSize }}
|
max_packet_msg_payload_size = {{ .P2P.MaxPacketMsgPayloadSize }}
|
||||||
|
|
||||||
# Rate at which packets can be sent, in bytes/second
|
# Rate at which packets can be sent, in bytes/second
|
||||||
send_rate = {{ .P2P.SendRate }}
|
send_rate = {{ .P2P.SendRate }}
|
||||||
@@ -160,9 +165,6 @@ pex = {{ .P2P.PexReactor }}
|
|||||||
# Does not work if the peer-exchange reactor is disabled.
|
# Does not work if the peer-exchange reactor is disabled.
|
||||||
seed_mode = {{ .P2P.SeedMode }}
|
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)
|
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
||||||
private_peer_ids = "{{ .P2P.PrivatePeerIDs }}"
|
private_peer_ids = "{{ .P2P.PrivatePeerIDs }}"
|
||||||
|
|
||||||
@@ -174,11 +176,16 @@ recheck_empty = {{ .Mempool.RecheckEmpty }}
|
|||||||
broadcast = {{ .Mempool.Broadcast }}
|
broadcast = {{ .Mempool.Broadcast }}
|
||||||
wal_dir = "{{ .Mempool.WalPath }}"
|
wal_dir = "{{ .Mempool.WalPath }}"
|
||||||
|
|
||||||
|
# size of the mempool
|
||||||
|
size = {{ .Mempool.Size }}
|
||||||
|
|
||||||
|
# size of the cache (used to filter transactions we saw earlier)
|
||||||
|
cache_size = {{ .Mempool.CacheSize }}
|
||||||
|
|
||||||
##### consensus configuration options #####
|
##### consensus configuration options #####
|
||||||
[consensus]
|
[consensus]
|
||||||
|
|
||||||
wal_file = "{{ .Consensus.WalPath }}"
|
wal_file = "{{ .Consensus.WalPath }}"
|
||||||
wal_light = {{ .Consensus.WalLight }}
|
|
||||||
|
|
||||||
# All timeouts are in milliseconds
|
# All timeouts are in milliseconds
|
||||||
timeout_propose = {{ .Consensus.TimeoutPropose }}
|
timeout_propose = {{ .Consensus.TimeoutPropose }}
|
||||||
@@ -262,7 +269,7 @@ func ResetTestRoot(testName string) *Config {
|
|||||||
|
|
||||||
// Write default config file if missing.
|
// Write default config file if missing.
|
||||||
if !cmn.FileExists(configFilePath) {
|
if !cmn.FileExists(configFilePath) {
|
||||||
writeConfigFile(configFilePath)
|
writeDefaultConfigFile(configFilePath)
|
||||||
}
|
}
|
||||||
if !cmn.FileExists(genesisFilePath) {
|
if !cmn.FileExists(genesisFilePath) {
|
||||||
cmn.MustWriteFile(genesisFilePath, []byte(testGenesis), 0644)
|
cmn.MustWriteFile(genesisFilePath, []byte(testGenesis), 0644)
|
||||||
@@ -280,8 +287,8 @@ var testGenesis = `{
|
|||||||
"validators": [
|
"validators": [
|
||||||
{
|
{
|
||||||
"pub_key": {
|
"pub_key": {
|
||||||
"type": "ed25519",
|
"type": "AC26791624DE60",
|
||||||
"data":"3B3069C422E19688B45CBFAE7BB009FC0FA1B1EA86593519318B7214853803C8"
|
"value":"AT/+aaL1eB0477Mud9JMm8Sh8BIvOYlPGC9KkIUmFaE="
|
||||||
},
|
},
|
||||||
"power": 10,
|
"power": 10,
|
||||||
"name": ""
|
"name": ""
|
||||||
@@ -291,14 +298,14 @@ var testGenesis = `{
|
|||||||
}`
|
}`
|
||||||
|
|
||||||
var testPrivValidator = `{
|
var testPrivValidator = `{
|
||||||
"address": "D028C9981F7A87F3093672BF0D5B0E2A1B3ED456",
|
"address": "849CB2C877F87A20925F35D00AE6688342D25B47",
|
||||||
"pub_key": {
|
"pub_key": {
|
||||||
"type": "ed25519",
|
"type": "AC26791624DE60",
|
||||||
"data": "3B3069C422E19688B45CBFAE7BB009FC0FA1B1EA86593519318B7214853803C8"
|
"value": "AT/+aaL1eB0477Mud9JMm8Sh8BIvOYlPGC9KkIUmFaE="
|
||||||
},
|
},
|
||||||
"priv_key": {
|
"priv_key": {
|
||||||
"type": "ed25519",
|
"type": "954568A3288910",
|
||||||
"data": "27F82582AEFAE7AB151CFB01C48BB6C1A0DA78F9BDDA979A9F70A84D074EB07D3B3069C422E19688B45CBFAE7BB009FC0FA1B1EA86593519318B7214853803C8"
|
"value": "EVkqJO/jIXp3rkASXfh9YnyToYXRXhBr6g9cQVxPFnQBP/5povV4HTjvsy530kybxKHwEi85iU8YL0qQhSYVoQ=="
|
||||||
},
|
},
|
||||||
"last_height": 0,
|
"last_height": 0,
|
||||||
"last_round": 0,
|
"last_round": 0,
|
||||||
|
@@ -1,18 +1 @@
|
|||||||
# The core consensus algorithm.
|
See the [consensus spec](https://github.com/tendermint/tendermint/tree/master/docs/spec/consensus) and the [reactor consensus spec](https://github.com/tendermint/tendermint/tree/master/docs/spec/reactors/consensus) for more information.
|
||||||
|
|
||||||
* state.go - The state machine as detailed in the whitepaper
|
|
||||||
* reactor.go - A reactor that connects the state machine to the gossip network
|
|
||||||
|
|
||||||
# Go-routine summary
|
|
||||||
|
|
||||||
The reactor runs 2 go-routines for each added peer: gossipDataRoutine and gossipVotesRoutine.
|
|
||||||
|
|
||||||
The consensus state runs two persistent go-routines: timeoutRoutine and receiveRoutine.
|
|
||||||
Go-routines are also started to trigger timeouts and to avoid blocking when the internalMsgQueue is really backed up.
|
|
||||||
|
|
||||||
# Replay/WAL
|
|
||||||
|
|
||||||
A write-ahead log is used to record all messages processed by the receiveRoutine,
|
|
||||||
which amounts to all inputs to the consensus state machine:
|
|
||||||
messages from peers, messages from ourselves, and timeouts.
|
|
||||||
They can be played back deterministically at startup or using the replay console.
|
|
||||||
|
@@ -7,7 +7,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
crypto "github.com/tendermint/go-crypto"
|
|
||||||
"github.com/tendermint/tendermint/p2p"
|
"github.com/tendermint/tendermint/p2p"
|
||||||
"github.com/tendermint/tendermint/types"
|
"github.com/tendermint/tendermint/types"
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
@@ -28,7 +27,7 @@ func init() {
|
|||||||
// Heal partition and ensure A sees the commit
|
// Heal partition and ensure A sees the commit
|
||||||
func TestByzantine(t *testing.T) {
|
func TestByzantine(t *testing.T) {
|
||||||
N := 4
|
N := 4
|
||||||
logger := consensusLogger()
|
logger := consensusLogger().With("test", "byzantine")
|
||||||
css := randConsensusNet(N, "consensus_byzantine_test", newMockTickerFunc(false), newCounter)
|
css := randConsensusNet(N, "consensus_byzantine_test", newMockTickerFunc(false), newCounter)
|
||||||
|
|
||||||
// give the byzantine validator a normal ticker
|
// give the byzantine validator a normal ticker
|
||||||
@@ -48,7 +47,9 @@ func TestByzantine(t *testing.T) {
|
|||||||
for i := 0; i < N; i++ {
|
for i := 0; i < N; i++ {
|
||||||
// make first val byzantine
|
// make first val byzantine
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
css[i].privValidator = NewByzantinePrivValidator(css[i].privValidator)
|
// NOTE: Now, test validators are MockPV, which by default doesn't
|
||||||
|
// do any safety checks.
|
||||||
|
css[i].privValidator.(*types.MockPV).DisableChecks()
|
||||||
css[i].decideProposal = func(j int) func(int64, int) {
|
css[i].decideProposal = func(j int) func(int64, int) {
|
||||||
return func(height int64, round int) {
|
return func(height int64, round int) {
|
||||||
byzantineDecideProposalFunc(t, height, round, css[j], switches[j])
|
byzantineDecideProposalFunc(t, height, round, css[j], switches[j])
|
||||||
@@ -57,14 +58,11 @@ func TestByzantine(t *testing.T) {
|
|||||||
css[i].doPrevote = func(height int64, round int) {}
|
css[i].doPrevote = func(height int64, round int) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
eventBus := types.NewEventBus()
|
eventBus := css[i].eventBus
|
||||||
eventBus.SetLogger(logger.With("module", "events", "validator", i))
|
eventBus.SetLogger(logger.With("module", "events", "validator", i))
|
||||||
err := eventBus.Start()
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer eventBus.Stop()
|
|
||||||
|
|
||||||
eventChans[i] = make(chan interface{}, 1)
|
eventChans[i] = make(chan interface{}, 1)
|
||||||
err = eventBus.Subscribe(context.Background(), testSubscriber, types.EventQueryNewBlock, eventChans[i])
|
err := eventBus.Subscribe(context.Background(), testSubscriber, types.EventQueryNewBlock, eventChans[i])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
conR := NewConsensusReactor(css[i], true) // so we dont start the consensus states
|
conR := NewConsensusReactor(css[i], true) // so we dont start the consensus states
|
||||||
@@ -104,15 +102,18 @@ func TestByzantine(t *testing.T) {
|
|||||||
p2p.Connect2Switches(sws, i, j)
|
p2p.Connect2Switches(sws, i, j)
|
||||||
})
|
})
|
||||||
|
|
||||||
// start the state machines
|
// start the non-byz state machines.
|
||||||
byzR := reactors[0].(*ByzantineReactor)
|
// note these must be started before the byz
|
||||||
s := byzR.reactor.conS.GetState()
|
|
||||||
byzR.reactor.SwitchToConsensus(s, 0)
|
|
||||||
for i := 1; i < N; i++ {
|
for i := 1; i < N; i++ {
|
||||||
cr := reactors[i].(*ConsensusReactor)
|
cr := reactors[i].(*ConsensusReactor)
|
||||||
cr.SwitchToConsensus(cr.conS.GetState(), 0)
|
cr.SwitchToConsensus(cr.conS.GetState(), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// start the byzantine state machine
|
||||||
|
byzR := reactors[0].(*ByzantineReactor)
|
||||||
|
s := byzR.reactor.conS.GetState()
|
||||||
|
byzR.reactor.SwitchToConsensus(s, 0)
|
||||||
|
|
||||||
// byz proposer sends one block to peers[0]
|
// byz proposer sends one block to peers[0]
|
||||||
// and the other block to peers[1] and peers[2].
|
// and the other block to peers[1] and peers[2].
|
||||||
// note peers and switches order don't match.
|
// note peers and switches order don't match.
|
||||||
@@ -203,7 +204,7 @@ func byzantineDecideProposalFunc(t *testing.T, height int64, round int, cs *Cons
|
|||||||
func sendProposalAndParts(height int64, round int, cs *ConsensusState, peer p2p.Peer, proposal *types.Proposal, blockHash []byte, parts *types.PartSet) {
|
func sendProposalAndParts(height int64, round int, cs *ConsensusState, peer p2p.Peer, proposal *types.Proposal, blockHash []byte, parts *types.PartSet) {
|
||||||
// proposal
|
// proposal
|
||||||
msg := &ProposalMessage{Proposal: proposal}
|
msg := &ProposalMessage{Proposal: proposal}
|
||||||
peer.Send(DataChannel, struct{ ConsensusMessage }{msg})
|
peer.Send(DataChannel, cdc.MustMarshalBinaryBare(msg))
|
||||||
|
|
||||||
// parts
|
// parts
|
||||||
for i := 0; i < parts.Total(); i++ {
|
for i := 0; i < parts.Total(); i++ {
|
||||||
@@ -213,7 +214,7 @@ func sendProposalAndParts(height int64, round int, cs *ConsensusState, peer p2p.
|
|||||||
Round: round, // This tells peer that this part applies to us.
|
Round: round, // This tells peer that this part applies to us.
|
||||||
Part: part,
|
Part: part,
|
||||||
}
|
}
|
||||||
peer.Send(DataChannel, struct{ ConsensusMessage }{msg})
|
peer.Send(DataChannel, cdc.MustMarshalBinaryBare(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
// votes
|
// votes
|
||||||
@@ -222,8 +223,8 @@ func sendProposalAndParts(height int64, round int, cs *ConsensusState, peer p2p.
|
|||||||
precommit, _ := cs.signVote(types.VoteTypePrecommit, blockHash, parts.Header())
|
precommit, _ := cs.signVote(types.VoteTypePrecommit, blockHash, parts.Header())
|
||||||
cs.mtx.Unlock()
|
cs.mtx.Unlock()
|
||||||
|
|
||||||
peer.Send(VoteChannel, struct{ ConsensusMessage }{&VoteMessage{prevote}})
|
peer.Send(VoteChannel, cdc.MustMarshalBinaryBare(&VoteMessage{prevote}))
|
||||||
peer.Send(VoteChannel, struct{ ConsensusMessage }{&VoteMessage{precommit}})
|
peer.Send(VoteChannel, cdc.MustMarshalBinaryBare(&VoteMessage{precommit}))
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------
|
//----------------------------------------
|
||||||
@@ -264,47 +265,3 @@ func (br *ByzantineReactor) RemovePeer(peer p2p.Peer, reason interface{}) {
|
|||||||
func (br *ByzantineReactor) Receive(chID byte, peer p2p.Peer, msgBytes []byte) {
|
func (br *ByzantineReactor) Receive(chID byte, peer p2p.Peer, msgBytes []byte) {
|
||||||
br.reactor.Receive(chID, peer, msgBytes)
|
br.reactor.Receive(chID, peer, msgBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------
|
|
||||||
// byzantine privValidator
|
|
||||||
|
|
||||||
type ByzantinePrivValidator struct {
|
|
||||||
types.Signer
|
|
||||||
|
|
||||||
pv types.PrivValidator
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a priv validator that will sign anything
|
|
||||||
func NewByzantinePrivValidator(pv types.PrivValidator) *ByzantinePrivValidator {
|
|
||||||
return &ByzantinePrivValidator{
|
|
||||||
Signer: pv.(*types.PrivValidatorFS).Signer,
|
|
||||||
pv: pv,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (privVal *ByzantinePrivValidator) GetAddress() types.Address {
|
|
||||||
return privVal.pv.GetAddress()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (privVal *ByzantinePrivValidator) GetPubKey() crypto.PubKey {
|
|
||||||
return privVal.pv.GetPubKey()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (privVal *ByzantinePrivValidator) SignVote(chainID string, vote *types.Vote) (err error) {
|
|
||||||
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(proposal.SignBytes(chainID))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (privVal *ByzantinePrivValidator) SignHeartbeat(chainID string, heartbeat *types.Heartbeat) (err error) {
|
|
||||||
heartbeat.Signature, _ = privVal.Sign(heartbeat.SignBytes(chainID))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (privVal *ByzantinePrivValidator) String() string {
|
|
||||||
return cmn.Fmt("PrivValidator{%X}", privVal.GetAddress())
|
|
||||||
}
|
|
||||||
|
@@ -19,6 +19,7 @@ import (
|
|||||||
cstypes "github.com/tendermint/tendermint/consensus/types"
|
cstypes "github.com/tendermint/tendermint/consensus/types"
|
||||||
mempl "github.com/tendermint/tendermint/mempool"
|
mempl "github.com/tendermint/tendermint/mempool"
|
||||||
"github.com/tendermint/tendermint/p2p"
|
"github.com/tendermint/tendermint/p2p"
|
||||||
|
"github.com/tendermint/tendermint/privval"
|
||||||
sm "github.com/tendermint/tendermint/state"
|
sm "github.com/tendermint/tendermint/state"
|
||||||
"github.com/tendermint/tendermint/types"
|
"github.com/tendermint/tendermint/types"
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
@@ -101,13 +102,13 @@ func signVotes(voteType byte, hash []byte, header types.PartSetHeader, vss ...*v
|
|||||||
|
|
||||||
func incrementHeight(vss ...*validatorStub) {
|
func incrementHeight(vss ...*validatorStub) {
|
||||||
for _, vs := range vss {
|
for _, vs := range vss {
|
||||||
vs.Height += 1
|
vs.Height++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func incrementRound(vss ...*validatorStub) {
|
func incrementRound(vss ...*validatorStub) {
|
||||||
for _, vs := range vss {
|
for _, vs := range vss {
|
||||||
vs.Round += 1
|
vs.Round++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +223,7 @@ func subscribeToVoter(cs *ConsensusState, addr []byte) chan interface{} {
|
|||||||
voteCh := make(chan interface{})
|
voteCh := make(chan interface{})
|
||||||
go func() {
|
go func() {
|
||||||
for v := range voteCh0 {
|
for v := range voteCh0 {
|
||||||
vote := v.(types.TMEventData).Unwrap().(types.EventDataVote)
|
vote := v.(types.EventDataVote)
|
||||||
// we only fire for our own votes
|
// we only fire for our own votes
|
||||||
if bytes.Equal(addr, vote.Vote.ValidatorAddress) {
|
if bytes.Equal(addr, vote.Vote.ValidatorAddress) {
|
||||||
voteCh <- v
|
voteCh <- v
|
||||||
@@ -261,9 +262,9 @@ func newConsensusStateWithConfigAndBlockStore(thisConfig *cfg.Config, state sm.S
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mock the evidence pool
|
// mock the evidence pool
|
||||||
evpool := types.MockEvidencePool{}
|
evpool := sm.MockEvidencePool{}
|
||||||
|
|
||||||
// Make ConsensusReactor
|
// Make ConsensusState
|
||||||
stateDB := dbm.NewMemDB()
|
stateDB := dbm.NewMemDB()
|
||||||
blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyAppConnCon, mempool, evpool)
|
blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyAppConnCon, mempool, evpool)
|
||||||
cs := NewConsensusState(thisConfig.Consensus, state, blockExec, blockStore, mempool, evpool)
|
cs := NewConsensusState(thisConfig.Consensus, state, blockExec, blockStore, mempool, evpool)
|
||||||
@@ -277,10 +278,10 @@ func newConsensusStateWithConfigAndBlockStore(thisConfig *cfg.Config, state sm.S
|
|||||||
return cs
|
return cs
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadPrivValidator(config *cfg.Config) *types.PrivValidatorFS {
|
func loadPrivValidator(config *cfg.Config) *privval.FilePV {
|
||||||
privValidatorFile := config.PrivValidatorFile()
|
privValidatorFile := config.PrivValidatorFile()
|
||||||
ensureDir(path.Dir(privValidatorFile), 0700)
|
ensureDir(path.Dir(privValidatorFile), 0700)
|
||||||
privValidator := types.LoadOrGenPrivValidatorFS(privValidatorFile)
|
privValidator := privval.LoadOrGenFilePV(privValidatorFile)
|
||||||
privValidator.Reset()
|
privValidator.Reset()
|
||||||
return privValidator
|
return privValidator
|
||||||
}
|
}
|
||||||
@@ -378,7 +379,7 @@ func randConsensusNetWithPeers(nValidators, nPeers int, testName string, tickerF
|
|||||||
privVal = privVals[i]
|
privVal = privVals[i]
|
||||||
} else {
|
} else {
|
||||||
_, tempFilePath := cmn.Tempfile("priv_validator_")
|
_, tempFilePath := cmn.Tempfile("priv_validator_")
|
||||||
privVal = types.GenPrivValidatorFS(tempFilePath)
|
privVal = privval.GenFilePV(tempFilePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
app := appFunc()
|
app := appFunc()
|
||||||
@@ -394,7 +395,7 @@ func randConsensusNetWithPeers(nValidators, nPeers int, testName string, tickerF
|
|||||||
|
|
||||||
func getSwitchIndex(switches []*p2p.Switch, peer p2p.Peer) int {
|
func getSwitchIndex(switches []*p2p.Switch, peer p2p.Peer) int {
|
||||||
for i, s := range switches {
|
for i, s := range switches {
|
||||||
if bytes.Equal(peer.NodeInfo().PubKey.Address(), s.NodeInfo().PubKey.Address()) {
|
if peer.NodeInfo().ID == s.NodeInfo().ID {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -405,9 +406,9 @@ func getSwitchIndex(switches []*p2p.Switch, peer p2p.Peer) int {
|
|||||||
//-------------------------------------------------------------------------------
|
//-------------------------------------------------------------------------------
|
||||||
// genesis
|
// genesis
|
||||||
|
|
||||||
func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.GenesisDoc, []*types.PrivValidatorFS) {
|
func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.GenesisDoc, []types.PrivValidator) {
|
||||||
validators := make([]types.GenesisValidator, numValidators)
|
validators := make([]types.GenesisValidator, numValidators)
|
||||||
privValidators := make([]*types.PrivValidatorFS, numValidators)
|
privValidators := make([]types.PrivValidator, numValidators)
|
||||||
for i := 0; i < numValidators; i++ {
|
for i := 0; i < numValidators; i++ {
|
||||||
val, privVal := types.RandValidator(randPower, minPower)
|
val, privVal := types.RandValidator(randPower, minPower)
|
||||||
validators[i] = types.GenesisValidator{
|
validators[i] = types.GenesisValidator{
|
||||||
@@ -425,7 +426,7 @@ func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.G
|
|||||||
}, privValidators
|
}, privValidators
|
||||||
}
|
}
|
||||||
|
|
||||||
func randGenesisState(numValidators int, randPower bool, minPower int64) (sm.State, []*types.PrivValidatorFS) {
|
func randGenesisState(numValidators int, randPower bool, minPower int64) (sm.State, []types.PrivValidator) {
|
||||||
genDoc, privValidators := randGenesisDoc(numValidators, randPower, minPower)
|
genDoc, privValidators := randGenesisDoc(numValidators, randPower, minPower)
|
||||||
s0, _ := sm.MakeGenesisState(genDoc)
|
s0, _ := sm.MakeGenesisState(genDoc)
|
||||||
db := dbm.NewMemDB()
|
db := dbm.NewMemDB()
|
||||||
|
@@ -108,7 +108,7 @@ func TestMempoolTxConcurrentWithCommit(t *testing.T) {
|
|||||||
ticker := time.NewTicker(time.Second * 30)
|
ticker := time.NewTicker(time.Second * 30)
|
||||||
select {
|
select {
|
||||||
case b := <-newBlockCh:
|
case b := <-newBlockCh:
|
||||||
evt := b.(types.TMEventData).Unwrap().(types.EventDataNewBlock)
|
evt := b.(types.EventDataNewBlock)
|
||||||
nTxs += int(evt.Block.Header.NumTxs)
|
nTxs += int(evt.Block.Header.NumTxs)
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
panic("Timed out waiting to commit blocks with transactions")
|
panic("Timed out waiting to commit blocks with transactions")
|
||||||
@@ -200,7 +200,7 @@ func (app *CounterApplication) DeliverTx(tx []byte) abci.ResponseDeliverTx {
|
|||||||
Code: code.CodeTypeBadNonce,
|
Code: code.CodeTypeBadNonce,
|
||||||
Log: fmt.Sprintf("Invalid nonce. Expected %v, got %v", app.txCount, txValue)}
|
Log: fmt.Sprintf("Invalid nonce. Expected %v, got %v", app.txCount, txValue)}
|
||||||
}
|
}
|
||||||
app.txCount += 1
|
app.txCount++
|
||||||
return abci.ResponseDeliverTx{Code: code.CodeTypeOK}
|
return abci.ResponseDeliverTx{Code: code.CodeTypeOK}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +211,7 @@ func (app *CounterApplication) CheckTx(tx []byte) abci.ResponseCheckTx {
|
|||||||
Code: code.CodeTypeBadNonce,
|
Code: code.CodeTypeBadNonce,
|
||||||
Log: fmt.Sprintf("Invalid nonce. Expected %v, got %v", app.mempoolTxCount, txValue)}
|
Log: fmt.Sprintf("Invalid nonce. Expected %v, got %v", app.mempoolTxCount, txValue)}
|
||||||
}
|
}
|
||||||
app.mempoolTxCount += 1
|
app.mempoolTxCount++
|
||||||
return abci.ResponseCheckTx{Code: code.CodeTypeOK}
|
return abci.ResponseCheckTx{Code: code.CodeTypeOK}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,9 +225,8 @@ func (app *CounterApplication) Commit() abci.ResponseCommit {
|
|||||||
app.mempoolTxCount = app.txCount
|
app.mempoolTxCount = app.txCount
|
||||||
if app.txCount == 0 {
|
if app.txCount == 0 {
|
||||||
return abci.ResponseCommit{}
|
return abci.ResponseCommit{}
|
||||||
} else {
|
|
||||||
hash := make([]byte, 8)
|
|
||||||
binary.BigEndian.PutUint64(hash, uint64(app.txCount))
|
|
||||||
return abci.ResponseCommit{Data: hash}
|
|
||||||
}
|
}
|
||||||
|
hash := make([]byte, 8)
|
||||||
|
binary.BigEndian.PutUint64(hash, uint64(app.txCount))
|
||||||
|
return abci.ResponseCommit{Data: hash}
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
package consensus
|
package consensus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -10,11 +8,12 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
wire "github.com/tendermint/go-wire"
|
amino "github.com/tendermint/go-amino"
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
"github.com/tendermint/tmlibs/log"
|
"github.com/tendermint/tmlibs/log"
|
||||||
|
|
||||||
cstypes "github.com/tendermint/tendermint/consensus/types"
|
cstypes "github.com/tendermint/tendermint/consensus/types"
|
||||||
|
tmevents "github.com/tendermint/tendermint/libs/events"
|
||||||
"github.com/tendermint/tendermint/p2p"
|
"github.com/tendermint/tendermint/p2p"
|
||||||
sm "github.com/tendermint/tendermint/state"
|
sm "github.com/tendermint/tendermint/state"
|
||||||
"github.com/tendermint/tendermint/types"
|
"github.com/tendermint/tendermint/types"
|
||||||
@@ -26,7 +25,7 @@ const (
|
|||||||
VoteChannel = byte(0x22)
|
VoteChannel = byte(0x22)
|
||||||
VoteSetBitsChannel = byte(0x23)
|
VoteSetBitsChannel = byte(0x23)
|
||||||
|
|
||||||
maxConsensusMessageSize = 1048576 // 1MB; NOTE/TODO: keep in sync with types.PartSet sizes.
|
maxMsgSize = 1048576 // 1MB; NOTE/TODO: keep in sync with types.PartSet sizes.
|
||||||
|
|
||||||
blocksToContributeToBecomeGoodPeer = 10000
|
blocksToContributeToBecomeGoodPeer = 10000
|
||||||
)
|
)
|
||||||
@@ -44,7 +43,8 @@ type ConsensusReactor struct {
|
|||||||
eventBus *types.EventBus
|
eventBus *types.EventBus
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConsensusReactor returns a new ConsensusReactor with the given consensusState.
|
// NewConsensusReactor returns a new ConsensusReactor with the given
|
||||||
|
// consensusState.
|
||||||
func NewConsensusReactor(consensusState *ConsensusState, fastSync bool) *ConsensusReactor {
|
func NewConsensusReactor(consensusState *ConsensusState, fastSync bool) *ConsensusReactor {
|
||||||
conR := &ConsensusReactor{
|
conR := &ConsensusReactor{
|
||||||
conS: consensusState,
|
conS: consensusState,
|
||||||
@@ -54,17 +54,15 @@ func NewConsensusReactor(consensusState *ConsensusState, fastSync bool) *Consens
|
|||||||
return conR
|
return conR
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnStart implements BaseService.
|
// OnStart implements BaseService by subscribing to events, which later will be
|
||||||
|
// broadcasted to other peers and starting state if we're not in fast sync.
|
||||||
func (conR *ConsensusReactor) OnStart() error {
|
func (conR *ConsensusReactor) OnStart() error {
|
||||||
conR.Logger.Info("ConsensusReactor ", "fastSync", conR.FastSync())
|
conR.Logger.Info("ConsensusReactor ", "fastSync", conR.FastSync())
|
||||||
if err := conR.BaseReactor.OnStart(); err != nil {
|
if err := conR.BaseReactor.OnStart(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err := conR.startBroadcastRoutine()
|
conR.subscribeToBroadcastEvents()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !conR.FastSync() {
|
if !conR.FastSync() {
|
||||||
err := conR.conS.Start()
|
err := conR.conS.Start()
|
||||||
@@ -76,9 +74,11 @@ func (conR *ConsensusReactor) OnStart() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnStop implements BaseService
|
// OnStop implements BaseService by unsubscribing from events and stopping
|
||||||
|
// state.
|
||||||
func (conR *ConsensusReactor) OnStop() {
|
func (conR *ConsensusReactor) OnStop() {
|
||||||
conR.BaseReactor.OnStop()
|
conR.BaseReactor.OnStop()
|
||||||
|
conR.unsubscribeFromBroadcastEvents()
|
||||||
conR.conS.Stop()
|
conR.conS.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +102,7 @@ func (conR *ConsensusReactor) SwitchToConsensus(state sm.State, blocksSynced int
|
|||||||
err := conR.conS.Start()
|
err := conR.conS.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conR.Logger.Error("Error starting conS", "err", err)
|
conR.Logger.Error("Error starting conS", "err", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,27 +111,31 @@ func (conR *ConsensusReactor) GetChannels() []*p2p.ChannelDescriptor {
|
|||||||
// TODO optimize
|
// TODO optimize
|
||||||
return []*p2p.ChannelDescriptor{
|
return []*p2p.ChannelDescriptor{
|
||||||
{
|
{
|
||||||
ID: StateChannel,
|
ID: StateChannel,
|
||||||
Priority: 5,
|
Priority: 5,
|
||||||
SendQueueCapacity: 100,
|
SendQueueCapacity: 100,
|
||||||
|
RecvMessageCapacity: maxMsgSize,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: DataChannel, // maybe split between gossiping current block and catchup stuff
|
ID: DataChannel, // maybe split between gossiping current block and catchup stuff
|
||||||
Priority: 10, // once we gossip the whole block there's nothing left to send until next height or round
|
Priority: 10, // once we gossip the whole block there's nothing left to send until next height or round
|
||||||
SendQueueCapacity: 100,
|
SendQueueCapacity: 100,
|
||||||
RecvBufferCapacity: 50 * 4096,
|
RecvBufferCapacity: 50 * 4096,
|
||||||
|
RecvMessageCapacity: maxMsgSize,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: VoteChannel,
|
ID: VoteChannel,
|
||||||
Priority: 5,
|
Priority: 5,
|
||||||
SendQueueCapacity: 100,
|
SendQueueCapacity: 100,
|
||||||
RecvBufferCapacity: 100 * 100,
|
RecvBufferCapacity: 100 * 100,
|
||||||
|
RecvMessageCapacity: maxMsgSize,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: VoteSetBitsChannel,
|
ID: VoteSetBitsChannel,
|
||||||
Priority: 1,
|
Priority: 1,
|
||||||
SendQueueCapacity: 2,
|
SendQueueCapacity: 2,
|
||||||
RecvBufferCapacity: 1024,
|
RecvBufferCapacity: 1024,
|
||||||
|
RecvMessageCapacity: maxMsgSize,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,7 +183,7 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, msg, err := DecodeMessage(msgBytes)
|
msg, err := DecodeMessage(msgBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conR.Logger.Error("Error decoding message", "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes)
|
conR.Logger.Error("Error decoding message", "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes)
|
||||||
conR.Switch.StopPeerForError(src, err)
|
conR.Switch.StopPeerForError(src, err)
|
||||||
@@ -207,7 +212,7 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Peer claims to have a maj23 for some BlockID at H,R,S,
|
// Peer claims to have a maj23 for some BlockID at H,R,S,
|
||||||
err := votes.SetPeerMaj23(msg.Round, msg.Type, ps.Peer.ID(), msg.BlockID)
|
err := votes.SetPeerMaj23(msg.Round, msg.Type, ps.peer.ID(), msg.BlockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conR.Switch.StopPeerForError(src, err)
|
conR.Switch.StopPeerForError(src, err)
|
||||||
return
|
return
|
||||||
@@ -224,13 +229,13 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte)
|
|||||||
conR.Logger.Error("Bad VoteSetBitsMessage field Type")
|
conR.Logger.Error("Bad VoteSetBitsMessage field Type")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
src.TrySend(VoteSetBitsChannel, struct{ ConsensusMessage }{&VoteSetBitsMessage{
|
src.TrySend(VoteSetBitsChannel, cdc.MustMarshalBinaryBare(&VoteSetBitsMessage{
|
||||||
Height: msg.Height,
|
Height: msg.Height,
|
||||||
Round: msg.Round,
|
Round: msg.Round,
|
||||||
Type: msg.Type,
|
Type: msg.Type,
|
||||||
BlockID: msg.BlockID,
|
BlockID: msg.BlockID,
|
||||||
Votes: ourVotes,
|
Votes: ourVotes,
|
||||||
}})
|
}))
|
||||||
case *ProposalHeartbeatMessage:
|
case *ProposalHeartbeatMessage:
|
||||||
hb := msg.Heartbeat
|
hb := msg.Heartbeat
|
||||||
conR.Logger.Debug("Received proposal heartbeat message",
|
conR.Logger.Debug("Received proposal heartbeat message",
|
||||||
@@ -342,77 +347,46 @@ func (conR *ConsensusReactor) FastSync() bool {
|
|||||||
|
|
||||||
//--------------------------------------
|
//--------------------------------------
|
||||||
|
|
||||||
// startBroadcastRoutine subscribes for new round steps, votes and proposal
|
// subscribeToBroadcastEvents subscribes for new round steps, votes and
|
||||||
// heartbeats using the event bus and starts a go routine to broadcasts events
|
// proposal heartbeats using internal pubsub defined on state to broadcast
|
||||||
// to peers upon receiving them.
|
// them to peers upon receiving.
|
||||||
func (conR *ConsensusReactor) startBroadcastRoutine() error {
|
func (conR *ConsensusReactor) subscribeToBroadcastEvents() {
|
||||||
const subscriber = "consensus-reactor"
|
const subscriber = "consensus-reactor"
|
||||||
ctx := context.Background()
|
conR.conS.evsw.AddListenerForEvent(subscriber, types.EventNewRoundStep,
|
||||||
|
func(data tmevents.EventData) {
|
||||||
|
conR.broadcastNewRoundStepMessages(data.(*cstypes.RoundState))
|
||||||
|
})
|
||||||
|
|
||||||
// new round steps
|
conR.conS.evsw.AddListenerForEvent(subscriber, types.EventVote,
|
||||||
stepsCh := make(chan interface{})
|
func(data tmevents.EventData) {
|
||||||
err := conR.eventBus.Subscribe(ctx, subscriber, types.EventQueryNewRoundStep, stepsCh)
|
conR.broadcastHasVoteMessage(data.(*types.Vote))
|
||||||
if err != nil {
|
})
|
||||||
return errors.Wrapf(err, "failed to subscribe %s to %s", subscriber, types.EventQueryNewRoundStep)
|
|
||||||
}
|
|
||||||
|
|
||||||
// votes
|
conR.conS.evsw.AddListenerForEvent(subscriber, types.EventProposalHeartbeat,
|
||||||
votesCh := make(chan interface{})
|
func(data tmevents.EventData) {
|
||||||
err = conR.eventBus.Subscribe(ctx, subscriber, types.EventQueryVote, votesCh)
|
conR.broadcastProposalHeartbeatMessage(data.(*types.Heartbeat))
|
||||||
if err != nil {
|
})
|
||||||
return errors.Wrapf(err, "failed to subscribe %s to %s", subscriber, types.EventQueryVote)
|
|
||||||
}
|
|
||||||
|
|
||||||
// proposal heartbeats
|
|
||||||
heartbeatsCh := make(chan interface{})
|
|
||||||
err = conR.eventBus.Subscribe(ctx, subscriber, types.EventQueryProposalHeartbeat, heartbeatsCh)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to subscribe %s to %s", subscriber, types.EventQueryProposalHeartbeat)
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case data, ok := <-stepsCh:
|
|
||||||
if ok { // a receive from a closed channel returns the zero value immediately
|
|
||||||
edrs := data.(types.TMEventData).Unwrap().(types.EventDataRoundState)
|
|
||||||
conR.broadcastNewRoundStep(edrs.RoundState.(*cstypes.RoundState))
|
|
||||||
}
|
|
||||||
case data, ok := <-votesCh:
|
|
||||||
if ok {
|
|
||||||
edv := data.(types.TMEventData).Unwrap().(types.EventDataVote)
|
|
||||||
conR.broadcastHasVoteMessage(edv.Vote)
|
|
||||||
}
|
|
||||||
case data, ok := <-heartbeatsCh:
|
|
||||||
if ok {
|
|
||||||
edph := data.(types.TMEventData).Unwrap().(types.EventDataProposalHeartbeat)
|
|
||||||
conR.broadcastProposalHeartbeatMessage(edph)
|
|
||||||
}
|
|
||||||
case <-conR.Quit():
|
|
||||||
conR.eventBus.UnsubscribeAll(ctx, subscriber)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conR *ConsensusReactor) broadcastProposalHeartbeatMessage(heartbeat types.EventDataProposalHeartbeat) {
|
func (conR *ConsensusReactor) unsubscribeFromBroadcastEvents() {
|
||||||
hb := heartbeat.Heartbeat
|
const subscriber = "consensus-reactor"
|
||||||
|
conR.conS.evsw.RemoveListener(subscriber)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conR *ConsensusReactor) broadcastProposalHeartbeatMessage(hb *types.Heartbeat) {
|
||||||
conR.Logger.Debug("Broadcasting proposal heartbeat message",
|
conR.Logger.Debug("Broadcasting proposal heartbeat message",
|
||||||
"height", hb.Height, "round", hb.Round, "sequence", hb.Sequence)
|
"height", hb.Height, "round", hb.Round, "sequence", hb.Sequence)
|
||||||
msg := &ProposalHeartbeatMessage{hb}
|
msg := &ProposalHeartbeatMessage{hb}
|
||||||
conR.Switch.Broadcast(StateChannel, struct{ ConsensusMessage }{msg})
|
conR.Switch.Broadcast(StateChannel, cdc.MustMarshalBinaryBare(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conR *ConsensusReactor) broadcastNewRoundStep(rs *cstypes.RoundState) {
|
func (conR *ConsensusReactor) broadcastNewRoundStepMessages(rs *cstypes.RoundState) {
|
||||||
nrsMsg, csMsg := makeRoundStepMessages(rs)
|
nrsMsg, csMsg := makeRoundStepMessages(rs)
|
||||||
if nrsMsg != nil {
|
if nrsMsg != nil {
|
||||||
conR.Switch.Broadcast(StateChannel, struct{ ConsensusMessage }{nrsMsg})
|
conR.Switch.Broadcast(StateChannel, cdc.MustMarshalBinaryBare(nrsMsg))
|
||||||
}
|
}
|
||||||
if csMsg != nil {
|
if csMsg != nil {
|
||||||
conR.Switch.Broadcast(StateChannel, struct{ ConsensusMessage }{csMsg})
|
conR.Switch.Broadcast(StateChannel, cdc.MustMarshalBinaryBare(csMsg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,7 +398,7 @@ func (conR *ConsensusReactor) broadcastHasVoteMessage(vote *types.Vote) {
|
|||||||
Type: vote.Type,
|
Type: vote.Type,
|
||||||
Index: vote.ValidatorIndex,
|
Index: vote.ValidatorIndex,
|
||||||
}
|
}
|
||||||
conR.Switch.Broadcast(StateChannel, struct{ ConsensusMessage }{msg})
|
conR.Switch.Broadcast(StateChannel, cdc.MustMarshalBinaryBare(msg))
|
||||||
/*
|
/*
|
||||||
// TODO: Make this broadcast more selective.
|
// TODO: Make this broadcast more selective.
|
||||||
for _, peer := range conR.Switch.Peers().List() {
|
for _, peer := range conR.Switch.Peers().List() {
|
||||||
@@ -464,10 +438,10 @@ func (conR *ConsensusReactor) sendNewRoundStepMessages(peer p2p.Peer) {
|
|||||||
rs := conR.conS.GetRoundState()
|
rs := conR.conS.GetRoundState()
|
||||||
nrsMsg, csMsg := makeRoundStepMessages(rs)
|
nrsMsg, csMsg := makeRoundStepMessages(rs)
|
||||||
if nrsMsg != nil {
|
if nrsMsg != nil {
|
||||||
peer.Send(StateChannel, struct{ ConsensusMessage }{nrsMsg})
|
peer.Send(StateChannel, cdc.MustMarshalBinaryBare(nrsMsg))
|
||||||
}
|
}
|
||||||
if csMsg != nil {
|
if csMsg != nil {
|
||||||
peer.Send(StateChannel, struct{ ConsensusMessage }{csMsg})
|
peer.Send(StateChannel, cdc.MustMarshalBinaryBare(csMsg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,7 +468,7 @@ OUTER_LOOP:
|
|||||||
Part: part,
|
Part: part,
|
||||||
}
|
}
|
||||||
logger.Debug("Sending block part", "height", prs.Height, "round", prs.Round)
|
logger.Debug("Sending block part", "height", prs.Height, "round", prs.Round)
|
||||||
if peer.Send(DataChannel, struct{ ConsensusMessage }{msg}) {
|
if peer.Send(DataChannel, cdc.MustMarshalBinaryBare(msg)) {
|
||||||
ps.SetHasProposalBlockPart(prs.Height, prs.Round, index)
|
ps.SetHasProposalBlockPart(prs.Height, prs.Round, index)
|
||||||
}
|
}
|
||||||
continue OUTER_LOOP
|
continue OUTER_LOOP
|
||||||
@@ -538,7 +512,7 @@ OUTER_LOOP:
|
|||||||
{
|
{
|
||||||
msg := &ProposalMessage{Proposal: rs.Proposal}
|
msg := &ProposalMessage{Proposal: rs.Proposal}
|
||||||
logger.Debug("Sending proposal", "height", prs.Height, "round", prs.Round)
|
logger.Debug("Sending proposal", "height", prs.Height, "round", prs.Round)
|
||||||
if peer.Send(DataChannel, struct{ ConsensusMessage }{msg}) {
|
if peer.Send(DataChannel, cdc.MustMarshalBinaryBare(msg)) {
|
||||||
ps.SetHasProposal(rs.Proposal)
|
ps.SetHasProposal(rs.Proposal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -553,7 +527,7 @@ OUTER_LOOP:
|
|||||||
ProposalPOL: rs.Votes.Prevotes(rs.Proposal.POLRound).BitArray(),
|
ProposalPOL: rs.Votes.Prevotes(rs.Proposal.POLRound).BitArray(),
|
||||||
}
|
}
|
||||||
logger.Debug("Sending POL", "height", prs.Height, "round", prs.Round)
|
logger.Debug("Sending POL", "height", prs.Height, "round", prs.Round)
|
||||||
peer.Send(DataChannel, struct{ ConsensusMessage }{msg})
|
peer.Send(DataChannel, cdc.MustMarshalBinaryBare(msg))
|
||||||
}
|
}
|
||||||
continue OUTER_LOOP
|
continue OUTER_LOOP
|
||||||
}
|
}
|
||||||
@@ -596,17 +570,15 @@ func (conR *ConsensusReactor) gossipDataForCatchup(logger log.Logger, rs *cstype
|
|||||||
Part: part,
|
Part: part,
|
||||||
}
|
}
|
||||||
logger.Debug("Sending block part for catchup", "round", prs.Round, "index", index)
|
logger.Debug("Sending block part for catchup", "round", prs.Round, "index", index)
|
||||||
if peer.Send(DataChannel, struct{ ConsensusMessage }{msg}) {
|
if peer.Send(DataChannel, cdc.MustMarshalBinaryBare(msg)) {
|
||||||
ps.SetHasProposalBlockPart(prs.Height, prs.Round, index)
|
ps.SetHasProposalBlockPart(prs.Height, prs.Round, index)
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("Sending block part for catchup failed")
|
logger.Debug("Sending block part for catchup failed")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
//logger.Info("No parts to send in catch-up, sleeping")
|
|
||||||
time.Sleep(conR.conS.config.PeerGossipSleep())
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
//logger.Info("No parts to send in catch-up, sleeping")
|
||||||
|
time.Sleep(conR.conS.config.PeerGossipSleep())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conR *ConsensusReactor) gossipVotesRoutine(peer p2p.Peer, ps *PeerState) {
|
func (conR *ConsensusReactor) gossipVotesRoutine(peer p2p.Peer, ps *PeerState) {
|
||||||
@@ -689,20 +661,37 @@ func (conR *ConsensusReactor) gossipVotesForHeight(logger log.Logger, rs *cstype
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If there are POL prevotes to send...
|
||||||
|
if prs.Step <= cstypes.RoundStepPropose && prs.Round != -1 && prs.Round <= rs.Round && prs.ProposalPOLRound != -1 {
|
||||||
|
if polPrevotes := rs.Votes.Prevotes(prs.ProposalPOLRound); polPrevotes != nil {
|
||||||
|
if ps.PickSendVote(polPrevotes) {
|
||||||
|
logger.Debug("Picked rs.Prevotes(prs.ProposalPOLRound) to send",
|
||||||
|
"round", prs.ProposalPOLRound)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// If there are prevotes to send...
|
// If there are prevotes to send...
|
||||||
if prs.Step <= cstypes.RoundStepPrevote && prs.Round != -1 && prs.Round <= rs.Round {
|
if prs.Step <= cstypes.RoundStepPrevoteWait && prs.Round != -1 && prs.Round <= rs.Round {
|
||||||
if ps.PickSendVote(rs.Votes.Prevotes(prs.Round)) {
|
if ps.PickSendVote(rs.Votes.Prevotes(prs.Round)) {
|
||||||
logger.Debug("Picked rs.Prevotes(prs.Round) to send", "round", prs.Round)
|
logger.Debug("Picked rs.Prevotes(prs.Round) to send", "round", prs.Round)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If there are precommits to send...
|
// If there are precommits to send...
|
||||||
if prs.Step <= cstypes.RoundStepPrecommit && prs.Round != -1 && prs.Round <= rs.Round {
|
if prs.Step <= cstypes.RoundStepPrecommitWait && prs.Round != -1 && prs.Round <= rs.Round {
|
||||||
if ps.PickSendVote(rs.Votes.Precommits(prs.Round)) {
|
if ps.PickSendVote(rs.Votes.Precommits(prs.Round)) {
|
||||||
logger.Debug("Picked rs.Precommits(prs.Round) to send", "round", prs.Round)
|
logger.Debug("Picked rs.Precommits(prs.Round) to send", "round", prs.Round)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If there are prevotes to send...Needed because of validBlock mechanism
|
||||||
|
if prs.Round != -1 && prs.Round <= rs.Round {
|
||||||
|
if ps.PickSendVote(rs.Votes.Prevotes(prs.Round)) {
|
||||||
|
logger.Debug("Picked rs.Prevotes(prs.Round) to send", "round", prs.Round)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
// If there are POLPrevotes to send...
|
// If there are POLPrevotes to send...
|
||||||
if prs.ProposalPOLRound != -1 {
|
if prs.ProposalPOLRound != -1 {
|
||||||
if polPrevotes := rs.Votes.Prevotes(prs.ProposalPOLRound); polPrevotes != nil {
|
if polPrevotes := rs.Votes.Prevotes(prs.ProposalPOLRound); polPrevotes != nil {
|
||||||
@@ -713,6 +702,7 @@ func (conR *ConsensusReactor) gossipVotesForHeight(logger log.Logger, rs *cstype
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -735,12 +725,12 @@ OUTER_LOOP:
|
|||||||
prs := ps.GetRoundState()
|
prs := ps.GetRoundState()
|
||||||
if rs.Height == prs.Height {
|
if rs.Height == prs.Height {
|
||||||
if maj23, ok := rs.Votes.Prevotes(prs.Round).TwoThirdsMajority(); ok {
|
if maj23, ok := rs.Votes.Prevotes(prs.Round).TwoThirdsMajority(); ok {
|
||||||
peer.TrySend(StateChannel, struct{ ConsensusMessage }{&VoteSetMaj23Message{
|
peer.TrySend(StateChannel, cdc.MustMarshalBinaryBare(&VoteSetMaj23Message{
|
||||||
Height: prs.Height,
|
Height: prs.Height,
|
||||||
Round: prs.Round,
|
Round: prs.Round,
|
||||||
Type: types.VoteTypePrevote,
|
Type: types.VoteTypePrevote,
|
||||||
BlockID: maj23,
|
BlockID: maj23,
|
||||||
}})
|
}))
|
||||||
time.Sleep(conR.conS.config.PeerQueryMaj23Sleep())
|
time.Sleep(conR.conS.config.PeerQueryMaj23Sleep())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -752,12 +742,12 @@ OUTER_LOOP:
|
|||||||
prs := ps.GetRoundState()
|
prs := ps.GetRoundState()
|
||||||
if rs.Height == prs.Height {
|
if rs.Height == prs.Height {
|
||||||
if maj23, ok := rs.Votes.Precommits(prs.Round).TwoThirdsMajority(); ok {
|
if maj23, ok := rs.Votes.Precommits(prs.Round).TwoThirdsMajority(); ok {
|
||||||
peer.TrySend(StateChannel, struct{ ConsensusMessage }{&VoteSetMaj23Message{
|
peer.TrySend(StateChannel, cdc.MustMarshalBinaryBare(&VoteSetMaj23Message{
|
||||||
Height: prs.Height,
|
Height: prs.Height,
|
||||||
Round: prs.Round,
|
Round: prs.Round,
|
||||||
Type: types.VoteTypePrecommit,
|
Type: types.VoteTypePrecommit,
|
||||||
BlockID: maj23,
|
BlockID: maj23,
|
||||||
}})
|
}))
|
||||||
time.Sleep(conR.conS.config.PeerQueryMaj23Sleep())
|
time.Sleep(conR.conS.config.PeerQueryMaj23Sleep())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -769,12 +759,12 @@ OUTER_LOOP:
|
|||||||
prs := ps.GetRoundState()
|
prs := ps.GetRoundState()
|
||||||
if rs.Height == prs.Height && prs.ProposalPOLRound >= 0 {
|
if rs.Height == prs.Height && prs.ProposalPOLRound >= 0 {
|
||||||
if maj23, ok := rs.Votes.Prevotes(prs.ProposalPOLRound).TwoThirdsMajority(); ok {
|
if maj23, ok := rs.Votes.Prevotes(prs.ProposalPOLRound).TwoThirdsMajority(); ok {
|
||||||
peer.TrySend(StateChannel, struct{ ConsensusMessage }{&VoteSetMaj23Message{
|
peer.TrySend(StateChannel, cdc.MustMarshalBinaryBare(&VoteSetMaj23Message{
|
||||||
Height: prs.Height,
|
Height: prs.Height,
|
||||||
Round: prs.ProposalPOLRound,
|
Round: prs.ProposalPOLRound,
|
||||||
Type: types.VoteTypePrevote,
|
Type: types.VoteTypePrevote,
|
||||||
BlockID: maj23,
|
BlockID: maj23,
|
||||||
}})
|
}))
|
||||||
time.Sleep(conR.conS.config.PeerQueryMaj23Sleep())
|
time.Sleep(conR.conS.config.PeerQueryMaj23Sleep())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -788,12 +778,12 @@ OUTER_LOOP:
|
|||||||
prs := ps.GetRoundState()
|
prs := ps.GetRoundState()
|
||||||
if prs.CatchupCommitRound != -1 && 0 < prs.Height && prs.Height <= conR.conS.blockStore.Height() {
|
if prs.CatchupCommitRound != -1 && 0 < prs.Height && prs.Height <= conR.conS.blockStore.Height() {
|
||||||
commit := conR.conS.LoadCommit(prs.Height)
|
commit := conR.conS.LoadCommit(prs.Height)
|
||||||
peer.TrySend(StateChannel, struct{ ConsensusMessage }{&VoteSetMaj23Message{
|
peer.TrySend(StateChannel, cdc.MustMarshalBinaryBare(&VoteSetMaj23Message{
|
||||||
Height: prs.Height,
|
Height: prs.Height,
|
||||||
Round: commit.Round(),
|
Round: commit.Round(),
|
||||||
Type: types.VoteTypePrecommit,
|
Type: types.VoteTypePrecommit,
|
||||||
BlockID: commit.BlockID,
|
BlockID: commit.BlockID,
|
||||||
}})
|
}))
|
||||||
time.Sleep(conR.conS.config.PeerQueryMaj23Sleep())
|
time.Sleep(conR.conS.config.PeerQueryMaj23Sleep())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -831,67 +821,78 @@ var (
|
|||||||
ErrPeerStateInvalidStartTime = errors.New("Error peer state invalid startTime")
|
ErrPeerStateInvalidStartTime = errors.New("Error peer state invalid startTime")
|
||||||
)
|
)
|
||||||
|
|
||||||
// PeerState contains the known state of a peer, including its connection
|
// PeerState contains the known state of a peer, including its connection and
|
||||||
// and threadsafe access to its PeerRoundState.
|
// threadsafe access to its PeerRoundState.
|
||||||
|
// NOTE: THIS GETS DUMPED WITH rpc/core/consensus.go.
|
||||||
|
// Be mindful of what you Expose.
|
||||||
type PeerState struct {
|
type PeerState struct {
|
||||||
Peer p2p.Peer
|
peer p2p.Peer
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
|
|
||||||
mtx sync.Mutex
|
mtx sync.Mutex `json:"-"` // NOTE: Modify below using setters, never directly.
|
||||||
cstypes.PeerRoundState
|
PRS cstypes.PeerRoundState `json:"round_state"` // Exposed.
|
||||||
|
Stats *peerStateStats `json:"stats"` // Exposed.
|
||||||
stats *peerStateStats
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// peerStateStats holds internal statistics for a peer.
|
// peerStateStats holds internal statistics for a peer.
|
||||||
type peerStateStats struct {
|
type peerStateStats struct {
|
||||||
lastVoteHeight int64
|
LastVoteHeight int64 `json:"last_vote_height"`
|
||||||
votes int
|
Votes int `json:"votes"`
|
||||||
|
LastBlockPartHeight int64 `json:"last_block_part_height"`
|
||||||
lastBlockPartHeight int64
|
BlockParts int `json:"block_parts"`
|
||||||
blockParts int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pss peerStateStats) String() string {
|
func (pss peerStateStats) String() string {
|
||||||
return fmt.Sprintf("peerStateStats{votes: %d, blockParts: %d}", pss.votes, pss.blockParts)
|
return fmt.Sprintf("peerStateStats{lvh: %d, votes: %d, lbph: %d, blockParts: %d}",
|
||||||
|
pss.LastVoteHeight, pss.Votes, pss.LastBlockPartHeight, pss.BlockParts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPeerState returns a new PeerState for the given Peer
|
// NewPeerState returns a new PeerState for the given Peer
|
||||||
func NewPeerState(peer p2p.Peer) *PeerState {
|
func NewPeerState(peer p2p.Peer) *PeerState {
|
||||||
return &PeerState{
|
return &PeerState{
|
||||||
Peer: peer,
|
peer: peer,
|
||||||
logger: log.NewNopLogger(),
|
logger: log.NewNopLogger(),
|
||||||
PeerRoundState: cstypes.PeerRoundState{
|
PRS: cstypes.PeerRoundState{
|
||||||
Round: -1,
|
Round: -1,
|
||||||
ProposalPOLRound: -1,
|
ProposalPOLRound: -1,
|
||||||
LastCommitRound: -1,
|
LastCommitRound: -1,
|
||||||
CatchupCommitRound: -1,
|
CatchupCommitRound: -1,
|
||||||
},
|
},
|
||||||
stats: &peerStateStats{},
|
Stats: &peerStateStats{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetLogger allows to set a logger on the peer state. Returns the peer state
|
||||||
|
// itself.
|
||||||
func (ps *PeerState) SetLogger(logger log.Logger) *PeerState {
|
func (ps *PeerState) SetLogger(logger log.Logger) *PeerState {
|
||||||
ps.logger = logger
|
ps.logger = logger
|
||||||
return ps
|
return ps
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRoundState returns an atomic snapshot of the PeerRoundState.
|
// GetRoundState returns an shallow copy of the PeerRoundState.
|
||||||
// There's no point in mutating it since it won't change PeerState.
|
// There's no point in mutating it since it won't change PeerState.
|
||||||
func (ps *PeerState) GetRoundState() *cstypes.PeerRoundState {
|
func (ps *PeerState) GetRoundState() *cstypes.PeerRoundState {
|
||||||
ps.mtx.Lock()
|
ps.mtx.Lock()
|
||||||
defer ps.mtx.Unlock()
|
defer ps.mtx.Unlock()
|
||||||
|
|
||||||
prs := ps.PeerRoundState // copy
|
prs := ps.PRS // copy
|
||||||
return &prs
|
return &prs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToJSON returns a json of PeerState, marshalled using go-amino.
|
||||||
|
func (ps *PeerState) ToJSON() ([]byte, error) {
|
||||||
|
ps.mtx.Lock()
|
||||||
|
defer ps.mtx.Unlock()
|
||||||
|
|
||||||
|
return cdc.MarshalJSON(ps)
|
||||||
|
}
|
||||||
|
|
||||||
// GetHeight returns an atomic snapshot of the PeerRoundState's height
|
// GetHeight returns an atomic snapshot of the PeerRoundState's height
|
||||||
// used by the mempool to ensure peers are caught up before broadcasting new txs
|
// used by the mempool to ensure peers are caught up before broadcasting new txs
|
||||||
func (ps *PeerState) GetHeight() int64 {
|
func (ps *PeerState) GetHeight() int64 {
|
||||||
ps.mtx.Lock()
|
ps.mtx.Lock()
|
||||||
defer ps.mtx.Unlock()
|
defer ps.mtx.Unlock()
|
||||||
return ps.PeerRoundState.Height
|
return ps.PRS.Height
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHasProposal sets the given proposal as known for the peer.
|
// SetHasProposal sets the given proposal as known for the peer.
|
||||||
@@ -899,18 +900,18 @@ func (ps *PeerState) SetHasProposal(proposal *types.Proposal) {
|
|||||||
ps.mtx.Lock()
|
ps.mtx.Lock()
|
||||||
defer ps.mtx.Unlock()
|
defer ps.mtx.Unlock()
|
||||||
|
|
||||||
if ps.Height != proposal.Height || ps.Round != proposal.Round {
|
if ps.PRS.Height != proposal.Height || ps.PRS.Round != proposal.Round {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ps.Proposal {
|
if ps.PRS.Proposal {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ps.Proposal = true
|
ps.PRS.Proposal = true
|
||||||
ps.ProposalBlockPartsHeader = proposal.BlockPartsHeader
|
ps.PRS.ProposalBlockPartsHeader = proposal.BlockPartsHeader
|
||||||
ps.ProposalBlockParts = cmn.NewBitArray(proposal.BlockPartsHeader.Total)
|
ps.PRS.ProposalBlockParts = cmn.NewBitArray(proposal.BlockPartsHeader.Total)
|
||||||
ps.ProposalPOLRound = proposal.POLRound
|
ps.PRS.ProposalPOLRound = proposal.POLRound
|
||||||
ps.ProposalPOL = nil // Nil until ProposalPOLMessage received.
|
ps.PRS.ProposalPOL = nil // Nil until ProposalPOLMessage received.
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitProposalBlockParts initializes the peer's proposal block parts header and bit array.
|
// InitProposalBlockParts initializes the peer's proposal block parts header and bit array.
|
||||||
@@ -918,12 +919,12 @@ func (ps *PeerState) InitProposalBlockParts(partsHeader types.PartSetHeader) {
|
|||||||
ps.mtx.Lock()
|
ps.mtx.Lock()
|
||||||
defer ps.mtx.Unlock()
|
defer ps.mtx.Unlock()
|
||||||
|
|
||||||
if ps.ProposalBlockParts != nil {
|
if ps.PRS.ProposalBlockParts != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ps.ProposalBlockPartsHeader = partsHeader
|
ps.PRS.ProposalBlockPartsHeader = partsHeader
|
||||||
ps.ProposalBlockParts = cmn.NewBitArray(partsHeader.Total)
|
ps.PRS.ProposalBlockParts = cmn.NewBitArray(partsHeader.Total)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHasProposalBlockPart sets the given block part index as known for the peer.
|
// SetHasProposalBlockPart sets the given block part index as known for the peer.
|
||||||
@@ -931,11 +932,11 @@ func (ps *PeerState) SetHasProposalBlockPart(height int64, round int, index int)
|
|||||||
ps.mtx.Lock()
|
ps.mtx.Lock()
|
||||||
defer ps.mtx.Unlock()
|
defer ps.mtx.Unlock()
|
||||||
|
|
||||||
if ps.Height != height || ps.Round != round {
|
if ps.PRS.Height != height || ps.PRS.Round != round {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ps.ProposalBlockParts.SetIndex(index, true)
|
ps.PRS.ProposalBlockParts.SetIndex(index, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PickSendVote picks a vote and sends it to the peer.
|
// PickSendVote picks a vote and sends it to the peer.
|
||||||
@@ -944,7 +945,7 @@ func (ps *PeerState) PickSendVote(votes types.VoteSetReader) bool {
|
|||||||
if vote, ok := ps.PickVoteToSend(votes); ok {
|
if vote, ok := ps.PickVoteToSend(votes); ok {
|
||||||
msg := &VoteMessage{vote}
|
msg := &VoteMessage{vote}
|
||||||
ps.logger.Debug("Sending vote message", "ps", ps, "vote", vote)
|
ps.logger.Debug("Sending vote message", "ps", ps, "vote", vote)
|
||||||
return ps.Peer.Send(VoteChannel, struct{ ConsensusMessage }{msg})
|
return ps.peer.Send(VoteChannel, cdc.MustMarshalBinaryBare(msg))
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -984,40 +985,40 @@ func (ps *PeerState) getVoteBitArray(height int64, round int, type_ byte) *cmn.B
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if ps.Height == height {
|
if ps.PRS.Height == height {
|
||||||
if ps.Round == round {
|
if ps.PRS.Round == round {
|
||||||
switch type_ {
|
switch type_ {
|
||||||
case types.VoteTypePrevote:
|
case types.VoteTypePrevote:
|
||||||
return ps.Prevotes
|
return ps.PRS.Prevotes
|
||||||
case types.VoteTypePrecommit:
|
case types.VoteTypePrecommit:
|
||||||
return ps.Precommits
|
return ps.PRS.Precommits
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ps.CatchupCommitRound == round {
|
if ps.PRS.CatchupCommitRound == round {
|
||||||
switch type_ {
|
switch type_ {
|
||||||
case types.VoteTypePrevote:
|
case types.VoteTypePrevote:
|
||||||
return nil
|
return nil
|
||||||
case types.VoteTypePrecommit:
|
case types.VoteTypePrecommit:
|
||||||
return ps.CatchupCommit
|
return ps.PRS.CatchupCommit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ps.ProposalPOLRound == round {
|
if ps.PRS.ProposalPOLRound == round {
|
||||||
switch type_ {
|
switch type_ {
|
||||||
case types.VoteTypePrevote:
|
case types.VoteTypePrevote:
|
||||||
return ps.ProposalPOL
|
return ps.PRS.ProposalPOL
|
||||||
case types.VoteTypePrecommit:
|
case types.VoteTypePrecommit:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if ps.Height == height+1 {
|
if ps.PRS.Height == height+1 {
|
||||||
if ps.LastCommitRound == round {
|
if ps.PRS.LastCommitRound == round {
|
||||||
switch type_ {
|
switch type_ {
|
||||||
case types.VoteTypePrevote:
|
case types.VoteTypePrevote:
|
||||||
return nil
|
return nil
|
||||||
case types.VoteTypePrecommit:
|
case types.VoteTypePrecommit:
|
||||||
return ps.LastCommit
|
return ps.PRS.LastCommit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -1027,7 +1028,7 @@ func (ps *PeerState) getVoteBitArray(height int64, round int, type_ byte) *cmn.B
|
|||||||
|
|
||||||
// 'round': A round for which we have a +2/3 commit.
|
// 'round': A round for which we have a +2/3 commit.
|
||||||
func (ps *PeerState) ensureCatchupCommitRound(height int64, round int, numValidators int) {
|
func (ps *PeerState) ensureCatchupCommitRound(height int64, round int, numValidators int) {
|
||||||
if ps.Height != height {
|
if ps.PRS.Height != height {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
@@ -1037,18 +1038,18 @@ func (ps *PeerState) ensureCatchupCommitRound(height int64, round int, numValida
|
|||||||
cmn.PanicSanity(cmn.Fmt("Conflicting CatchupCommitRound. Height: %v, Orig: %v, New: %v", height, ps.CatchupCommitRound, round))
|
cmn.PanicSanity(cmn.Fmt("Conflicting CatchupCommitRound. Height: %v, Orig: %v, New: %v", height, ps.CatchupCommitRound, round))
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
if ps.CatchupCommitRound == round {
|
if ps.PRS.CatchupCommitRound == round {
|
||||||
return // Nothing to do!
|
return // Nothing to do!
|
||||||
}
|
}
|
||||||
ps.CatchupCommitRound = round
|
ps.PRS.CatchupCommitRound = round
|
||||||
if round == ps.Round {
|
if round == ps.PRS.Round {
|
||||||
ps.CatchupCommit = ps.Precommits
|
ps.PRS.CatchupCommit = ps.PRS.Precommits
|
||||||
} else {
|
} else {
|
||||||
ps.CatchupCommit = cmn.NewBitArray(numValidators)
|
ps.PRS.CatchupCommit = cmn.NewBitArray(numValidators)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnsureVoteVitArrays ensures the bit-arrays have been allocated for tracking
|
// EnsureVoteBitArrays ensures the bit-arrays have been allocated for tracking
|
||||||
// what votes this peer has received.
|
// what votes this peer has received.
|
||||||
// NOTE: It's important to make sure that numValidators actually matches
|
// NOTE: It's important to make sure that numValidators actually matches
|
||||||
// what the node sees as the number of validators for height.
|
// what the node sees as the number of validators for height.
|
||||||
@@ -1059,22 +1060,22 @@ func (ps *PeerState) EnsureVoteBitArrays(height int64, numValidators int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ps *PeerState) ensureVoteBitArrays(height int64, numValidators int) {
|
func (ps *PeerState) ensureVoteBitArrays(height int64, numValidators int) {
|
||||||
if ps.Height == height {
|
if ps.PRS.Height == height {
|
||||||
if ps.Prevotes == nil {
|
if ps.PRS.Prevotes == nil {
|
||||||
ps.Prevotes = cmn.NewBitArray(numValidators)
|
ps.PRS.Prevotes = cmn.NewBitArray(numValidators)
|
||||||
}
|
}
|
||||||
if ps.Precommits == nil {
|
if ps.PRS.Precommits == nil {
|
||||||
ps.Precommits = cmn.NewBitArray(numValidators)
|
ps.PRS.Precommits = cmn.NewBitArray(numValidators)
|
||||||
}
|
}
|
||||||
if ps.CatchupCommit == nil {
|
if ps.PRS.CatchupCommit == nil {
|
||||||
ps.CatchupCommit = cmn.NewBitArray(numValidators)
|
ps.PRS.CatchupCommit = cmn.NewBitArray(numValidators)
|
||||||
}
|
}
|
||||||
if ps.ProposalPOL == nil {
|
if ps.PRS.ProposalPOL == nil {
|
||||||
ps.ProposalPOL = cmn.NewBitArray(numValidators)
|
ps.PRS.ProposalPOL = cmn.NewBitArray(numValidators)
|
||||||
}
|
}
|
||||||
} else if ps.Height == height+1 {
|
} else if ps.PRS.Height == height+1 {
|
||||||
if ps.LastCommit == nil {
|
if ps.PRS.LastCommit == nil {
|
||||||
ps.LastCommit = cmn.NewBitArray(numValidators)
|
ps.PRS.LastCommit = cmn.NewBitArray(numValidators)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1083,37 +1084,50 @@ func (ps *PeerState) ensureVoteBitArrays(height int64, numValidators int) {
|
|||||||
// It returns the total number of votes (1 per block). This essentially means
|
// 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.
|
// the number of blocks for which peer has been sending us votes.
|
||||||
func (ps *PeerState) RecordVote(vote *types.Vote) int {
|
func (ps *PeerState) RecordVote(vote *types.Vote) int {
|
||||||
if ps.stats.lastVoteHeight >= vote.Height {
|
ps.mtx.Lock()
|
||||||
return ps.stats.votes
|
defer ps.mtx.Unlock()
|
||||||
|
|
||||||
|
if ps.Stats.LastVoteHeight >= vote.Height {
|
||||||
|
return ps.Stats.Votes
|
||||||
}
|
}
|
||||||
ps.stats.lastVoteHeight = vote.Height
|
ps.Stats.LastVoteHeight = vote.Height
|
||||||
ps.stats.votes += 1
|
ps.Stats.Votes++
|
||||||
return ps.stats.votes
|
return ps.Stats.Votes
|
||||||
}
|
}
|
||||||
|
|
||||||
// VotesSent returns the number of blocks for which peer has been sending us
|
// VotesSent returns the number of blocks for which peer has been sending us
|
||||||
// votes.
|
// votes.
|
||||||
func (ps *PeerState) VotesSent() int {
|
func (ps *PeerState) VotesSent() int {
|
||||||
return ps.stats.votes
|
ps.mtx.Lock()
|
||||||
|
defer ps.mtx.Unlock()
|
||||||
|
|
||||||
|
return ps.Stats.Votes
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordVote updates internal statistics for this peer by recording the block part.
|
// RecordBlockPart updates internal statistics for this peer by recording the
|
||||||
// It returns the total number of block parts (1 per block). This essentially means
|
// block part. It returns the total number of block parts (1 per block). This
|
||||||
// the number of blocks for which peer has been sending us block parts.
|
// essentially means the number of blocks for which peer has been sending us
|
||||||
|
// block parts.
|
||||||
func (ps *PeerState) RecordBlockPart(bp *BlockPartMessage) int {
|
func (ps *PeerState) RecordBlockPart(bp *BlockPartMessage) int {
|
||||||
if ps.stats.lastBlockPartHeight >= bp.Height {
|
ps.mtx.Lock()
|
||||||
return ps.stats.blockParts
|
defer ps.mtx.Unlock()
|
||||||
|
|
||||||
|
if ps.Stats.LastBlockPartHeight >= bp.Height {
|
||||||
|
return ps.Stats.BlockParts
|
||||||
}
|
}
|
||||||
|
|
||||||
ps.stats.lastBlockPartHeight = bp.Height
|
ps.Stats.LastBlockPartHeight = bp.Height
|
||||||
ps.stats.blockParts += 1
|
ps.Stats.BlockParts++
|
||||||
return ps.stats.blockParts
|
return ps.Stats.BlockParts
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlockPartsSent returns the number of blocks for which peer has been sending
|
// BlockPartsSent returns the number of blocks for which peer has been sending
|
||||||
// us block parts.
|
// us block parts.
|
||||||
func (ps *PeerState) BlockPartsSent() int {
|
func (ps *PeerState) BlockPartsSent() int {
|
||||||
return ps.stats.blockParts
|
ps.mtx.Lock()
|
||||||
|
defer ps.mtx.Unlock()
|
||||||
|
|
||||||
|
return ps.Stats.BlockParts
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHasVote sets the given vote as known by the peer
|
// SetHasVote sets the given vote as known by the peer
|
||||||
@@ -1125,7 +1139,7 @@ func (ps *PeerState) SetHasVote(vote *types.Vote) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ps *PeerState) setHasVote(height int64, round int, type_ byte, index int) {
|
func (ps *PeerState) setHasVote(height int64, round int, type_ byte, index int) {
|
||||||
logger := ps.logger.With("peerH/R", cmn.Fmt("%d/%d", ps.Height, ps.Round), "H/R", cmn.Fmt("%d/%d", height, round))
|
logger := ps.logger.With("peerH/R", cmn.Fmt("%d/%d", ps.PRS.Height, ps.PRS.Round), "H/R", cmn.Fmt("%d/%d", height, round))
|
||||||
logger.Debug("setHasVote", "type", type_, "index", index)
|
logger.Debug("setHasVote", "type", type_, "index", index)
|
||||||
|
|
||||||
// NOTE: some may be nil BitArrays -> no side effects.
|
// NOTE: some may be nil BitArrays -> no side effects.
|
||||||
@@ -1141,51 +1155,51 @@ func (ps *PeerState) ApplyNewRoundStepMessage(msg *NewRoundStepMessage) {
|
|||||||
defer ps.mtx.Unlock()
|
defer ps.mtx.Unlock()
|
||||||
|
|
||||||
// Ignore duplicates or decreases
|
// Ignore duplicates or decreases
|
||||||
if CompareHRS(msg.Height, msg.Round, msg.Step, ps.Height, ps.Round, ps.Step) <= 0 {
|
if CompareHRS(msg.Height, msg.Round, msg.Step, ps.PRS.Height, ps.PRS.Round, ps.PRS.Step) <= 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just remember these values.
|
// Just remember these values.
|
||||||
psHeight := ps.Height
|
psHeight := ps.PRS.Height
|
||||||
psRound := ps.Round
|
psRound := ps.PRS.Round
|
||||||
//psStep := ps.Step
|
//psStep := ps.PRS.Step
|
||||||
psCatchupCommitRound := ps.CatchupCommitRound
|
psCatchupCommitRound := ps.PRS.CatchupCommitRound
|
||||||
psCatchupCommit := ps.CatchupCommit
|
psCatchupCommit := ps.PRS.CatchupCommit
|
||||||
|
|
||||||
startTime := time.Now().Add(-1 * time.Duration(msg.SecondsSinceStartTime) * time.Second)
|
startTime := time.Now().Add(-1 * time.Duration(msg.SecondsSinceStartTime) * time.Second)
|
||||||
ps.Height = msg.Height
|
ps.PRS.Height = msg.Height
|
||||||
ps.Round = msg.Round
|
ps.PRS.Round = msg.Round
|
||||||
ps.Step = msg.Step
|
ps.PRS.Step = msg.Step
|
||||||
ps.StartTime = startTime
|
ps.PRS.StartTime = startTime
|
||||||
if psHeight != msg.Height || psRound != msg.Round {
|
if psHeight != msg.Height || psRound != msg.Round {
|
||||||
ps.Proposal = false
|
ps.PRS.Proposal = false
|
||||||
ps.ProposalBlockPartsHeader = types.PartSetHeader{}
|
ps.PRS.ProposalBlockPartsHeader = types.PartSetHeader{}
|
||||||
ps.ProposalBlockParts = nil
|
ps.PRS.ProposalBlockParts = nil
|
||||||
ps.ProposalPOLRound = -1
|
ps.PRS.ProposalPOLRound = -1
|
||||||
ps.ProposalPOL = nil
|
ps.PRS.ProposalPOL = nil
|
||||||
// We'll update the BitArray capacity later.
|
// We'll update the BitArray capacity later.
|
||||||
ps.Prevotes = nil
|
ps.PRS.Prevotes = nil
|
||||||
ps.Precommits = nil
|
ps.PRS.Precommits = nil
|
||||||
}
|
}
|
||||||
if psHeight == msg.Height && psRound != msg.Round && msg.Round == psCatchupCommitRound {
|
if psHeight == msg.Height && psRound != msg.Round && msg.Round == psCatchupCommitRound {
|
||||||
// Peer caught up to CatchupCommitRound.
|
// Peer caught up to CatchupCommitRound.
|
||||||
// Preserve psCatchupCommit!
|
// Preserve psCatchupCommit!
|
||||||
// NOTE: We prefer to use prs.Precommits if
|
// NOTE: We prefer to use prs.Precommits if
|
||||||
// pr.Round matches pr.CatchupCommitRound.
|
// pr.Round matches pr.CatchupCommitRound.
|
||||||
ps.Precommits = psCatchupCommit
|
ps.PRS.Precommits = psCatchupCommit
|
||||||
}
|
}
|
||||||
if psHeight != msg.Height {
|
if psHeight != msg.Height {
|
||||||
// Shift Precommits to LastCommit.
|
// Shift Precommits to LastCommit.
|
||||||
if psHeight+1 == msg.Height && psRound == msg.LastCommitRound {
|
if psHeight+1 == msg.Height && psRound == msg.LastCommitRound {
|
||||||
ps.LastCommitRound = msg.LastCommitRound
|
ps.PRS.LastCommitRound = msg.LastCommitRound
|
||||||
ps.LastCommit = ps.Precommits
|
ps.PRS.LastCommit = ps.PRS.Precommits
|
||||||
} else {
|
} else {
|
||||||
ps.LastCommitRound = msg.LastCommitRound
|
ps.PRS.LastCommitRound = msg.LastCommitRound
|
||||||
ps.LastCommit = nil
|
ps.PRS.LastCommit = nil
|
||||||
}
|
}
|
||||||
// We'll update the BitArray capacity later.
|
// We'll update the BitArray capacity later.
|
||||||
ps.CatchupCommitRound = -1
|
ps.PRS.CatchupCommitRound = -1
|
||||||
ps.CatchupCommit = nil
|
ps.PRS.CatchupCommit = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1194,12 +1208,12 @@ func (ps *PeerState) ApplyCommitStepMessage(msg *CommitStepMessage) {
|
|||||||
ps.mtx.Lock()
|
ps.mtx.Lock()
|
||||||
defer ps.mtx.Unlock()
|
defer ps.mtx.Unlock()
|
||||||
|
|
||||||
if ps.Height != msg.Height {
|
if ps.PRS.Height != msg.Height {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ps.ProposalBlockPartsHeader = msg.BlockPartsHeader
|
ps.PRS.ProposalBlockPartsHeader = msg.BlockPartsHeader
|
||||||
ps.ProposalBlockParts = msg.BlockParts
|
ps.PRS.ProposalBlockParts = msg.BlockParts
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyProposalPOLMessage updates the peer state for the new proposal POL.
|
// ApplyProposalPOLMessage updates the peer state for the new proposal POL.
|
||||||
@@ -1207,16 +1221,16 @@ func (ps *PeerState) ApplyProposalPOLMessage(msg *ProposalPOLMessage) {
|
|||||||
ps.mtx.Lock()
|
ps.mtx.Lock()
|
||||||
defer ps.mtx.Unlock()
|
defer ps.mtx.Unlock()
|
||||||
|
|
||||||
if ps.Height != msg.Height {
|
if ps.PRS.Height != msg.Height {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ps.ProposalPOLRound != msg.ProposalPOLRound {
|
if ps.PRS.ProposalPOLRound != msg.ProposalPOLRound {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Merge onto existing ps.ProposalPOL?
|
// TODO: Merge onto existing ps.PRS.ProposalPOL?
|
||||||
// We might have sent some prevotes in the meantime.
|
// We might have sent some prevotes in the meantime.
|
||||||
ps.ProposalPOL = msg.ProposalPOL
|
ps.PRS.ProposalPOL = msg.ProposalPOL
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyHasVoteMessage updates the peer state for the new vote.
|
// ApplyHasVoteMessage updates the peer state for the new vote.
|
||||||
@@ -1224,7 +1238,7 @@ func (ps *PeerState) ApplyHasVoteMessage(msg *HasVoteMessage) {
|
|||||||
ps.mtx.Lock()
|
ps.mtx.Lock()
|
||||||
defer ps.mtx.Unlock()
|
defer ps.mtx.Unlock()
|
||||||
|
|
||||||
if ps.Height != msg.Height {
|
if ps.PRS.Height != msg.Height {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1262,58 +1276,43 @@ func (ps *PeerState) StringIndented(indent string) string {
|
|||||||
ps.mtx.Lock()
|
ps.mtx.Lock()
|
||||||
defer ps.mtx.Unlock()
|
defer ps.mtx.Unlock()
|
||||||
return fmt.Sprintf(`PeerState{
|
return fmt.Sprintf(`PeerState{
|
||||||
%s Key %v
|
%s Key %v
|
||||||
%s PRS %v
|
%s RoundState %v
|
||||||
%s Stats %v
|
%s Stats %v
|
||||||
%s}`,
|
%s}`,
|
||||||
indent, ps.Peer.ID(),
|
indent, ps.peer.ID(),
|
||||||
indent, ps.PeerRoundState.StringIndented(indent+" "),
|
indent, ps.PRS.StringIndented(indent+" "),
|
||||||
indent, ps.stats,
|
indent, ps.Stats,
|
||||||
indent)
|
indent)
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Messages
|
// Messages
|
||||||
|
|
||||||
const (
|
|
||||||
msgTypeNewRoundStep = byte(0x01)
|
|
||||||
msgTypeCommitStep = byte(0x02)
|
|
||||||
msgTypeProposal = byte(0x11)
|
|
||||||
msgTypeProposalPOL = byte(0x12)
|
|
||||||
msgTypeBlockPart = byte(0x13) // both block & POL
|
|
||||||
msgTypeVote = byte(0x14)
|
|
||||||
msgTypeHasVote = byte(0x15)
|
|
||||||
msgTypeVoteSetMaj23 = byte(0x16)
|
|
||||||
msgTypeVoteSetBits = byte(0x17)
|
|
||||||
|
|
||||||
msgTypeProposalHeartbeat = byte(0x20)
|
|
||||||
)
|
|
||||||
|
|
||||||
// ConsensusMessage is a message that can be sent and received on the ConsensusReactor
|
// ConsensusMessage is a message that can be sent and received on the ConsensusReactor
|
||||||
type ConsensusMessage interface{}
|
type ConsensusMessage interface{}
|
||||||
|
|
||||||
var _ = wire.RegisterInterface(
|
func RegisterConsensusMessages(cdc *amino.Codec) {
|
||||||
struct{ ConsensusMessage }{},
|
cdc.RegisterInterface((*ConsensusMessage)(nil), nil)
|
||||||
wire.ConcreteType{&NewRoundStepMessage{}, msgTypeNewRoundStep},
|
cdc.RegisterConcrete(&NewRoundStepMessage{}, "tendermint/NewRoundStepMessage", nil)
|
||||||
wire.ConcreteType{&CommitStepMessage{}, msgTypeCommitStep},
|
cdc.RegisterConcrete(&CommitStepMessage{}, "tendermint/CommitStep", nil)
|
||||||
wire.ConcreteType{&ProposalMessage{}, msgTypeProposal},
|
cdc.RegisterConcrete(&ProposalMessage{}, "tendermint/Proposal", nil)
|
||||||
wire.ConcreteType{&ProposalPOLMessage{}, msgTypeProposalPOL},
|
cdc.RegisterConcrete(&ProposalPOLMessage{}, "tendermint/ProposalPOL", nil)
|
||||||
wire.ConcreteType{&BlockPartMessage{}, msgTypeBlockPart},
|
cdc.RegisterConcrete(&BlockPartMessage{}, "tendermint/BlockPart", nil)
|
||||||
wire.ConcreteType{&VoteMessage{}, msgTypeVote},
|
cdc.RegisterConcrete(&VoteMessage{}, "tendermint/Vote", nil)
|
||||||
wire.ConcreteType{&HasVoteMessage{}, msgTypeHasVote},
|
cdc.RegisterConcrete(&HasVoteMessage{}, "tendermint/HasVote", nil)
|
||||||
wire.ConcreteType{&VoteSetMaj23Message{}, msgTypeVoteSetMaj23},
|
cdc.RegisterConcrete(&VoteSetMaj23Message{}, "tendermint/VoteSetMaj23", nil)
|
||||||
wire.ConcreteType{&VoteSetBitsMessage{}, msgTypeVoteSetBits},
|
cdc.RegisterConcrete(&VoteSetBitsMessage{}, "tendermint/VoteSetBits", nil)
|
||||||
wire.ConcreteType{&ProposalHeartbeatMessage{}, msgTypeProposalHeartbeat},
|
cdc.RegisterConcrete(&ProposalHeartbeatMessage{}, "tendermint/ProposalHeartbeat", nil)
|
||||||
)
|
}
|
||||||
|
|
||||||
// DecodeMessage decodes the given bytes into a ConsensusMessage.
|
// DecodeMessage decodes the given bytes into a ConsensusMessage.
|
||||||
// TODO: check for unnecessary extra bytes at the end.
|
func DecodeMessage(bz []byte) (msg ConsensusMessage, err error) {
|
||||||
func DecodeMessage(bz []byte) (msgType byte, msg ConsensusMessage, err error) {
|
if len(bz) > maxMsgSize {
|
||||||
msgType = bz[0]
|
return msg, fmt.Errorf("Msg exceeds max size (%d > %d)",
|
||||||
n := new(int)
|
len(bz), maxMsgSize)
|
||||||
r := bytes.NewReader(bz)
|
}
|
||||||
msgI := wire.ReadBinary(struct{ ConsensusMessage }{}, r, maxConsensusMessageSize, n, &err)
|
err = cdc.UnmarshalBinaryBare(bz, &msg)
|
||||||
msg = msgI.(struct{ ConsensusMessage }).ConsensusMessage
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,7 +11,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tendermint/abci/example/kvstore"
|
"github.com/tendermint/abci/example/kvstore"
|
||||||
wire "github.com/tendermint/tendermint/wire"
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
"github.com/tendermint/tmlibs/log"
|
"github.com/tendermint/tmlibs/log"
|
||||||
|
|
||||||
@@ -149,30 +148,30 @@ func TestReactorRecordsBlockParts(t *testing.T) {
|
|||||||
Round: 0,
|
Round: 0,
|
||||||
Part: parts.GetPart(0),
|
Part: parts.GetPart(0),
|
||||||
}
|
}
|
||||||
bz, err := wire.MarshalBinary(struct{ ConsensusMessage }{msg})
|
bz, err := cdc.MarshalBinaryBare(msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
reactor.Receive(DataChannel, peer, bz)
|
reactor.Receive(DataChannel, peer, bz)
|
||||||
assert.Equal(t, 1, ps.BlockPartsSent(), "number of block parts sent should have increased by 1")
|
require.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
|
// 2) block part with the same height, but different round
|
||||||
msg.Round = 1
|
msg.Round = 1
|
||||||
|
|
||||||
bz, err = wire.MarshalBinary(struct{ ConsensusMessage }{msg})
|
bz, err = cdc.MarshalBinaryBare(msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
reactor.Receive(DataChannel, peer, bz)
|
reactor.Receive(DataChannel, peer, bz)
|
||||||
assert.Equal(t, 1, ps.BlockPartsSent(), "number of block parts sent should stay the same")
|
require.Equal(t, 1, ps.BlockPartsSent(), "number of block parts sent should stay the same")
|
||||||
|
|
||||||
// 3) block part from earlier height
|
// 3) block part from earlier height
|
||||||
msg.Height = 1
|
msg.Height = 1
|
||||||
msg.Round = 0
|
msg.Round = 0
|
||||||
|
|
||||||
bz, err = wire.MarshalBinary(struct{ ConsensusMessage }{msg})
|
bz, err = cdc.MarshalBinaryBare(msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
reactor.Receive(DataChannel, peer, bz)
|
reactor.Receive(DataChannel, peer, bz)
|
||||||
assert.Equal(t, 1, ps.BlockPartsSent(), "number of block parts sent should stay the same")
|
require.Equal(t, 1, ps.BlockPartsSent(), "number of block parts sent should stay the same")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test we record votes from other peers
|
// Test we record votes from other peers
|
||||||
@@ -204,7 +203,7 @@ func TestReactorRecordsVotes(t *testing.T) {
|
|||||||
Type: types.VoteTypePrevote,
|
Type: types.VoteTypePrevote,
|
||||||
BlockID: types.BlockID{},
|
BlockID: types.BlockID{},
|
||||||
}
|
}
|
||||||
bz, err := wire.MarshalBinary(struct{ ConsensusMessage }{&VoteMessage{vote}})
|
bz, err := cdc.MarshalBinaryBare(&VoteMessage{vote})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
reactor.Receive(VoteChannel, peer, bz)
|
reactor.Receive(VoteChannel, peer, bz)
|
||||||
@@ -213,7 +212,7 @@ func TestReactorRecordsVotes(t *testing.T) {
|
|||||||
// 2) vote with the same height, but different round
|
// 2) vote with the same height, but different round
|
||||||
vote.Round = 1
|
vote.Round = 1
|
||||||
|
|
||||||
bz, err = wire.MarshalBinary(struct{ ConsensusMessage }{&VoteMessage{vote}})
|
bz, err = cdc.MarshalBinaryBare(&VoteMessage{vote})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
reactor.Receive(VoteChannel, peer, bz)
|
reactor.Receive(VoteChannel, peer, bz)
|
||||||
@@ -223,7 +222,7 @@ func TestReactorRecordsVotes(t *testing.T) {
|
|||||||
vote.Height = 1
|
vote.Height = 1
|
||||||
vote.Round = 0
|
vote.Round = 0
|
||||||
|
|
||||||
bz, err = wire.MarshalBinary(struct{ ConsensusMessage }{&VoteMessage{vote}})
|
bz, err = cdc.MarshalBinaryBare(&VoteMessage{vote})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
reactor.Receive(VoteChannel, peer, bz)
|
reactor.Receive(VoteChannel, peer, bz)
|
||||||
@@ -255,7 +254,8 @@ func TestReactorVotingPowerChange(t *testing.T) {
|
|||||||
logger.Debug("---------------------------- Testing changing the voting power of one validator a few times")
|
logger.Debug("---------------------------- Testing changing the voting power of one validator a few times")
|
||||||
|
|
||||||
val1PubKey := css[0].privValidator.GetPubKey()
|
val1PubKey := css[0].privValidator.GetPubKey()
|
||||||
updateValidatorTx := kvstore.MakeValSetChangeTx(val1PubKey.Bytes(), 25)
|
val1PubKeyABCI := types.TM2PB.PubKey(val1PubKey)
|
||||||
|
updateValidatorTx := kvstore.MakeValSetChangeTx(val1PubKeyABCI, 25)
|
||||||
previousTotalVotingPower := css[0].GetRoundState().LastValidators.TotalVotingPower()
|
previousTotalVotingPower := css[0].GetRoundState().LastValidators.TotalVotingPower()
|
||||||
|
|
||||||
waitForAndValidateBlock(t, nVals, activeVals, eventChans, css, updateValidatorTx)
|
waitForAndValidateBlock(t, nVals, activeVals, eventChans, css, updateValidatorTx)
|
||||||
@@ -267,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())
|
t.Fatalf("expected voting power to change (before: %d, after: %d)", previousTotalVotingPower, css[0].GetRoundState().LastValidators.TotalVotingPower())
|
||||||
}
|
}
|
||||||
|
|
||||||
updateValidatorTx = kvstore.MakeValSetChangeTx(val1PubKey.Bytes(), 2)
|
updateValidatorTx = kvstore.MakeValSetChangeTx(val1PubKeyABCI, 2)
|
||||||
previousTotalVotingPower = css[0].GetRoundState().LastValidators.TotalVotingPower()
|
previousTotalVotingPower = css[0].GetRoundState().LastValidators.TotalVotingPower()
|
||||||
|
|
||||||
waitForAndValidateBlock(t, nVals, activeVals, eventChans, css, updateValidatorTx)
|
waitForAndValidateBlock(t, nVals, activeVals, eventChans, css, updateValidatorTx)
|
||||||
@@ -279,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())
|
t.Fatalf("expected voting power to change (before: %d, after: %d)", previousTotalVotingPower, css[0].GetRoundState().LastValidators.TotalVotingPower())
|
||||||
}
|
}
|
||||||
|
|
||||||
updateValidatorTx = kvstore.MakeValSetChangeTx(val1PubKey.Bytes(), 26)
|
updateValidatorTx = kvstore.MakeValSetChangeTx(val1PubKeyABCI, 26)
|
||||||
previousTotalVotingPower = css[0].GetRoundState().LastValidators.TotalVotingPower()
|
previousTotalVotingPower = css[0].GetRoundState().LastValidators.TotalVotingPower()
|
||||||
|
|
||||||
waitForAndValidateBlock(t, nVals, activeVals, eventChans, css, updateValidatorTx)
|
waitForAndValidateBlock(t, nVals, activeVals, eventChans, css, updateValidatorTx)
|
||||||
@@ -317,7 +317,8 @@ func TestReactorValidatorSetChanges(t *testing.T) {
|
|||||||
logger.Info("---------------------------- Testing adding one validator")
|
logger.Info("---------------------------- Testing adding one validator")
|
||||||
|
|
||||||
newValidatorPubKey1 := css[nVals].privValidator.GetPubKey()
|
newValidatorPubKey1 := css[nVals].privValidator.GetPubKey()
|
||||||
newValidatorTx1 := kvstore.MakeValSetChangeTx(newValidatorPubKey1.Bytes(), testMinPower)
|
valPubKey1ABCI := types.TM2PB.PubKey(newValidatorPubKey1)
|
||||||
|
newValidatorTx1 := kvstore.MakeValSetChangeTx(valPubKey1ABCI, testMinPower)
|
||||||
|
|
||||||
// wait till everyone makes block 2
|
// wait till everyone makes block 2
|
||||||
// ensure the commit includes all validators
|
// ensure the commit includes all validators
|
||||||
@@ -343,7 +344,8 @@ func TestReactorValidatorSetChanges(t *testing.T) {
|
|||||||
logger.Info("---------------------------- Testing changing the voting power of one validator")
|
logger.Info("---------------------------- Testing changing the voting power of one validator")
|
||||||
|
|
||||||
updateValidatorPubKey1 := css[nVals].privValidator.GetPubKey()
|
updateValidatorPubKey1 := css[nVals].privValidator.GetPubKey()
|
||||||
updateValidatorTx1 := kvstore.MakeValSetChangeTx(updateValidatorPubKey1.Bytes(), 25)
|
updatePubKey1ABCI := types.TM2PB.PubKey(updateValidatorPubKey1)
|
||||||
|
updateValidatorTx1 := kvstore.MakeValSetChangeTx(updatePubKey1ABCI, 25)
|
||||||
previousTotalVotingPower := css[nVals].GetRoundState().LastValidators.TotalVotingPower()
|
previousTotalVotingPower := css[nVals].GetRoundState().LastValidators.TotalVotingPower()
|
||||||
|
|
||||||
waitForAndValidateBlock(t, nPeers, activeVals, eventChans, css, updateValidatorTx1)
|
waitForAndValidateBlock(t, nPeers, activeVals, eventChans, css, updateValidatorTx1)
|
||||||
@@ -359,10 +361,12 @@ func TestReactorValidatorSetChanges(t *testing.T) {
|
|||||||
logger.Info("---------------------------- Testing adding two validators at once")
|
logger.Info("---------------------------- Testing adding two validators at once")
|
||||||
|
|
||||||
newValidatorPubKey2 := css[nVals+1].privValidator.GetPubKey()
|
newValidatorPubKey2 := css[nVals+1].privValidator.GetPubKey()
|
||||||
newValidatorTx2 := kvstore.MakeValSetChangeTx(newValidatorPubKey2.Bytes(), testMinPower)
|
newVal2ABCI := types.TM2PB.PubKey(newValidatorPubKey2)
|
||||||
|
newValidatorTx2 := kvstore.MakeValSetChangeTx(newVal2ABCI, testMinPower)
|
||||||
|
|
||||||
newValidatorPubKey3 := css[nVals+2].privValidator.GetPubKey()
|
newValidatorPubKey3 := css[nVals+2].privValidator.GetPubKey()
|
||||||
newValidatorTx3 := kvstore.MakeValSetChangeTx(newValidatorPubKey3.Bytes(), testMinPower)
|
newVal3ABCI := types.TM2PB.PubKey(newValidatorPubKey3)
|
||||||
|
newValidatorTx3 := kvstore.MakeValSetChangeTx(newVal3ABCI, testMinPower)
|
||||||
|
|
||||||
waitForAndValidateBlock(t, nPeers, activeVals, eventChans, css, newValidatorTx2, newValidatorTx3)
|
waitForAndValidateBlock(t, nPeers, activeVals, eventChans, css, newValidatorTx2, newValidatorTx3)
|
||||||
waitForAndValidateBlockWithTx(t, nPeers, activeVals, eventChans, css, newValidatorTx2, newValidatorTx3)
|
waitForAndValidateBlockWithTx(t, nPeers, activeVals, eventChans, css, newValidatorTx2, newValidatorTx3)
|
||||||
@@ -374,8 +378,8 @@ func TestReactorValidatorSetChanges(t *testing.T) {
|
|||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
logger.Info("---------------------------- Testing removing two validators at once")
|
logger.Info("---------------------------- Testing removing two validators at once")
|
||||||
|
|
||||||
removeValidatorTx2 := kvstore.MakeValSetChangeTx(newValidatorPubKey2.Bytes(), 0)
|
removeValidatorTx2 := kvstore.MakeValSetChangeTx(newVal2ABCI, 0)
|
||||||
removeValidatorTx3 := kvstore.MakeValSetChangeTx(newValidatorPubKey3.Bytes(), 0)
|
removeValidatorTx3 := kvstore.MakeValSetChangeTx(newVal3ABCI, 0)
|
||||||
|
|
||||||
waitForAndValidateBlock(t, nPeers, activeVals, eventChans, css, removeValidatorTx2, removeValidatorTx3)
|
waitForAndValidateBlock(t, nPeers, activeVals, eventChans, css, removeValidatorTx2, removeValidatorTx3)
|
||||||
waitForAndValidateBlockWithTx(t, nPeers, activeVals, eventChans, css, removeValidatorTx2, removeValidatorTx3)
|
waitForAndValidateBlockWithTx(t, nPeers, activeVals, eventChans, css, removeValidatorTx2, removeValidatorTx3)
|
||||||
@@ -410,7 +414,7 @@ func waitForAndValidateBlock(t *testing.T, n int, activeVals map[string]struct{}
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
newBlock := newBlockI.(types.TMEventData).Unwrap().(types.EventDataNewBlock).Block
|
newBlock := newBlockI.(types.EventDataNewBlock).Block
|
||||||
css[j].Logger.Debug("waitForAndValidateBlock: Got block", "height", newBlock.Height)
|
css[j].Logger.Debug("waitForAndValidateBlock: Got block", "height", newBlock.Height)
|
||||||
err := validateBlock(newBlock, activeVals)
|
err := validateBlock(newBlock, activeVals)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -431,7 +435,7 @@ func waitForAndValidateBlockWithTx(t *testing.T, n int, activeVals map[string]st
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
newBlock := newBlockI.(types.TMEventData).Unwrap().(types.EventDataNewBlock).Block
|
newBlock := newBlockI.(types.EventDataNewBlock).Block
|
||||||
css[j].Logger.Debug("waitForAndValidateBlockWithTx: Got block", "height", newBlock.Height)
|
css[j].Logger.Debug("waitForAndValidateBlockWithTx: Got block", "height", newBlock.Height)
|
||||||
err := validateBlock(newBlock, activeVals)
|
err := validateBlock(newBlock, activeVals)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -441,7 +445,7 @@ func waitForAndValidateBlockWithTx(t *testing.T, n int, activeVals map[string]st
|
|||||||
// but they should be in order.
|
// but they should be in order.
|
||||||
for _, tx := range newBlock.Data.Txs {
|
for _, tx := range newBlock.Data.Txs {
|
||||||
assert.EqualValues(t, txs[ntxs], tx)
|
assert.EqualValues(t, txs[ntxs], tx)
|
||||||
ntxs += 1
|
ntxs++
|
||||||
}
|
}
|
||||||
|
|
||||||
if ntxs == len(txs) {
|
if ntxs == len(txs) {
|
||||||
@@ -463,7 +467,7 @@ func waitForBlockWithUpdatedValsAndValidateIt(t *testing.T, n int, updatedVals m
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
newBlock = newBlockI.(types.TMEventData).Unwrap().(types.EventDataNewBlock).Block
|
newBlock = newBlockI.(types.EventDataNewBlock).Block
|
||||||
if newBlock.LastCommit.Size() == len(updatedVals) {
|
if newBlock.LastCommit.Size() == len(updatedVals) {
|
||||||
css[j].Logger.Debug("waitForBlockWithUpdatedValsAndValidateIt: Got block", "height", newBlock.Height)
|
css[j].Logger.Debug("waitForBlockWithUpdatedValsAndValidateIt: Got block", "height", newBlock.Height)
|
||||||
break LOOP
|
break LOOP
|
||||||
|
@@ -2,7 +2,6 @@ package consensus
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
"io"
|
"io"
|
||||||
@@ -26,20 +25,24 @@ import (
|
|||||||
var crc32c = crc32.MakeTable(crc32.Castagnoli)
|
var crc32c = crc32.MakeTable(crc32.Castagnoli)
|
||||||
|
|
||||||
// Functionality to replay blocks and messages on recovery from a crash.
|
// Functionality to replay blocks and messages on recovery from a crash.
|
||||||
// There are two general failure scenarios: failure during consensus, and failure while applying the block.
|
// There are two general failure scenarios:
|
||||||
// The former is handled by the WAL, the latter by the proxyApp Handshake on restart,
|
//
|
||||||
// which ultimately hands off the work to the WAL.
|
// 1. failure during consensus
|
||||||
|
// 2. failure while applying the block
|
||||||
|
//
|
||||||
|
// The former is handled by the WAL, the latter by the proxyApp Handshake on
|
||||||
|
// restart, which ultimately hands off the work to the WAL.
|
||||||
|
|
||||||
//-----------------------------------------
|
//-----------------------------------------
|
||||||
// recover from failure during consensus
|
// 1. Recover from failure during consensus
|
||||||
// by replaying messages from the WAL
|
// (by replaying messages from the WAL)
|
||||||
|
//-----------------------------------------
|
||||||
|
|
||||||
// Unmarshal and apply a single message to the consensus state
|
// Unmarshal and apply a single message to the consensus state as if it were
|
||||||
// as if it were received in receiveRoutine
|
// received in receiveRoutine. Lines that start with "#" are ignored.
|
||||||
// Lines that start with "#" are ignored.
|
// NOTE: receiveRoutine should not be running.
|
||||||
// NOTE: receiveRoutine should not be running
|
|
||||||
func (cs *ConsensusState) readReplayMessage(msg *TimedWALMessage, newStepCh chan interface{}) error {
|
func (cs *ConsensusState) readReplayMessage(msg *TimedWALMessage, newStepCh chan interface{}) error {
|
||||||
// skip meta messages
|
// Skip meta messages which exist for demarcating boundaries.
|
||||||
if _, ok := msg.Msg.(EndHeightMessage); ok {
|
if _, ok := msg.Msg.(EndHeightMessage); ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -89,17 +92,18 @@ func (cs *ConsensusState) readReplayMessage(msg *TimedWALMessage, newStepCh chan
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// replay only those messages since the last block.
|
// Replay only those messages since the last block. `timeoutRoutine` should
|
||||||
// timeoutRoutine should run concurrently to read off tickChan
|
// run concurrently to read off tickChan.
|
||||||
func (cs *ConsensusState) catchupReplay(csHeight int64) error {
|
func (cs *ConsensusState) catchupReplay(csHeight int64) error {
|
||||||
// set replayMode
|
|
||||||
|
// Set replayMode to true so we don't log signing errors.
|
||||||
cs.replayMode = true
|
cs.replayMode = true
|
||||||
defer func() { cs.replayMode = false }()
|
defer func() { cs.replayMode = false }()
|
||||||
|
|
||||||
// Ensure that ENDHEIGHT for this height doesn't exist.
|
// Ensure that #ENDHEIGHT for this height doesn't exist.
|
||||||
// NOTE: This is just a sanity check. As far as we know things work fine
|
// NOTE: This is just a sanity check. As far as we know things work fine
|
||||||
// without it, and Handshake could reuse ConsensusState if it weren't for
|
// without it, and Handshake could reuse ConsensusState if it weren't for
|
||||||
// this check (since we can crash after writing ENDHEIGHT).
|
// this check (since we can crash after writing #ENDHEIGHT).
|
||||||
//
|
//
|
||||||
// Ignore data corruption errors since this is a sanity check.
|
// Ignore data corruption errors since this is a sanity check.
|
||||||
gr, found, err := cs.wal.SearchForEndHeight(csHeight, &WALSearchOptions{IgnoreDataCorruptionErrors: true})
|
gr, found, err := cs.wal.SearchForEndHeight(csHeight, &WALSearchOptions{IgnoreDataCorruptionErrors: true})
|
||||||
@@ -112,10 +116,10 @@ func (cs *ConsensusState) catchupReplay(csHeight int64) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if found {
|
if found {
|
||||||
return fmt.Errorf("WAL should not contain #ENDHEIGHT %d.", csHeight)
|
return fmt.Errorf("WAL should not contain #ENDHEIGHT %d", csHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search for last height marker
|
// Search for last height marker.
|
||||||
//
|
//
|
||||||
// Ignore data corruption errors in previous heights because we only care about last height
|
// Ignore data corruption errors in previous heights because we only care about last height
|
||||||
gr, found, err = cs.wal.SearchForEndHeight(csHeight-1, &WALSearchOptions{IgnoreDataCorruptionErrors: true})
|
gr, found, err = cs.wal.SearchForEndHeight(csHeight-1, &WALSearchOptions{IgnoreDataCorruptionErrors: true})
|
||||||
@@ -125,7 +129,7 @@ func (cs *ConsensusState) catchupReplay(csHeight int64) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
return fmt.Errorf("Cannot replay height %d. WAL does not contain #ENDHEIGHT for %d.", csHeight, csHeight-1)
|
return fmt.Errorf("Cannot replay height %d. WAL does not contain #ENDHEIGHT for %d", csHeight, csHeight-1)
|
||||||
}
|
}
|
||||||
defer gr.Close() // nolint: errcheck
|
defer gr.Close() // nolint: errcheck
|
||||||
|
|
||||||
@@ -182,29 +186,30 @@ func makeHeightSearchFunc(height int64) auto.SearchFunc {
|
|||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
//----------------------------------------------
|
//---------------------------------------------------
|
||||||
// Recover from failure during block processing
|
// 2. Recover from failure while applying the block.
|
||||||
// by handshaking with the app to figure out where
|
// (by handshaking with the app to figure out where
|
||||||
// we were last and using the WAL to recover there
|
// we were last, and using the WAL to recover there.)
|
||||||
|
//---------------------------------------------------
|
||||||
|
|
||||||
type Handshaker struct {
|
type Handshaker struct {
|
||||||
stateDB dbm.DB
|
stateDB dbm.DB
|
||||||
initialState sm.State
|
initialState sm.State
|
||||||
store types.BlockStore
|
store sm.BlockStore
|
||||||
appState json.RawMessage
|
genDoc *types.GenesisDoc
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
|
|
||||||
nBlocks int // number of blocks applied to the state
|
nBlocks int // number of blocks applied to the state
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHandshaker(stateDB dbm.DB, state sm.State,
|
func NewHandshaker(stateDB dbm.DB, state sm.State,
|
||||||
store types.BlockStore, appState json.RawMessage) *Handshaker {
|
store sm.BlockStore, genDoc *types.GenesisDoc) *Handshaker {
|
||||||
|
|
||||||
return &Handshaker{
|
return &Handshaker{
|
||||||
stateDB: stateDB,
|
stateDB: stateDB,
|
||||||
initialState: state,
|
initialState: state,
|
||||||
store: store,
|
store: store,
|
||||||
appState: appState,
|
genDoc: genDoc,
|
||||||
logger: log.NewNopLogger(),
|
logger: log.NewNopLogger(),
|
||||||
nBlocks: 0,
|
nBlocks: 0,
|
||||||
}
|
}
|
||||||
@@ -220,7 +225,8 @@ func (h *Handshaker) NBlocks() int {
|
|||||||
|
|
||||||
// TODO: retry the handshake/replay if it fails ?
|
// TODO: retry the handshake/replay if it fails ?
|
||||||
func (h *Handshaker) Handshake(proxyApp proxy.AppConns) error {
|
func (h *Handshaker) Handshake(proxyApp proxy.AppConns) error {
|
||||||
// handshake is done via info request on the query conn
|
|
||||||
|
// Handshake is done via ABCI Info on the query conn.
|
||||||
res, err := proxyApp.Query().InfoSync(abci.RequestInfo{version.Version})
|
res, err := proxyApp.Query().InfoSync(abci.RequestInfo{version.Version})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error calling Info: %v", err)
|
return fmt.Errorf("Error calling Info: %v", err)
|
||||||
@@ -234,15 +240,16 @@ func (h *Handshaker) Handshake(proxyApp proxy.AppConns) error {
|
|||||||
|
|
||||||
h.logger.Info("ABCI Handshake", "appHeight", blockHeight, "appHash", fmt.Sprintf("%X", appHash))
|
h.logger.Info("ABCI Handshake", "appHeight", blockHeight, "appHash", fmt.Sprintf("%X", appHash))
|
||||||
|
|
||||||
// TODO: check version
|
// TODO: check app version.
|
||||||
|
|
||||||
// replay blocks up to the latest in the blockstore
|
// Replay blocks up to the latest in the blockstore.
|
||||||
_, err = h.ReplayBlocks(h.initialState, appHash, blockHeight, proxyApp)
|
_, err = h.ReplayBlocks(h.initialState, appHash, blockHeight, proxyApp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error on replay: %v", err)
|
return fmt.Errorf("Error on replay: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
h.logger.Info("Completed ABCI Handshake - Tendermint and App are synced", "appHeight", blockHeight, "appHash", fmt.Sprintf("%X", appHash))
|
h.logger.Info("Completed ABCI Handshake - Tendermint and App are synced",
|
||||||
|
"appHeight", blockHeight, "appHash", fmt.Sprintf("%X", appHash))
|
||||||
|
|
||||||
// TODO: (on restart) replay mempool
|
// TODO: (on restart) replay mempool
|
||||||
|
|
||||||
@@ -250,7 +257,7 @@ func (h *Handshaker) Handshake(proxyApp proxy.AppConns) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Replay all blocks since appBlockHeight and ensure the result matches the current state.
|
// Replay all blocks since appBlockHeight and ensure the result matches the current state.
|
||||||
// Returns the final AppHash or an error
|
// Returns the final AppHash or an error.
|
||||||
func (h *Handshaker) ReplayBlocks(state sm.State, appHash []byte, appBlockHeight int64, proxyApp proxy.AppConns) ([]byte, error) {
|
func (h *Handshaker) ReplayBlocks(state sm.State, appHash []byte, appBlockHeight int64, proxyApp proxy.AppConns) ([]byte, error) {
|
||||||
|
|
||||||
storeBlockHeight := h.store.Height()
|
storeBlockHeight := h.store.Height()
|
||||||
@@ -260,14 +267,33 @@ 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 it means that we are at genesis and hence should send InitChain
|
||||||
if appBlockHeight == 0 {
|
if appBlockHeight == 0 {
|
||||||
validators := types.TM2PB.Validators(state.Validators)
|
validators := types.TM2PB.Validators(state.Validators)
|
||||||
|
csParams := types.TM2PB.ConsensusParams(h.genDoc.ConsensusParams)
|
||||||
req := abci.RequestInitChain{
|
req := abci.RequestInitChain{
|
||||||
Validators: validators,
|
Time: h.genDoc.GenesisTime.Unix(), // TODO
|
||||||
AppStateBytes: h.appState,
|
ChainId: h.genDoc.ChainID,
|
||||||
|
ConsensusParams: csParams,
|
||||||
|
Validators: validators,
|
||||||
|
AppStateBytes: h.genDoc.AppStateJSON,
|
||||||
}
|
}
|
||||||
_, err := proxyApp.Consensus().InitChainSync(req)
|
res, err := proxyApp.Consensus().InitChainSync(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the app returned validators
|
||||||
|
// or consensus params, update the state
|
||||||
|
// with the them
|
||||||
|
if len(res.Validators) > 0 {
|
||||||
|
vals, err := types.PB2TM.Validators(res.Validators)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
state.Validators = types.NewValidatorSet(vals)
|
||||||
|
}
|
||||||
|
if res.ConsensusParams != nil {
|
||||||
|
state.ConsensusParams = types.PB2TM.ConsensusParams(res.ConsensusParams)
|
||||||
|
}
|
||||||
|
sm.SaveState(h.stateDB, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
// First handle edge cases and constraints on the storeBlockHeight
|
// First handle edge cases and constraints on the storeBlockHeight
|
||||||
@@ -314,7 +340,7 @@ func (h *Handshaker) ReplayBlocks(state sm.State, appHash []byte, appBlockHeight
|
|||||||
// We haven't run Commit (both the state and app are one block behind),
|
// We haven't run Commit (both the state and app are one block behind),
|
||||||
// so replayBlock with the real app.
|
// so replayBlock with the real app.
|
||||||
// NOTE: We could instead use the cs.WAL on cs.Start,
|
// NOTE: We could instead use the cs.WAL on cs.Start,
|
||||||
// but we'd have to allow the WAL to replay a block that wrote it's ENDHEIGHT
|
// but we'd have to allow the WAL to replay a block that wrote it's #ENDHEIGHT
|
||||||
h.logger.Info("Replay last block using real app")
|
h.logger.Info("Replay last block using real app")
|
||||||
state, err = h.replayBlock(state, storeBlockHeight, proxyApp.Consensus())
|
state, err = h.replayBlock(state, storeBlockHeight, proxyApp.Consensus())
|
||||||
return state.AppHash, err
|
return state.AppHash, err
|
||||||
@@ -352,17 +378,17 @@ func (h *Handshaker) replayBlocks(state sm.State, proxyApp proxy.AppConns, appBl
|
|||||||
var err error
|
var err error
|
||||||
finalBlock := storeBlockHeight
|
finalBlock := storeBlockHeight
|
||||||
if mutateState {
|
if mutateState {
|
||||||
finalBlock -= 1
|
finalBlock--
|
||||||
}
|
}
|
||||||
for i := appBlockHeight + 1; i <= finalBlock; i++ {
|
for i := appBlockHeight + 1; i <= finalBlock; i++ {
|
||||||
h.logger.Info("Applying block", "height", i)
|
h.logger.Info("Applying block", "height", i)
|
||||||
block := h.store.LoadBlock(i)
|
block := h.store.LoadBlock(i)
|
||||||
appHash, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, h.logger)
|
appHash, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, h.logger, state.LastValidators, h.stateDB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
h.nBlocks += 1
|
h.nBlocks++
|
||||||
}
|
}
|
||||||
|
|
||||||
if mutateState {
|
if mutateState {
|
||||||
@@ -382,7 +408,7 @@ func (h *Handshaker) replayBlock(state sm.State, height int64, proxyApp proxy.Ap
|
|||||||
block := h.store.LoadBlock(height)
|
block := h.store.LoadBlock(height)
|
||||||
meta := h.store.LoadBlockMeta(height)
|
meta := h.store.LoadBlockMeta(height)
|
||||||
|
|
||||||
blockExec := sm.NewBlockExecutor(h.stateDB, h.logger, proxyApp, types.MockMempool{}, types.MockEvidencePool{})
|
blockExec := sm.NewBlockExecutor(h.stateDB, h.logger, proxyApp, sm.MockMempool{}, sm.MockEvidencePool{})
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
state, err = blockExec.ApplyBlock(state, meta.BlockID, block)
|
state, err = blockExec.ApplyBlock(state, meta.BlockID, block)
|
||||||
@@ -390,7 +416,7 @@ func (h *Handshaker) replayBlock(state sm.State, height int64, proxyApp proxy.Ap
|
|||||||
return sm.State{}, err
|
return sm.State{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
h.nBlocks += 1
|
h.nBlocks++
|
||||||
|
|
||||||
return state, nil
|
return state, nil
|
||||||
}
|
}
|
||||||
@@ -429,7 +455,7 @@ type mockProxyApp struct {
|
|||||||
|
|
||||||
func (mock *mockProxyApp) DeliverTx(tx []byte) abci.ResponseDeliverTx {
|
func (mock *mockProxyApp) DeliverTx(tx []byte) abci.ResponseDeliverTx {
|
||||||
r := mock.abciResponses.DeliverTx[mock.txCount]
|
r := mock.abciResponses.DeliverTx[mock.txCount]
|
||||||
mock.txCount += 1
|
mock.txCount++
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -29,6 +29,7 @@ const (
|
|||||||
//--------------------------------------------------------
|
//--------------------------------------------------------
|
||||||
// replay messages interactively or all at once
|
// replay messages interactively or all at once
|
||||||
|
|
||||||
|
// replay the wal file
|
||||||
func RunReplayFile(config cfg.BaseConfig, csConfig *cfg.ConsensusConfig, console bool) {
|
func RunReplayFile(config cfg.BaseConfig, csConfig *cfg.ConsensusConfig, console bool) {
|
||||||
consensusState := newConsensusStateForReplay(config, csConfig)
|
consensusState := newConsensusStateForReplay(config, csConfig)
|
||||||
|
|
||||||
@@ -87,9 +88,9 @@ func (cs *ConsensusState) ReplayFile(file string, console bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if nextN > 0 {
|
if nextN > 0 {
|
||||||
nextN -= 1
|
nextN--
|
||||||
}
|
}
|
||||||
pb.count += 1
|
pb.count++
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -153,7 +154,7 @@ func (pb *playback) replayReset(count int, newStepCh chan interface{}) error {
|
|||||||
if err := pb.cs.readReplayMessage(msg, newStepCh); err != nil {
|
if err := pb.cs.readReplayMessage(msg, newStepCh); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
pb.count += 1
|
pb.count++
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -197,13 +198,12 @@ func (pb *playback) replayConsoleLoop() int {
|
|||||||
|
|
||||||
if len(tokens) == 1 {
|
if len(tokens) == 1 {
|
||||||
return 0
|
return 0
|
||||||
|
}
|
||||||
|
i, err := strconv.Atoi(tokens[1])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("next takes an integer argument")
|
||||||
} else {
|
} else {
|
||||||
i, err := strconv.Atoi(tokens[1])
|
return i
|
||||||
if err != nil {
|
|
||||||
fmt.Println("next takes an integer argument")
|
|
||||||
} else {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "back":
|
case "back":
|
||||||
@@ -263,7 +263,7 @@ func (pb *playback) replayConsoleLoop() int {
|
|||||||
case "locked_block":
|
case "locked_block":
|
||||||
fmt.Printf("%v %v\n", rs.LockedBlockParts.StringShort(), rs.LockedBlock.StringShort())
|
fmt.Printf("%v %v\n", rs.LockedBlockParts.StringShort(), rs.LockedBlock.StringShort())
|
||||||
case "votes":
|
case "votes":
|
||||||
fmt.Println(rs.Votes.StringIndented(" "))
|
fmt.Println(rs.Votes.StringIndented(" "))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
fmt.Println("Unknown option", tokens[1])
|
fmt.Println("Unknown option", tokens[1])
|
||||||
@@ -299,7 +299,7 @@ func newConsensusStateForReplay(config cfg.BaseConfig, csConfig *cfg.ConsensusCo
|
|||||||
// Create proxyAppConn connection (consensus, mempool, query)
|
// Create proxyAppConn connection (consensus, mempool, query)
|
||||||
clientCreator := proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir())
|
clientCreator := proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir())
|
||||||
proxyApp := proxy.NewAppConns(clientCreator,
|
proxyApp := proxy.NewAppConns(clientCreator,
|
||||||
NewHandshaker(stateDB, state, blockStore, gdoc.AppState()))
|
NewHandshaker(stateDB, state, blockStore, gdoc))
|
||||||
err = proxyApp.Start()
|
err = proxyApp.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmn.Exit(cmn.Fmt("Error starting proxy app conns: %v", err))
|
cmn.Exit(cmn.Fmt("Error starting proxy app conns: %v", err))
|
||||||
@@ -310,7 +310,7 @@ func newConsensusStateForReplay(config cfg.BaseConfig, csConfig *cfg.ConsensusCo
|
|||||||
cmn.Exit(cmn.Fmt("Failed to start event bus: %v", err))
|
cmn.Exit(cmn.Fmt("Failed to start event bus: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
mempool, evpool := types.MockMempool{}, types.MockEvidencePool{}
|
mempool, evpool := sm.MockMempool{}, sm.MockEvidencePool{}
|
||||||
blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool)
|
blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool)
|
||||||
|
|
||||||
consensusState := NewConsensusState(csConfig, state.Copy(), blockExec,
|
consensusState := NewConsensusState(csConfig, state.Copy(), blockExec,
|
||||||
|
@@ -13,17 +13,18 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/tendermint/abci/example/kvstore"
|
"github.com/tendermint/abci/example/kvstore"
|
||||||
abci "github.com/tendermint/abci/types"
|
abci "github.com/tendermint/abci/types"
|
||||||
crypto "github.com/tendermint/go-crypto"
|
crypto "github.com/tendermint/go-crypto"
|
||||||
wire "github.com/tendermint/go-wire"
|
|
||||||
auto "github.com/tendermint/tmlibs/autofile"
|
auto "github.com/tendermint/tmlibs/autofile"
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
dbm "github.com/tendermint/tmlibs/db"
|
dbm "github.com/tendermint/tmlibs/db"
|
||||||
|
|
||||||
cfg "github.com/tendermint/tendermint/config"
|
cfg "github.com/tendermint/tendermint/config"
|
||||||
|
"github.com/tendermint/tendermint/privval"
|
||||||
"github.com/tendermint/tendermint/proxy"
|
"github.com/tendermint/tendermint/proxy"
|
||||||
sm "github.com/tendermint/tendermint/state"
|
sm "github.com/tendermint/tendermint/state"
|
||||||
"github.com/tendermint/tendermint/types"
|
"github.com/tendermint/tendermint/types"
|
||||||
@@ -60,7 +61,7 @@ func startNewConsensusStateAndWaitForBlock(t *testing.T, lastBlockHeight int64,
|
|||||||
|
|
||||||
bytes, _ := ioutil.ReadFile(cs.config.WalFile())
|
bytes, _ := ioutil.ReadFile(cs.config.WalFile())
|
||||||
// fmt.Printf("====== WAL: \n\r%s\n", bytes)
|
// fmt.Printf("====== WAL: \n\r%s\n", bytes)
|
||||||
t.Logf("====== WAL: \n\r%s\n", bytes)
|
t.Logf("====== WAL: \n\r%X\n", bytes)
|
||||||
|
|
||||||
err := cs.Start()
|
err := cs.Start()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -218,15 +219,15 @@ func (e ReachedHeightToStopError) Error() string {
|
|||||||
return fmt.Sprintf("reached height to stop %d", e.height)
|
return fmt.Sprintf("reached height to stop %d", e.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save simulate WAL's crashing by sending an error to the panicCh and then
|
// Write simulate WAL's crashing by sending an error to the panicCh and then
|
||||||
// exiting the cs.receiveRoutine.
|
// exiting the cs.receiveRoutine.
|
||||||
func (w *crashingWAL) Save(m WALMessage) {
|
func (w *crashingWAL) Write(m WALMessage) {
|
||||||
if endMsg, ok := m.(EndHeightMessage); ok {
|
if endMsg, ok := m.(EndHeightMessage); ok {
|
||||||
if endMsg.Height == w.heightToStop {
|
if endMsg.Height == w.heightToStop {
|
||||||
w.panicCh <- ReachedHeightToStopError{endMsg.Height}
|
w.panicCh <- ReachedHeightToStopError{endMsg.Height}
|
||||||
runtime.Goexit()
|
runtime.Goexit()
|
||||||
} else {
|
} else {
|
||||||
w.next.Save(m)
|
w.next.Write(m)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -238,10 +239,14 @@ func (w *crashingWAL) Save(m WALMessage) {
|
|||||||
runtime.Goexit()
|
runtime.Goexit()
|
||||||
} else {
|
} else {
|
||||||
w.msgIndex++
|
w.msgIndex++
|
||||||
w.next.Save(m)
|
w.next.Write(m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *crashingWAL) WriteSync(m WALMessage) {
|
||||||
|
w.Write(m)
|
||||||
|
}
|
||||||
|
|
||||||
func (w *crashingWAL) Group() *auto.Group { return w.next.Group() }
|
func (w *crashingWAL) Group() *auto.Group { return w.next.Group() }
|
||||||
func (w *crashingWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (gr *auto.GroupReader, found bool, err error) {
|
func (w *crashingWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (gr *auto.GroupReader, found bool, err error) {
|
||||||
return w.next.SearchForEndHeight(height, options)
|
return w.next.SearchForEndHeight(height, options)
|
||||||
@@ -259,8 +264,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
mempool = types.MockMempool{}
|
mempool = sm.MockMempool{}
|
||||||
evpool = types.MockEvidencePool{}
|
evpool = sm.MockEvidencePool{}
|
||||||
)
|
)
|
||||||
|
|
||||||
//---------------------------------------
|
//---------------------------------------
|
||||||
@@ -325,9 +330,9 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) {
|
|||||||
walFile := tempWALWithData(walBody)
|
walFile := tempWALWithData(walBody)
|
||||||
config.Consensus.SetWalFile(walFile)
|
config.Consensus.SetWalFile(walFile)
|
||||||
|
|
||||||
privVal := types.LoadPrivValidatorFS(config.PrivValidatorFile())
|
privVal := privval.LoadFilePV(config.PrivValidatorFile())
|
||||||
|
|
||||||
wal, err := NewWAL(walFile, false)
|
wal, err := NewWAL(walFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -362,7 +367,8 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// now start the app using the handshake - it should sync
|
// now start the app using the handshake - it should sync
|
||||||
handshaker := NewHandshaker(stateDB, state, store, nil)
|
genDoc, _ := sm.MakeGenesisDocFromFile(config.GenesisFile())
|
||||||
|
handshaker := NewHandshaker(stateDB, state, store, genDoc)
|
||||||
proxyApp := proxy.NewAppConns(clientCreator2, handshaker)
|
proxyApp := proxy.NewAppConns(clientCreator2, handshaker)
|
||||||
if err := proxyApp.Start(); err != nil {
|
if err := proxyApp.Start(); err != nil {
|
||||||
t.Fatalf("Error starting proxy app connections: %v", err)
|
t.Fatalf("Error starting proxy app connections: %v", err)
|
||||||
@@ -382,9 +388,9 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) {
|
|||||||
|
|
||||||
expectedBlocksToSync := NUM_BLOCKS - nBlocks
|
expectedBlocksToSync := NUM_BLOCKS - nBlocks
|
||||||
if nBlocks == NUM_BLOCKS && mode > 0 {
|
if nBlocks == NUM_BLOCKS && mode > 0 {
|
||||||
expectedBlocksToSync += 1
|
expectedBlocksToSync++
|
||||||
} else if nBlocks > 0 && mode == 1 {
|
} else if nBlocks > 0 && mode == 1 {
|
||||||
expectedBlocksToSync += 1
|
expectedBlocksToSync++
|
||||||
}
|
}
|
||||||
|
|
||||||
if handshaker.NBlocks() != expectedBlocksToSync {
|
if handshaker.NBlocks() != expectedBlocksToSync {
|
||||||
@@ -412,10 +418,10 @@ func buildAppStateFromChain(proxyApp proxy.AppConns, stateDB dbm.DB,
|
|||||||
}
|
}
|
||||||
defer proxyApp.Stop()
|
defer proxyApp.Stop()
|
||||||
|
|
||||||
// TODO: get the genesis bytes (https://github.com/tendermint/tendermint/issues/1224)
|
|
||||||
var genesisBytes []byte
|
|
||||||
validators := types.TM2PB.Validators(state.Validators)
|
validators := types.TM2PB.Validators(state.Validators)
|
||||||
if _, err := proxyApp.Consensus().InitChainSync(abci.RequestInitChain{validators, genesisBytes}); err != nil {
|
if _, err := proxyApp.Consensus().InitChainSync(abci.RequestInitChain{
|
||||||
|
Validators: validators,
|
||||||
|
}); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,10 +455,10 @@ func buildTMStateFromChain(config *cfg.Config, stateDB dbm.DB, state sm.State, c
|
|||||||
}
|
}
|
||||||
defer proxyApp.Stop()
|
defer proxyApp.Stop()
|
||||||
|
|
||||||
// TODO: get the genesis bytes (https://github.com/tendermint/tendermint/issues/1224)
|
|
||||||
var genesisBytes []byte
|
|
||||||
validators := types.TM2PB.Validators(state.Validators)
|
validators := types.TM2PB.Validators(state.Validators)
|
||||||
if _, err := proxyApp.Consensus().InitChainSync(abci.RequestInitChain{validators, genesisBytes}); err != nil {
|
if _, err := proxyApp.Consensus().InitChainSync(abci.RequestInitChain{
|
||||||
|
Validators: validators,
|
||||||
|
}); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,8 +525,8 @@ func makeBlockchainFromWAL(wal WAL) ([]*types.Block, []*types.Commit, error) {
|
|||||||
case EndHeightMessage:
|
case EndHeightMessage:
|
||||||
// if its not the first one, we have a full block
|
// if its not the first one, we have a full block
|
||||||
if thisBlockParts != nil {
|
if thisBlockParts != nil {
|
||||||
var n int
|
var block = new(types.Block)
|
||||||
block := wire.ReadBinary(&types.Block{}, thisBlockParts.GetReader(), 0, &n, &err).(*types.Block)
|
_, err = cdc.UnmarshalBinaryReader(thisBlockParts.GetReader(), block, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -533,12 +539,12 @@ func makeBlockchainFromWAL(wal WAL) ([]*types.Block, []*types.Commit, error) {
|
|||||||
}
|
}
|
||||||
blocks = append(blocks, block)
|
blocks = append(blocks, block)
|
||||||
commits = append(commits, thisBlockCommit)
|
commits = append(commits, thisBlockCommit)
|
||||||
height += 1
|
height++
|
||||||
}
|
}
|
||||||
case *types.PartSetHeader:
|
case *types.PartSetHeader:
|
||||||
thisBlockParts = types.NewPartSetFromHeader(*p)
|
thisBlockParts = types.NewPartSetFromHeader(*p)
|
||||||
case *types.Part:
|
case *types.Part:
|
||||||
_, err := thisBlockParts.AddPart(p, false)
|
_, err := thisBlockParts.AddPart(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -552,8 +558,8 @@ func makeBlockchainFromWAL(wal WAL) ([]*types.Block, []*types.Commit, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// grab the last block too
|
// grab the last block too
|
||||||
var n int
|
var block = new(types.Block)
|
||||||
block := wire.ReadBinary(&types.Block{}, thisBlockParts.GetReader(), 0, &n, &err).(*types.Block)
|
_, err = cdc.UnmarshalBinaryReader(thisBlockParts.GetReader(), block, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -629,3 +635,53 @@ func (bs *mockBlockStore) LoadBlockCommit(height int64) *types.Commit {
|
|||||||
func (bs *mockBlockStore) LoadSeenCommit(height int64) *types.Commit {
|
func (bs *mockBlockStore) LoadSeenCommit(height int64) *types.Commit {
|
||||||
return bs.commits[height-1]
|
return bs.commits[height-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//----------------------------------------
|
||||||
|
|
||||||
|
func TestInitChainUpdateValidators(t *testing.T) {
|
||||||
|
val, _ := types.RandValidator(true, 10)
|
||||||
|
vals := types.NewValidatorSet([]*types.Validator{val})
|
||||||
|
app := &initChainApp{vals: types.TM2PB.Validators(vals)}
|
||||||
|
clientCreator := proxy.NewLocalClientCreator(app)
|
||||||
|
|
||||||
|
config := ResetConfig("proxy_test_")
|
||||||
|
privVal := privval.LoadFilePV(config.PrivValidatorFile())
|
||||||
|
stateDB, state, store := stateAndStore(config, privVal.GetPubKey())
|
||||||
|
|
||||||
|
oldValAddr := state.Validators.Validators[0].Address
|
||||||
|
|
||||||
|
// now start the app using the handshake - it should sync
|
||||||
|
genDoc, _ := sm.MakeGenesisDocFromFile(config.GenesisFile())
|
||||||
|
handshaker := NewHandshaker(stateDB, state, store, genDoc)
|
||||||
|
proxyApp := proxy.NewAppConns(clientCreator, handshaker)
|
||||||
|
if err := proxyApp.Start(); err != nil {
|
||||||
|
t.Fatalf("Error starting proxy app connections: %v", err)
|
||||||
|
}
|
||||||
|
defer proxyApp.Stop()
|
||||||
|
|
||||||
|
// reload the state, check the validator set was updated
|
||||||
|
state = sm.LoadState(stateDB)
|
||||||
|
|
||||||
|
newValAddr := state.Validators.Validators[0].Address
|
||||||
|
expectValAddr := val.Address
|
||||||
|
assert.NotEqual(t, oldValAddr, newValAddr)
|
||||||
|
assert.Equal(t, newValAddr, expectValAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newInitChainApp(vals []abci.Validator) *initChainApp {
|
||||||
|
return &initChainApp{
|
||||||
|
vals: vals,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the vals on InitChain
|
||||||
|
type initChainApp struct {
|
||||||
|
abci.BaseApplication
|
||||||
|
vals []abci.Validator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ica *initChainApp) InitChain(req abci.RequestInitChain) abci.ResponseInitChain {
|
||||||
|
return abci.ResponseInitChain{
|
||||||
|
Validators: ica.vals,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -10,13 +10,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
fail "github.com/ebuchman/fail-test"
|
fail "github.com/ebuchman/fail-test"
|
||||||
|
|
||||||
wire "github.com/tendermint/go-wire"
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
"github.com/tendermint/tmlibs/log"
|
"github.com/tendermint/tmlibs/log"
|
||||||
|
|
||||||
cfg "github.com/tendermint/tendermint/config"
|
cfg "github.com/tendermint/tendermint/config"
|
||||||
cstypes "github.com/tendermint/tendermint/consensus/types"
|
cstypes "github.com/tendermint/tendermint/consensus/types"
|
||||||
|
tmevents "github.com/tendermint/tendermint/libs/events"
|
||||||
"github.com/tendermint/tendermint/p2p"
|
"github.com/tendermint/tendermint/p2p"
|
||||||
sm "github.com/tendermint/tendermint/state"
|
sm "github.com/tendermint/tendermint/state"
|
||||||
"github.com/tendermint/tendermint/types"
|
"github.com/tendermint/tendermint/types"
|
||||||
@@ -77,9 +76,9 @@ type ConsensusState struct {
|
|||||||
// services for creating and executing blocks
|
// services for creating and executing blocks
|
||||||
// TODO: encapsulate all of this in one "BlockManager"
|
// TODO: encapsulate all of this in one "BlockManager"
|
||||||
blockExec *sm.BlockExecutor
|
blockExec *sm.BlockExecutor
|
||||||
blockStore types.BlockStore
|
blockStore sm.BlockStore
|
||||||
mempool types.Mempool
|
mempool sm.Mempool
|
||||||
evpool types.EvidencePool
|
evpool sm.EvidencePool
|
||||||
|
|
||||||
// internal state
|
// internal state
|
||||||
mtx sync.Mutex
|
mtx sync.Mutex
|
||||||
@@ -112,10 +111,14 @@ type ConsensusState struct {
|
|||||||
|
|
||||||
// closed when we finish shutting down
|
// closed when we finish shutting down
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
|
|
||||||
|
// synchronous pubsub between consensus state and reactor.
|
||||||
|
// state only emits EventNewRoundStep, EventVote and EventProposalHeartbeat
|
||||||
|
evsw tmevents.EventSwitch
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConsensusState returns a new ConsensusState.
|
// NewConsensusState returns a new ConsensusState.
|
||||||
func NewConsensusState(config *cfg.ConsensusConfig, state sm.State, blockExec *sm.BlockExecutor, blockStore types.BlockStore, mempool types.Mempool, evpool types.EvidencePool) *ConsensusState {
|
func NewConsensusState(config *cfg.ConsensusConfig, state sm.State, blockExec *sm.BlockExecutor, blockStore sm.BlockStore, mempool sm.Mempool, evpool sm.EvidencePool) *ConsensusState {
|
||||||
cs := &ConsensusState{
|
cs := &ConsensusState{
|
||||||
config: config,
|
config: config,
|
||||||
blockExec: blockExec,
|
blockExec: blockExec,
|
||||||
@@ -128,6 +131,7 @@ func NewConsensusState(config *cfg.ConsensusConfig, state sm.State, blockExec *s
|
|||||||
doWALCatchup: true,
|
doWALCatchup: true,
|
||||||
wal: nilWAL{},
|
wal: nilWAL{},
|
||||||
evpool: evpool,
|
evpool: evpool,
|
||||||
|
evsw: tmevents.NewEventSwitch(),
|
||||||
}
|
}
|
||||||
// set function defaults (may be overwritten before calling Start)
|
// set function defaults (may be overwritten before calling Start)
|
||||||
cs.decideProposal = cs.defaultDecideProposal
|
cs.decideProposal = cs.defaultDecideProposal
|
||||||
@@ -170,18 +174,31 @@ func (cs *ConsensusState) GetState() sm.State {
|
|||||||
return cs.state.Copy()
|
return cs.state.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRoundState returns a copy of the internal consensus state.
|
// GetRoundState returns a shallow copy of the internal consensus state.
|
||||||
func (cs *ConsensusState) GetRoundState() *cstypes.RoundState {
|
func (cs *ConsensusState) GetRoundState() *cstypes.RoundState {
|
||||||
cs.mtx.Lock()
|
cs.mtx.Lock()
|
||||||
defer cs.mtx.Unlock()
|
defer cs.mtx.Unlock()
|
||||||
return cs.getRoundState()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs *ConsensusState) getRoundState() *cstypes.RoundState {
|
|
||||||
rs := cs.RoundState // copy
|
rs := cs.RoundState // copy
|
||||||
return &rs
|
return &rs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRoundStateJSON returns a json of RoundState, marshalled using go-amino.
|
||||||
|
func (cs *ConsensusState) GetRoundStateJSON() ([]byte, error) {
|
||||||
|
cs.mtx.Lock()
|
||||||
|
defer cs.mtx.Unlock()
|
||||||
|
|
||||||
|
return cdc.MarshalJSON(cs.RoundState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoundStateSimpleJSON returns a json of RoundStateSimple, marshalled using go-amino.
|
||||||
|
func (cs *ConsensusState) GetRoundStateSimpleJSON() ([]byte, error) {
|
||||||
|
cs.mtx.Lock()
|
||||||
|
defer cs.mtx.Unlock()
|
||||||
|
|
||||||
|
return cdc.MarshalJSON(cs.RoundState.RoundStateSimple())
|
||||||
|
}
|
||||||
|
|
||||||
// GetValidators returns a copy of the current validators.
|
// GetValidators returns a copy of the current validators.
|
||||||
func (cs *ConsensusState) GetValidators() (int64, []*types.Validator) {
|
func (cs *ConsensusState) GetValidators() (int64, []*types.Validator) {
|
||||||
cs.mtx.Lock()
|
cs.mtx.Lock()
|
||||||
@@ -216,6 +233,10 @@ func (cs *ConsensusState) LoadCommit(height int64) *types.Commit {
|
|||||||
// OnStart implements cmn.Service.
|
// OnStart implements cmn.Service.
|
||||||
// It loads the latest state via the WAL, and starts the timeout and receive routines.
|
// It loads the latest state via the WAL, and starts the timeout and receive routines.
|
||||||
func (cs *ConsensusState) OnStart() error {
|
func (cs *ConsensusState) OnStart() error {
|
||||||
|
if err := cs.evsw.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// we may set the WAL in testing before calling Start,
|
// we may set the WAL in testing before calling Start,
|
||||||
// so only OpenWAL if its still the nilWAL
|
// so only OpenWAL if its still the nilWAL
|
||||||
if _, ok := cs.wal.(nilWAL); ok {
|
if _, ok := cs.wal.(nilWAL); ok {
|
||||||
@@ -233,8 +254,7 @@ func (cs *ConsensusState) OnStart() error {
|
|||||||
// NOTE: we will get a build up of garbage go routines
|
// NOTE: we will get a build up of garbage go routines
|
||||||
// firing on the tockChan until the receiveRoutine is started
|
// firing on the tockChan until the receiveRoutine is started
|
||||||
// to deal with them (by that point, at most one will be valid)
|
// to deal with them (by that point, at most one will be valid)
|
||||||
err := cs.timeoutTicker.Start()
|
if err := cs.timeoutTicker.Start(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,6 +293,8 @@ func (cs *ConsensusState) startRoutines(maxSteps int) {
|
|||||||
func (cs *ConsensusState) OnStop() {
|
func (cs *ConsensusState) OnStop() {
|
||||||
cs.BaseService.OnStop()
|
cs.BaseService.OnStop()
|
||||||
|
|
||||||
|
cs.evsw.Stop()
|
||||||
|
|
||||||
cs.timeoutTicker.Stop()
|
cs.timeoutTicker.Stop()
|
||||||
|
|
||||||
// Make BaseService.Wait() wait until cs.wal.Wait()
|
// Make BaseService.Wait() wait until cs.wal.Wait()
|
||||||
@@ -290,7 +312,7 @@ func (cs *ConsensusState) Wait() {
|
|||||||
|
|
||||||
// OpenWAL opens a file to log all consensus messages and timeouts for deterministic accountability
|
// OpenWAL opens a file to log all consensus messages and timeouts for deterministic accountability
|
||||||
func (cs *ConsensusState) OpenWAL(walFile string) (WAL, error) {
|
func (cs *ConsensusState) OpenWAL(walFile string) (WAL, error) {
|
||||||
wal, err := NewWAL(walFile, cs.config.WalLight)
|
wal, err := NewWAL(walFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cs.Logger.Error("Failed to open WAL for consensus state", "wal", walFile, "err", err)
|
cs.Logger.Error("Failed to open WAL for consensus state", "wal", walFile, "err", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -493,11 +515,12 @@ func (cs *ConsensusState) updateToState(state sm.State) {
|
|||||||
|
|
||||||
func (cs *ConsensusState) newStep() {
|
func (cs *ConsensusState) newStep() {
|
||||||
rs := cs.RoundStateEvent()
|
rs := cs.RoundStateEvent()
|
||||||
cs.wal.Save(rs)
|
cs.wal.Write(rs)
|
||||||
cs.nSteps += 1
|
cs.nSteps++
|
||||||
// newStep is called by updateToStep in NewConsensusState before the eventBus is set!
|
// newStep is called by updateToStep in NewConsensusState before the eventBus is set!
|
||||||
if cs.eventBus != nil {
|
if cs.eventBus != nil {
|
||||||
cs.eventBus.PublishEventNewRoundStep(rs)
|
cs.eventBus.PublishEventNewRoundStep(rs)
|
||||||
|
cs.evsw.FireEvent(types.EventNewRoundStep, &cs.RoundState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,16 +554,16 @@ func (cs *ConsensusState) receiveRoutine(maxSteps int) {
|
|||||||
case height := <-cs.mempool.TxsAvailable():
|
case height := <-cs.mempool.TxsAvailable():
|
||||||
cs.handleTxsAvailable(height)
|
cs.handleTxsAvailable(height)
|
||||||
case mi = <-cs.peerMsgQueue:
|
case mi = <-cs.peerMsgQueue:
|
||||||
cs.wal.Save(mi)
|
cs.wal.Write(mi)
|
||||||
// handles proposals, block parts, votes
|
// handles proposals, block parts, votes
|
||||||
// may generate internal events (votes, complete proposals, 2/3 majorities)
|
// may generate internal events (votes, complete proposals, 2/3 majorities)
|
||||||
cs.handleMsg(mi)
|
cs.handleMsg(mi)
|
||||||
case mi = <-cs.internalMsgQueue:
|
case mi = <-cs.internalMsgQueue:
|
||||||
cs.wal.Save(mi)
|
cs.wal.WriteSync(mi) // NOTE: fsync
|
||||||
// handles proposals, block parts, votes
|
// handles proposals, block parts, votes
|
||||||
cs.handleMsg(mi)
|
cs.handleMsg(mi)
|
||||||
case ti := <-cs.timeoutTicker.Chan(): // tockChan:
|
case ti := <-cs.timeoutTicker.Chan(): // tockChan:
|
||||||
cs.wal.Save(ti)
|
cs.wal.Write(ti)
|
||||||
// if the timeout is relevant to the rs
|
// if the timeout is relevant to the rs
|
||||||
// go to the next step
|
// go to the next step
|
||||||
cs.handleTimeout(ti, rs)
|
cs.handleTimeout(ti, rs)
|
||||||
@@ -573,8 +596,9 @@ func (cs *ConsensusState) handleMsg(mi msgInfo) {
|
|||||||
err = cs.setProposal(msg.Proposal)
|
err = cs.setProposal(msg.Proposal)
|
||||||
case *BlockPartMessage:
|
case *BlockPartMessage:
|
||||||
// if the proposal is complete, we'll enterPrevote or tryFinalizeCommit
|
// if the proposal is complete, we'll enterPrevote or tryFinalizeCommit
|
||||||
_, err = cs.addProposalBlockPart(msg.Height, msg.Part, peerID != "")
|
_, err = cs.addProposalBlockPart(msg.Height, msg.Part)
|
||||||
if err != nil && msg.Round != cs.Round {
|
if err != nil && msg.Round != cs.Round {
|
||||||
|
cs.Logger.Debug("Received block part from wrong round", "height", cs.Height, "csRound", cs.Round, "blockRound", msg.Round)
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
case *VoteMessage:
|
case *VoteMessage:
|
||||||
@@ -599,7 +623,7 @@ func (cs *ConsensusState) handleMsg(mi msgInfo) {
|
|||||||
cs.Logger.Error("Unknown msg type", reflect.TypeOf(msg))
|
cs.Logger.Error("Unknown msg type", reflect.TypeOf(msg))
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cs.Logger.Error("Error with msg", "type", reflect.TypeOf(msg), "peer", peerID, "err", err, "msg", msg)
|
cs.Logger.Error("Error with msg", "height", cs.Height, "round", cs.Round, "type", reflect.TypeOf(msg), "peer", peerID, "err", err, "msg", msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,16 +680,18 @@ func (cs *ConsensusState) handleTxsAvailable(height int64) {
|
|||||||
// Enter: +2/3 prevotes any or +2/3 precommits for block or any from (height, round)
|
// Enter: +2/3 prevotes any or +2/3 precommits for block or any from (height, round)
|
||||||
// NOTE: cs.StartTime was already set for height.
|
// NOTE: cs.StartTime was already set for height.
|
||||||
func (cs *ConsensusState) enterNewRound(height int64, round int) {
|
func (cs *ConsensusState) enterNewRound(height int64, round int) {
|
||||||
|
logger := cs.Logger.With("height", height, "round", round)
|
||||||
|
|
||||||
if cs.Height != height || round < cs.Round || (cs.Round == round && cs.Step != cstypes.RoundStepNewHeight) {
|
if cs.Height != height || round < cs.Round || (cs.Round == round && cs.Step != cstypes.RoundStepNewHeight) {
|
||||||
cs.Logger.Debug(cmn.Fmt("enterNewRound(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
|
logger.Debug(cmn.Fmt("enterNewRound(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if now := time.Now(); cs.StartTime.After(now) {
|
if now := time.Now(); cs.StartTime.After(now) {
|
||||||
cs.Logger.Info("Need to set a buffer and log message here for sanity.", "startTime", cs.StartTime, "now", now)
|
logger.Info("Need to set a buffer and log message here for sanity.", "startTime", cs.StartTime, "now", now)
|
||||||
}
|
}
|
||||||
|
|
||||||
cs.Logger.Info(cmn.Fmt("enterNewRound(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
|
logger.Info(cmn.Fmt("enterNewRound(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
|
||||||
|
|
||||||
// Increment validators if necessary
|
// Increment validators if necessary
|
||||||
validators := cs.Validators
|
validators := cs.Validators
|
||||||
@@ -684,6 +710,7 @@ func (cs *ConsensusState) enterNewRound(height int64, round int) {
|
|||||||
// and meanwhile we might have received a proposal
|
// and meanwhile we might have received a proposal
|
||||||
// for round 0.
|
// for round 0.
|
||||||
} else {
|
} else {
|
||||||
|
logger.Info("Resetting Proposal info")
|
||||||
cs.Proposal = nil
|
cs.Proposal = nil
|
||||||
cs.ProposalBlock = nil
|
cs.ProposalBlock = nil
|
||||||
cs.ProposalBlockParts = nil
|
cs.ProposalBlockParts = nil
|
||||||
@@ -720,11 +747,7 @@ func (cs *ConsensusState) needProofBlock(height int64) bool {
|
|||||||
func (cs *ConsensusState) proposalHeartbeat(height int64, round int) {
|
func (cs *ConsensusState) proposalHeartbeat(height int64, round int) {
|
||||||
counter := 0
|
counter := 0
|
||||||
addr := cs.privValidator.GetAddress()
|
addr := cs.privValidator.GetAddress()
|
||||||
valIndex, v := cs.Validators.GetByAddress(addr)
|
valIndex, _ := cs.Validators.GetByAddress(addr)
|
||||||
if v == nil {
|
|
||||||
// not a validator
|
|
||||||
valIndex = -1
|
|
||||||
}
|
|
||||||
chainID := cs.state.ChainID
|
chainID := cs.state.ChainID
|
||||||
for {
|
for {
|
||||||
rs := cs.GetRoundState()
|
rs := cs.GetRoundState()
|
||||||
@@ -741,7 +764,8 @@ func (cs *ConsensusState) proposalHeartbeat(height int64, round int) {
|
|||||||
}
|
}
|
||||||
cs.privValidator.SignHeartbeat(chainID, heartbeat)
|
cs.privValidator.SignHeartbeat(chainID, heartbeat)
|
||||||
cs.eventBus.PublishEventProposalHeartbeat(types.EventDataProposalHeartbeat{heartbeat})
|
cs.eventBus.PublishEventProposalHeartbeat(types.EventDataProposalHeartbeat{heartbeat})
|
||||||
counter += 1
|
cs.evsw.FireEvent(types.EventProposalHeartbeat, heartbeat)
|
||||||
|
counter++
|
||||||
time.Sleep(proposalHeartbeatIntervalSeconds * time.Second)
|
time.Sleep(proposalHeartbeatIntervalSeconds * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -750,11 +774,13 @@ func (cs *ConsensusState) proposalHeartbeat(height int64, round int) {
|
|||||||
// Enter (CreateEmptyBlocks, CreateEmptyBlocksInterval > 0 ): after enterNewRound(height,round), after timeout of CreateEmptyBlocksInterval
|
// Enter (CreateEmptyBlocks, CreateEmptyBlocksInterval > 0 ): after enterNewRound(height,round), after timeout of CreateEmptyBlocksInterval
|
||||||
// Enter (!CreateEmptyBlocks) : after enterNewRound(height,round), once txs are in the mempool
|
// Enter (!CreateEmptyBlocks) : after enterNewRound(height,round), once txs are in the mempool
|
||||||
func (cs *ConsensusState) enterPropose(height int64, round int) {
|
func (cs *ConsensusState) enterPropose(height int64, round int) {
|
||||||
|
logger := cs.Logger.With("height", height, "round", round)
|
||||||
|
|
||||||
if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPropose <= cs.Step) {
|
if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPropose <= cs.Step) {
|
||||||
cs.Logger.Debug(cmn.Fmt("enterPropose(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
|
logger.Debug(cmn.Fmt("enterPropose(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cs.Logger.Info(cmn.Fmt("enterPropose(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
|
logger.Info(cmn.Fmt("enterPropose(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// Done enterPropose:
|
// Done enterPropose:
|
||||||
@@ -774,22 +800,22 @@ func (cs *ConsensusState) enterPropose(height int64, round int) {
|
|||||||
|
|
||||||
// Nothing more to do if we're not a validator
|
// Nothing more to do if we're not a validator
|
||||||
if cs.privValidator == nil {
|
if cs.privValidator == nil {
|
||||||
cs.Logger.Debug("This node is not a validator")
|
logger.Debug("This node is not a validator")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// if not a validator, we're done
|
// if not a validator, we're done
|
||||||
if !cs.Validators.HasAddress(cs.privValidator.GetAddress()) {
|
if !cs.Validators.HasAddress(cs.privValidator.GetAddress()) {
|
||||||
cs.Logger.Debug("This node is not a validator")
|
logger.Debug("This node is not a validator", "addr", cs.privValidator.GetAddress(), "vals", cs.Validators)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cs.Logger.Debug("This node is a validator")
|
logger.Debug("This node is a validator")
|
||||||
|
|
||||||
if cs.isProposer() {
|
if cs.isProposer() {
|
||||||
cs.Logger.Info("enterPropose: Our turn to propose", "proposer", cs.Validators.GetProposer().Address, "privValidator", cs.privValidator)
|
logger.Info("enterPropose: Our turn to propose", "proposer", cs.Validators.GetProposer().Address, "privValidator", cs.privValidator)
|
||||||
cs.decideProposal(height, round)
|
cs.decideProposal(height, round)
|
||||||
} else {
|
} else {
|
||||||
cs.Logger.Info("enterPropose: Not our turn to propose", "proposer", cs.Validators.GetProposer().Address, "privValidator", cs.privValidator)
|
logger.Info("enterPropose: Not our turn to propose", "proposer", cs.Validators.GetProposer().Address, "privValidator", cs.privValidator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -852,10 +878,10 @@ func (cs *ConsensusState) isProposalComplete() bool {
|
|||||||
// make sure we have the prevotes from it too
|
// make sure we have the prevotes from it too
|
||||||
if cs.Proposal.POLRound < 0 {
|
if cs.Proposal.POLRound < 0 {
|
||||||
return true
|
return true
|
||||||
} else {
|
|
||||||
// if this is false the proposer is lying or we haven't received the POL yet
|
|
||||||
return cs.Votes.Prevotes(cs.Proposal.POLRound).HasTwoThirdsMajority()
|
|
||||||
}
|
}
|
||||||
|
// if this is false the proposer is lying or we haven't received the POL yet
|
||||||
|
return cs.Votes.Prevotes(cs.Proposal.POLRound).HasTwoThirdsMajority()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the next block to propose and return it.
|
// Create the next block to propose and return it.
|
||||||
@@ -952,14 +978,16 @@ func (cs *ConsensusState) defaultDoPrevote(height int64, round int) {
|
|||||||
|
|
||||||
// Enter: any +2/3 prevotes at next round.
|
// Enter: any +2/3 prevotes at next round.
|
||||||
func (cs *ConsensusState) enterPrevoteWait(height int64, round int) {
|
func (cs *ConsensusState) enterPrevoteWait(height int64, round int) {
|
||||||
|
logger := cs.Logger.With("height", height, "round", round)
|
||||||
|
|
||||||
if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPrevoteWait <= cs.Step) {
|
if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPrevoteWait <= cs.Step) {
|
||||||
cs.Logger.Debug(cmn.Fmt("enterPrevoteWait(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
|
logger.Debug(cmn.Fmt("enterPrevoteWait(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !cs.Votes.Prevotes(round).HasTwoThirdsAny() {
|
if !cs.Votes.Prevotes(round).HasTwoThirdsAny() {
|
||||||
cmn.PanicSanity(cmn.Fmt("enterPrevoteWait(%v/%v), but Prevotes does not have any +2/3 votes", height, round))
|
cmn.PanicSanity(cmn.Fmt("enterPrevoteWait(%v/%v), but Prevotes does not have any +2/3 votes", height, round))
|
||||||
}
|
}
|
||||||
cs.Logger.Info(cmn.Fmt("enterPrevoteWait(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
|
logger.Info(cmn.Fmt("enterPrevoteWait(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// Done enterPrevoteWait:
|
// Done enterPrevoteWait:
|
||||||
@@ -978,12 +1006,14 @@ func (cs *ConsensusState) enterPrevoteWait(height int64, round int) {
|
|||||||
// else, unlock an existing lock and precommit nil if +2/3 of prevotes were nil,
|
// else, unlock an existing lock and precommit nil if +2/3 of prevotes were nil,
|
||||||
// else, precommit nil otherwise.
|
// else, precommit nil otherwise.
|
||||||
func (cs *ConsensusState) enterPrecommit(height int64, round int) {
|
func (cs *ConsensusState) enterPrecommit(height int64, round int) {
|
||||||
|
logger := cs.Logger.With("height", height, "round", round)
|
||||||
|
|
||||||
if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPrecommit <= cs.Step) {
|
if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPrecommit <= cs.Step) {
|
||||||
cs.Logger.Debug(cmn.Fmt("enterPrecommit(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
|
logger.Debug(cmn.Fmt("enterPrecommit(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cs.Logger.Info(cmn.Fmt("enterPrecommit(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
|
logger.Info(cmn.Fmt("enterPrecommit(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// Done enterPrecommit:
|
// Done enterPrecommit:
|
||||||
@@ -991,23 +1021,24 @@ func (cs *ConsensusState) enterPrecommit(height int64, round int) {
|
|||||||
cs.newStep()
|
cs.newStep()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// check for a polka
|
||||||
blockID, ok := cs.Votes.Prevotes(round).TwoThirdsMajority()
|
blockID, ok := cs.Votes.Prevotes(round).TwoThirdsMajority()
|
||||||
|
|
||||||
// If we don't have a polka, we must precommit nil
|
// If we don't have a polka, we must precommit nil.
|
||||||
if !ok {
|
if !ok {
|
||||||
if cs.LockedBlock != nil {
|
if cs.LockedBlock != nil {
|
||||||
cs.Logger.Info("enterPrecommit: No +2/3 prevotes during enterPrecommit while we're locked. Precommitting nil")
|
logger.Info("enterPrecommit: No +2/3 prevotes during enterPrecommit while we're locked. Precommitting nil")
|
||||||
} else {
|
} else {
|
||||||
cs.Logger.Info("enterPrecommit: No +2/3 prevotes during enterPrecommit. Precommitting nil.")
|
logger.Info("enterPrecommit: No +2/3 prevotes during enterPrecommit. Precommitting nil.")
|
||||||
}
|
}
|
||||||
cs.signAddVote(types.VoteTypePrecommit, nil, types.PartSetHeader{})
|
cs.signAddVote(types.VoteTypePrecommit, nil, types.PartSetHeader{})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point +2/3 prevoted for a particular block or nil
|
// At this point +2/3 prevoted for a particular block or nil.
|
||||||
cs.eventBus.PublishEventPolka(cs.RoundStateEvent())
|
cs.eventBus.PublishEventPolka(cs.RoundStateEvent())
|
||||||
|
|
||||||
// the latest POLRound should be this round
|
// the latest POLRound should be this round.
|
||||||
polRound, _ := cs.Votes.POLInfo()
|
polRound, _ := cs.Votes.POLInfo()
|
||||||
if polRound < round {
|
if polRound < round {
|
||||||
cmn.PanicSanity(cmn.Fmt("This POLRound should be %v but got %", round, polRound))
|
cmn.PanicSanity(cmn.Fmt("This POLRound should be %v but got %", round, polRound))
|
||||||
@@ -1016,9 +1047,9 @@ func (cs *ConsensusState) enterPrecommit(height int64, round int) {
|
|||||||
// +2/3 prevoted nil. Unlock and precommit nil.
|
// +2/3 prevoted nil. Unlock and precommit nil.
|
||||||
if len(blockID.Hash) == 0 {
|
if len(blockID.Hash) == 0 {
|
||||||
if cs.LockedBlock == nil {
|
if cs.LockedBlock == nil {
|
||||||
cs.Logger.Info("enterPrecommit: +2/3 prevoted for nil.")
|
logger.Info("enterPrecommit: +2/3 prevoted for nil.")
|
||||||
} else {
|
} else {
|
||||||
cs.Logger.Info("enterPrecommit: +2/3 prevoted for nil. Unlocking")
|
logger.Info("enterPrecommit: +2/3 prevoted for nil. Unlocking")
|
||||||
cs.LockedRound = 0
|
cs.LockedRound = 0
|
||||||
cs.LockedBlock = nil
|
cs.LockedBlock = nil
|
||||||
cs.LockedBlockParts = nil
|
cs.LockedBlockParts = nil
|
||||||
@@ -1032,7 +1063,7 @@ func (cs *ConsensusState) enterPrecommit(height int64, round int) {
|
|||||||
|
|
||||||
// If we're already locked on that block, precommit it, and update the LockedRound
|
// If we're already locked on that block, precommit it, and update the LockedRound
|
||||||
if cs.LockedBlock.HashesTo(blockID.Hash) {
|
if cs.LockedBlock.HashesTo(blockID.Hash) {
|
||||||
cs.Logger.Info("enterPrecommit: +2/3 prevoted locked block. Relocking")
|
logger.Info("enterPrecommit: +2/3 prevoted locked block. Relocking")
|
||||||
cs.LockedRound = round
|
cs.LockedRound = round
|
||||||
cs.eventBus.PublishEventRelock(cs.RoundStateEvent())
|
cs.eventBus.PublishEventRelock(cs.RoundStateEvent())
|
||||||
cs.signAddVote(types.VoteTypePrecommit, blockID.Hash, blockID.PartsHeader)
|
cs.signAddVote(types.VoteTypePrecommit, blockID.Hash, blockID.PartsHeader)
|
||||||
@@ -1041,7 +1072,7 @@ func (cs *ConsensusState) enterPrecommit(height int64, round int) {
|
|||||||
|
|
||||||
// If +2/3 prevoted for proposal block, stage and precommit it
|
// If +2/3 prevoted for proposal block, stage and precommit it
|
||||||
if cs.ProposalBlock.HashesTo(blockID.Hash) {
|
if cs.ProposalBlock.HashesTo(blockID.Hash) {
|
||||||
cs.Logger.Info("enterPrecommit: +2/3 prevoted proposal block. Locking", "hash", blockID.Hash)
|
logger.Info("enterPrecommit: +2/3 prevoted proposal block. Locking", "hash", blockID.Hash)
|
||||||
// Validate the block.
|
// Validate the block.
|
||||||
if err := cs.blockExec.ValidateBlock(cs.state, cs.ProposalBlock); err != nil {
|
if err := cs.blockExec.ValidateBlock(cs.state, cs.ProposalBlock); err != nil {
|
||||||
cmn.PanicConsensus(cmn.Fmt("enterPrecommit: +2/3 prevoted for an invalid block: %v", err))
|
cmn.PanicConsensus(cmn.Fmt("enterPrecommit: +2/3 prevoted for an invalid block: %v", err))
|
||||||
@@ -1071,14 +1102,16 @@ func (cs *ConsensusState) enterPrecommit(height int64, round int) {
|
|||||||
|
|
||||||
// Enter: any +2/3 precommits for next round.
|
// Enter: any +2/3 precommits for next round.
|
||||||
func (cs *ConsensusState) enterPrecommitWait(height int64, round int) {
|
func (cs *ConsensusState) enterPrecommitWait(height int64, round int) {
|
||||||
|
logger := cs.Logger.With("height", height, "round", round)
|
||||||
|
|
||||||
if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPrecommitWait <= cs.Step) {
|
if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPrecommitWait <= cs.Step) {
|
||||||
cs.Logger.Debug(cmn.Fmt("enterPrecommitWait(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
|
logger.Debug(cmn.Fmt("enterPrecommitWait(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !cs.Votes.Precommits(round).HasTwoThirdsAny() {
|
if !cs.Votes.Precommits(round).HasTwoThirdsAny() {
|
||||||
cmn.PanicSanity(cmn.Fmt("enterPrecommitWait(%v/%v), but Precommits does not have any +2/3 votes", height, round))
|
cmn.PanicSanity(cmn.Fmt("enterPrecommitWait(%v/%v), but Precommits does not have any +2/3 votes", height, round))
|
||||||
}
|
}
|
||||||
cs.Logger.Info(cmn.Fmt("enterPrecommitWait(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
|
logger.Info(cmn.Fmt("enterPrecommitWait(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// Done enterPrecommitWait:
|
// Done enterPrecommitWait:
|
||||||
@@ -1093,11 +1126,13 @@ func (cs *ConsensusState) enterPrecommitWait(height int64, round int) {
|
|||||||
|
|
||||||
// Enter: +2/3 precommits for block
|
// Enter: +2/3 precommits for block
|
||||||
func (cs *ConsensusState) enterCommit(height int64, commitRound int) {
|
func (cs *ConsensusState) enterCommit(height int64, commitRound int) {
|
||||||
|
logger := cs.Logger.With("height", height, "commitRound", commitRound)
|
||||||
|
|
||||||
if cs.Height != height || cstypes.RoundStepCommit <= cs.Step {
|
if cs.Height != height || cstypes.RoundStepCommit <= cs.Step {
|
||||||
cs.Logger.Debug(cmn.Fmt("enterCommit(%v/%v): Invalid args. Current step: %v/%v/%v", height, commitRound, cs.Height, cs.Round, cs.Step))
|
logger.Debug(cmn.Fmt("enterCommit(%v/%v): Invalid args. Current step: %v/%v/%v", height, commitRound, cs.Height, cs.Round, cs.Step))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cs.Logger.Info(cmn.Fmt("enterCommit(%v/%v). Current: %v/%v/%v", height, commitRound, cs.Height, cs.Round, cs.Step))
|
logger.Info(cmn.Fmt("enterCommit(%v/%v). Current: %v/%v/%v", height, commitRound, cs.Height, cs.Round, cs.Step))
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// Done enterCommit:
|
// Done enterCommit:
|
||||||
@@ -1120,6 +1155,7 @@ func (cs *ConsensusState) enterCommit(height int64, commitRound int) {
|
|||||||
// Move them over to ProposalBlock if they match the commit hash,
|
// Move them over to ProposalBlock if they match the commit hash,
|
||||||
// otherwise they'll be cleared in updateToState.
|
// otherwise they'll be cleared in updateToState.
|
||||||
if cs.LockedBlock.HashesTo(blockID.Hash) {
|
if cs.LockedBlock.HashesTo(blockID.Hash) {
|
||||||
|
logger.Info("Commit is for locked block. Set ProposalBlock=LockedBlock", "blockHash", blockID.Hash)
|
||||||
cs.ProposalBlock = cs.LockedBlock
|
cs.ProposalBlock = cs.LockedBlock
|
||||||
cs.ProposalBlockParts = cs.LockedBlockParts
|
cs.ProposalBlockParts = cs.LockedBlockParts
|
||||||
}
|
}
|
||||||
@@ -1127,6 +1163,7 @@ func (cs *ConsensusState) enterCommit(height int64, commitRound int) {
|
|||||||
// If we don't have the block being committed, set up to get it.
|
// If we don't have the block being committed, set up to get it.
|
||||||
if !cs.ProposalBlock.HashesTo(blockID.Hash) {
|
if !cs.ProposalBlock.HashesTo(blockID.Hash) {
|
||||||
if !cs.ProposalBlockParts.HasHeader(blockID.PartsHeader) {
|
if !cs.ProposalBlockParts.HasHeader(blockID.PartsHeader) {
|
||||||
|
logger.Info("Commit is for a block we don't know about. Set ProposalBlock=nil", "proposal", cs.ProposalBlock.Hash(), "commit", blockID.Hash)
|
||||||
// We're getting the wrong block.
|
// We're getting the wrong block.
|
||||||
// Set up ProposalBlockParts and keep waiting.
|
// Set up ProposalBlockParts and keep waiting.
|
||||||
cs.ProposalBlock = nil
|
cs.ProposalBlock = nil
|
||||||
@@ -1139,19 +1176,21 @@ func (cs *ConsensusState) enterCommit(height int64, commitRound int) {
|
|||||||
|
|
||||||
// If we have the block AND +2/3 commits for it, finalize.
|
// If we have the block AND +2/3 commits for it, finalize.
|
||||||
func (cs *ConsensusState) tryFinalizeCommit(height int64) {
|
func (cs *ConsensusState) tryFinalizeCommit(height int64) {
|
||||||
|
logger := cs.Logger.With("height", height)
|
||||||
|
|
||||||
if cs.Height != height {
|
if cs.Height != height {
|
||||||
cmn.PanicSanity(cmn.Fmt("tryFinalizeCommit() cs.Height: %v vs height: %v", cs.Height, height))
|
cmn.PanicSanity(cmn.Fmt("tryFinalizeCommit() cs.Height: %v vs height: %v", cs.Height, height))
|
||||||
}
|
}
|
||||||
|
|
||||||
blockID, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority()
|
blockID, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority()
|
||||||
if !ok || len(blockID.Hash) == 0 {
|
if !ok || len(blockID.Hash) == 0 {
|
||||||
cs.Logger.Error("Attempt to finalize failed. There was no +2/3 majority, or +2/3 was for <nil>.", "height", height)
|
logger.Error("Attempt to finalize failed. There was no +2/3 majority, or +2/3 was for <nil>.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !cs.ProposalBlock.HashesTo(blockID.Hash) {
|
if !cs.ProposalBlock.HashesTo(blockID.Hash) {
|
||||||
// TODO: this happens every time if we're not a validator (ugly logs)
|
// TODO: this happens every time if we're not a validator (ugly logs)
|
||||||
// TODO: ^^ wait, why does it matter that we're a validator?
|
// TODO: ^^ wait, why does it matter that we're a validator?
|
||||||
cs.Logger.Info("Attempt to finalize failed. We don't have the commit block.", "height", height, "proposal-block", cs.ProposalBlock.Hash(), "commit-block", blockID.Hash)
|
logger.Info("Attempt to finalize failed. We don't have the commit block.", "proposal-block", cs.ProposalBlock.Hash(), "commit-block", blockID.Hash)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1202,23 +1241,28 @@ func (cs *ConsensusState) finalizeCommit(height int64) {
|
|||||||
|
|
||||||
fail.Fail() // XXX
|
fail.Fail() // XXX
|
||||||
|
|
||||||
// Finish writing to the WAL for this height.
|
// Write EndHeightMessage{} for this height, implying that the blockstore
|
||||||
// NOTE: If we fail before writing this, we'll never write it,
|
// has saved the block.
|
||||||
// and just recover by running ApplyBlock in the Handshake.
|
//
|
||||||
// If we moved it before persisting the block, we'd have to allow
|
// If we crash before writing this EndHeightMessage{}, we will recover by
|
||||||
// WAL replay for blocks with an #ENDHEIGHT
|
// running ApplyBlock during the ABCI handshake when we restart. If we
|
||||||
// As is, ConsensusState should not be started again
|
// didn't save the block to the blockstore before writing
|
||||||
// until we successfully call ApplyBlock (ie. here or in Handshake after restart)
|
// EndHeightMessage{}, we'd have to change WAL replay -- currently it
|
||||||
cs.wal.Save(EndHeightMessage{height})
|
// complains about replaying for heights where an #ENDHEIGHT entry already
|
||||||
|
// exists.
|
||||||
|
//
|
||||||
|
// Either way, the ConsensusState should not be resumed until we
|
||||||
|
// successfully call ApplyBlock (ie. later here, or in Handshake after
|
||||||
|
// restart).
|
||||||
|
cs.wal.WriteSync(EndHeightMessage{height}) // NOTE: fsync
|
||||||
|
|
||||||
fail.Fail() // XXX
|
fail.Fail() // XXX
|
||||||
|
|
||||||
// Create a copy of the state for staging
|
// Create a copy of the state for staging and an event cache for txs.
|
||||||
// and an event cache for txs
|
|
||||||
stateCopy := cs.state.Copy()
|
stateCopy := cs.state.Copy()
|
||||||
|
|
||||||
// Execute and commit the block, update and save the state, and update the mempool.
|
// Execute and commit the block, update and save the state, and update the mempool.
|
||||||
// NOTE: the block.AppHash wont reflect these txs until the next block
|
// NOTE The block.AppHash wont reflect these txs until the next block.
|
||||||
var err error
|
var err error
|
||||||
stateCopy, err = cs.blockExec.ApplyBlock(stateCopy, types.BlockID{block.Hash(), blockParts.Header()}, block)
|
stateCopy, err = cs.blockExec.ApplyBlock(stateCopy, types.BlockID{block.Hash(), blockParts.Header()}, block)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1279,34 +1323,56 @@ func (cs *ConsensusState) defaultSetProposal(proposal *types.Proposal) error {
|
|||||||
|
|
||||||
cs.Proposal = proposal
|
cs.Proposal = proposal
|
||||||
cs.ProposalBlockParts = types.NewPartSetFromHeader(proposal.BlockPartsHeader)
|
cs.ProposalBlockParts = types.NewPartSetFromHeader(proposal.BlockPartsHeader)
|
||||||
|
cs.Logger.Info("Received proposal", "proposal", proposal)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: block is not necessarily valid.
|
// NOTE: block is not necessarily valid.
|
||||||
// Asynchronously triggers either enterPrevote (before we timeout of propose) or tryFinalizeCommit, once we have the full block.
|
// Asynchronously triggers either enterPrevote (before we timeout of propose) or tryFinalizeCommit, once we have the full block.
|
||||||
func (cs *ConsensusState) addProposalBlockPart(height int64, part *types.Part, verify bool) (added bool, err error) {
|
func (cs *ConsensusState) addProposalBlockPart(height int64, part *types.Part) (added bool, err error) {
|
||||||
// Blocks might be reused, so round mismatch is OK
|
// Blocks might be reused, so round mismatch is OK
|
||||||
if cs.Height != height {
|
if cs.Height != height {
|
||||||
|
cs.Logger.Debug("Received block part from wrong height", "height", height)
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're not expecting a block part.
|
// We're not expecting a block part.
|
||||||
if cs.ProposalBlockParts == nil {
|
if cs.ProposalBlockParts == nil {
|
||||||
|
cs.Logger.Info("Received a block part when we're not expecting any", "height", height)
|
||||||
return false, nil // TODO: bad peer? Return error?
|
return false, nil // TODO: bad peer? Return error?
|
||||||
}
|
}
|
||||||
|
|
||||||
added, err = cs.ProposalBlockParts.AddPart(part, verify)
|
added, err = cs.ProposalBlockParts.AddPart(part)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return added, err
|
return added, err
|
||||||
}
|
}
|
||||||
if added && cs.ProposalBlockParts.IsComplete() {
|
if added && cs.ProposalBlockParts.IsComplete() {
|
||||||
// Added and completed!
|
// Added and completed!
|
||||||
var n int
|
_, err = cdc.UnmarshalBinaryReader(cs.ProposalBlockParts.GetReader(), &cs.ProposalBlock, int64(cs.state.ConsensusParams.BlockSize.MaxBytes))
|
||||||
var err error
|
if err != nil {
|
||||||
cs.ProposalBlock = wire.ReadBinary(&types.Block{}, cs.ProposalBlockParts.GetReader(),
|
return true, err
|
||||||
cs.state.ConsensusParams.BlockSize.MaxBytes, &n, &err).(*types.Block)
|
}
|
||||||
// NOTE: it's possible to receive complete proposal blocks for future rounds without having the proposal
|
// NOTE: it's possible to receive complete proposal blocks for future rounds without having the proposal
|
||||||
cs.Logger.Info("Received complete proposal block", "height", cs.ProposalBlock.Height, "hash", cs.ProposalBlock.Hash())
|
cs.Logger.Info("Received complete proposal block", "height", cs.ProposalBlock.Height, "hash", cs.ProposalBlock.Hash())
|
||||||
|
|
||||||
|
// Update Valid* if we can.
|
||||||
|
prevotes := cs.Votes.Prevotes(cs.Round)
|
||||||
|
blockID, hasTwoThirds := prevotes.TwoThirdsMajority()
|
||||||
|
if hasTwoThirds && !blockID.IsZero() && (cs.ValidRound < cs.Round) {
|
||||||
|
if cs.ProposalBlock.HashesTo(blockID.Hash) {
|
||||||
|
cs.Logger.Info("Updating valid block to new proposal block",
|
||||||
|
"valid-round", cs.Round, "valid-block-hash", cs.ProposalBlock.Hash())
|
||||||
|
cs.ValidRound = cs.Round
|
||||||
|
cs.ValidBlock = cs.ProposalBlock
|
||||||
|
cs.ValidBlockParts = cs.ProposalBlockParts
|
||||||
|
}
|
||||||
|
// TODO: In case there is +2/3 majority in Prevotes set for some
|
||||||
|
// block and cs.ProposalBlock contains different block, either
|
||||||
|
// proposer is faulty or voting power of faulty processes is more
|
||||||
|
// than 1/3. We should trigger in the future accountability
|
||||||
|
// procedure at this point.
|
||||||
|
}
|
||||||
|
|
||||||
if cs.Step == cstypes.RoundStepPropose && cs.isProposalComplete() {
|
if cs.Step == cstypes.RoundStepPropose && cs.isProposalComplete() {
|
||||||
// Move onto the next step
|
// Move onto the next step
|
||||||
cs.enterPrevote(height, cs.Round)
|
cs.enterPrevote(height, cs.Round)
|
||||||
@@ -1314,7 +1380,7 @@ func (cs *ConsensusState) addProposalBlockPart(height int64, part *types.Part, v
|
|||||||
// If we're waiting on the proposal block...
|
// If we're waiting on the proposal block...
|
||||||
cs.tryFinalizeCommit(height)
|
cs.tryFinalizeCommit(height)
|
||||||
}
|
}
|
||||||
return true, err
|
return true, nil
|
||||||
}
|
}
|
||||||
return added, nil
|
return added, nil
|
||||||
}
|
}
|
||||||
@@ -1365,6 +1431,7 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool,
|
|||||||
|
|
||||||
cs.Logger.Info(cmn.Fmt("Added to lastPrecommits: %v", cs.LastCommit.StringShort()))
|
cs.Logger.Info(cmn.Fmt("Added to lastPrecommits: %v", cs.LastCommit.StringShort()))
|
||||||
cs.eventBus.PublishEventVote(types.EventDataVote{vote})
|
cs.eventBus.PublishEventVote(types.EventDataVote{vote})
|
||||||
|
cs.evsw.FireEvent(types.EventVote, vote)
|
||||||
|
|
||||||
// if we can skip timeoutCommit and have all the votes now,
|
// if we can skip timeoutCommit and have all the votes now,
|
||||||
if cs.config.SkipTimeoutCommit && cs.LastCommit.HasAll() {
|
if cs.config.SkipTimeoutCommit && cs.LastCommit.HasAll() {
|
||||||
@@ -1392,38 +1459,50 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
cs.eventBus.PublishEventVote(types.EventDataVote{vote})
|
cs.eventBus.PublishEventVote(types.EventDataVote{vote})
|
||||||
|
cs.evsw.FireEvent(types.EventVote, vote)
|
||||||
|
|
||||||
switch vote.Type {
|
switch vote.Type {
|
||||||
case types.VoteTypePrevote:
|
case types.VoteTypePrevote:
|
||||||
prevotes := cs.Votes.Prevotes(vote.Round)
|
prevotes := cs.Votes.Prevotes(vote.Round)
|
||||||
cs.Logger.Info("Added to prevote", "vote", vote, "prevotes", prevotes.StringShort())
|
cs.Logger.Info("Added to prevote", "vote", vote, "prevotes", prevotes.StringShort())
|
||||||
blockID, ok := prevotes.TwoThirdsMajority()
|
|
||||||
// First, unlock if prevotes is a valid POL.
|
// If +2/3 prevotes for a block or nil for *any* round:
|
||||||
// >> lockRound < POLRound <= unlockOrChangeLockRound (see spec)
|
if blockID, ok := prevotes.TwoThirdsMajority(); ok {
|
||||||
// NOTE: If (lockRound < POLRound) but !(POLRound <= unlockOrChangeLockRound),
|
|
||||||
// we'll still enterNewRound(H,vote.R) and enterPrecommit(H,vote.R) to process it
|
// There was a polka!
|
||||||
// there.
|
// If we're locked but this is a recent polka, unlock.
|
||||||
if (cs.LockedBlock != nil) && (cs.LockedRound < vote.Round) && (vote.Round <= cs.Round) {
|
// If it matches our ProposalBlock, update the ValidBlock
|
||||||
if ok && !cs.LockedBlock.HashesTo(blockID.Hash) {
|
|
||||||
|
// Unlock if `cs.LockedRound < vote.Round <= cs.Round`
|
||||||
|
// NOTE: If vote.Round > cs.Round, we'll deal with it when we get to vote.Round
|
||||||
|
if (cs.LockedBlock != nil) &&
|
||||||
|
(cs.LockedRound < vote.Round) &&
|
||||||
|
(vote.Round <= cs.Round) &&
|
||||||
|
!cs.LockedBlock.HashesTo(blockID.Hash) {
|
||||||
|
|
||||||
cs.Logger.Info("Unlocking because of POL.", "lockedRound", cs.LockedRound, "POLRound", vote.Round)
|
cs.Logger.Info("Unlocking because of POL.", "lockedRound", cs.LockedRound, "POLRound", vote.Round)
|
||||||
cs.LockedRound = 0
|
cs.LockedRound = 0
|
||||||
cs.LockedBlock = nil
|
cs.LockedBlock = nil
|
||||||
cs.LockedBlockParts = nil
|
cs.LockedBlockParts = nil
|
||||||
cs.eventBus.PublishEventUnlock(cs.RoundStateEvent())
|
cs.eventBus.PublishEventUnlock(cs.RoundStateEvent())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Update ValidBlock
|
// Update Valid* if we can.
|
||||||
if ok && !blockID.IsZero() && !cs.ValidBlock.HashesTo(blockID.Hash) && vote.Round > cs.ValidRound {
|
// NOTE: our proposal block may be nil or not what received a polka..
|
||||||
// update valid value
|
// TODO: we may want to still update the ValidBlock and obtain it via gossipping
|
||||||
if cs.ProposalBlock.HashesTo(blockID.Hash) {
|
if !blockID.IsZero() &&
|
||||||
|
(cs.ValidRound < vote.Round) &&
|
||||||
|
(vote.Round <= cs.Round) &&
|
||||||
|
cs.ProposalBlock.HashesTo(blockID.Hash) {
|
||||||
|
|
||||||
|
cs.Logger.Info("Updating ValidBlock because of POL.", "validRound", cs.ValidRound, "POLRound", vote.Round)
|
||||||
cs.ValidRound = vote.Round
|
cs.ValidRound = vote.Round
|
||||||
cs.ValidBlock = cs.ProposalBlock
|
cs.ValidBlock = cs.ProposalBlock
|
||||||
cs.ValidBlockParts = cs.ProposalBlockParts
|
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 +2/3 prevotes for *anything* for this or future round:
|
||||||
if cs.Round <= vote.Round && prevotes.HasTwoThirdsAny() {
|
if cs.Round <= vote.Round && prevotes.HasTwoThirdsAny() {
|
||||||
// Round-skip over to PrevoteWait or goto Precommit.
|
// Round-skip over to PrevoteWait or goto Precommit.
|
||||||
cs.enterNewRound(height, vote.Round) // if the vote is ahead of us
|
cs.enterNewRound(height, vote.Round) // if the vote is ahead of us
|
||||||
@@ -1439,6 +1518,7 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool,
|
|||||||
cs.enterPrevote(height, cs.Round)
|
cs.enterPrevote(height, cs.Round)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case types.VoteTypePrecommit:
|
case types.VoteTypePrecommit:
|
||||||
precommits := cs.Votes.Precommits(vote.Round)
|
precommits := cs.Votes.Precommits(vote.Round)
|
||||||
cs.Logger.Info("Added to precommit", "vote", vote, "precommits", precommits.StringShort())
|
cs.Logger.Info("Added to precommit", "vote", vote, "precommits", precommits.StringShort())
|
||||||
@@ -1498,12 +1578,11 @@ func (cs *ConsensusState) signAddVote(type_ byte, hash []byte, header types.Part
|
|||||||
cs.sendInternalMessage(msgInfo{&VoteMessage{vote}, ""})
|
cs.sendInternalMessage(msgInfo{&VoteMessage{vote}, ""})
|
||||||
cs.Logger.Info("Signed and pushed vote", "height", cs.Height, "round", cs.Round, "vote", vote, "err", err)
|
cs.Logger.Info("Signed and pushed vote", "height", cs.Height, "round", cs.Round, "vote", vote, "err", err)
|
||||||
return vote
|
return vote
|
||||||
} else {
|
|
||||||
//if !cs.replayMode {
|
|
||||||
cs.Logger.Error("Error signing vote", "height", cs.Height, "round", cs.Round, "vote", vote, "err", err)
|
|
||||||
//}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
//if !cs.replayMode {
|
||||||
|
cs.Logger.Error("Error signing vote", "height", cs.Height, "round", cs.Round, "vote", vote, "err", err)
|
||||||
|
//}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------
|
//---------------------------------------------------------
|
||||||
|
@@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/tendermint/tendermint/types"
|
"github.com/tendermint/tendermint/types"
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
"github.com/tendermint/tmlibs/log"
|
"github.com/tendermint/tmlibs/log"
|
||||||
tmpubsub "github.com/tendermint/tmlibs/pubsub"
|
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -261,7 +261,7 @@ func TestStateFullRound1(t *testing.T) {
|
|||||||
|
|
||||||
// grab proposal
|
// grab proposal
|
||||||
re := <-propCh
|
re := <-propCh
|
||||||
propBlockHash := re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState).ProposalBlock.Hash()
|
propBlockHash := re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState).ProposalBlock.Hash()
|
||||||
|
|
||||||
<-voteCh // wait for prevote
|
<-voteCh // wait for prevote
|
||||||
validatePrevote(t, cs, round, vss[0], propBlockHash)
|
validatePrevote(t, cs, round, vss[0], propBlockHash)
|
||||||
@@ -356,7 +356,7 @@ func TestStateLockNoPOL(t *testing.T) {
|
|||||||
cs1.startRoutines(0)
|
cs1.startRoutines(0)
|
||||||
|
|
||||||
re := <-proposalCh
|
re := <-proposalCh
|
||||||
rs := re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
rs := re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
||||||
theBlockHash := rs.ProposalBlock.Hash()
|
theBlockHash := rs.ProposalBlock.Hash()
|
||||||
|
|
||||||
<-voteCh // prevote
|
<-voteCh // prevote
|
||||||
@@ -396,7 +396,7 @@ func TestStateLockNoPOL(t *testing.T) {
|
|||||||
|
|
||||||
// now we're on a new round and not the proposer, so wait for timeout
|
// now we're on a new round and not the proposer, so wait for timeout
|
||||||
re = <-timeoutProposeCh
|
re = <-timeoutProposeCh
|
||||||
rs = re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
rs = re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
||||||
|
|
||||||
if rs.ProposalBlock != nil {
|
if rs.ProposalBlock != nil {
|
||||||
panic("Expected proposal block to be nil")
|
panic("Expected proposal block to be nil")
|
||||||
@@ -409,7 +409,7 @@ func TestStateLockNoPOL(t *testing.T) {
|
|||||||
validatePrevote(t, cs1, 1, vss[0], rs.LockedBlock.Hash())
|
validatePrevote(t, cs1, 1, vss[0], rs.LockedBlock.Hash())
|
||||||
|
|
||||||
// add a conflicting prevote from the other validator
|
// add a conflicting prevote from the other validator
|
||||||
signAddVotes(cs1, types.VoteTypePrevote, hash, rs.ProposalBlock.MakePartSet(partSize).Header(), vs2)
|
signAddVotes(cs1, types.VoteTypePrevote, hash, rs.LockedBlock.MakePartSet(partSize).Header(), vs2)
|
||||||
<-voteCh
|
<-voteCh
|
||||||
|
|
||||||
// now we're going to enter prevote again, but with invalid args
|
// now we're going to enter prevote again, but with invalid args
|
||||||
@@ -424,7 +424,7 @@ func TestStateLockNoPOL(t *testing.T) {
|
|||||||
|
|
||||||
// add conflicting precommit from vs2
|
// add conflicting precommit from vs2
|
||||||
// NOTE: in practice we should never get to a point where there are precommits for different blocks at the same round
|
// NOTE: in practice we should never get to a point where there are precommits for different blocks at the same round
|
||||||
signAddVotes(cs1, types.VoteTypePrecommit, hash, rs.ProposalBlock.MakePartSet(partSize).Header(), vs2)
|
signAddVotes(cs1, types.VoteTypePrecommit, hash, rs.LockedBlock.MakePartSet(partSize).Header(), vs2)
|
||||||
<-voteCh
|
<-voteCh
|
||||||
|
|
||||||
// (note we're entering precommit for a second time this round, but with invalid args
|
// (note we're entering precommit for a second time this round, but with invalid args
|
||||||
@@ -440,7 +440,7 @@ func TestStateLockNoPOL(t *testing.T) {
|
|||||||
incrementRound(vs2)
|
incrementRound(vs2)
|
||||||
|
|
||||||
re = <-proposalCh
|
re = <-proposalCh
|
||||||
rs = re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
rs = re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
||||||
|
|
||||||
// now we're on a new round and are the proposer
|
// now we're on a new round and are the proposer
|
||||||
if !bytes.Equal(rs.ProposalBlock.Hash(), rs.LockedBlock.Hash()) {
|
if !bytes.Equal(rs.ProposalBlock.Hash(), rs.LockedBlock.Hash()) {
|
||||||
@@ -529,7 +529,7 @@ func TestStateLockPOLRelock(t *testing.T) {
|
|||||||
|
|
||||||
<-newRoundCh
|
<-newRoundCh
|
||||||
re := <-proposalCh
|
re := <-proposalCh
|
||||||
rs := re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
rs := re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
||||||
theBlockHash := rs.ProposalBlock.Hash()
|
theBlockHash := rs.ProposalBlock.Hash()
|
||||||
|
|
||||||
<-voteCh // prevote
|
<-voteCh // prevote
|
||||||
@@ -605,9 +605,9 @@ func TestStateLockPOLRelock(t *testing.T) {
|
|||||||
discardFromChan(voteCh, 2)
|
discardFromChan(voteCh, 2)
|
||||||
|
|
||||||
be := <-newBlockCh
|
be := <-newBlockCh
|
||||||
b := be.(types.TMEventData).Unwrap().(types.EventDataNewBlockHeader)
|
b := be.(types.EventDataNewBlockHeader)
|
||||||
re = <-newRoundCh
|
re = <-newRoundCh
|
||||||
rs = re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
rs = re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
||||||
if rs.Height != 2 {
|
if rs.Height != 2 {
|
||||||
panic("Expected height to increment")
|
panic("Expected height to increment")
|
||||||
}
|
}
|
||||||
@@ -643,7 +643,7 @@ func TestStateLockPOLUnlock(t *testing.T) {
|
|||||||
startTestRound(cs1, cs1.Height, 0)
|
startTestRound(cs1, cs1.Height, 0)
|
||||||
<-newRoundCh
|
<-newRoundCh
|
||||||
re := <-proposalCh
|
re := <-proposalCh
|
||||||
rs := re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
rs := re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
||||||
theBlockHash := rs.ProposalBlock.Hash()
|
theBlockHash := rs.ProposalBlock.Hash()
|
||||||
|
|
||||||
<-voteCh // prevote
|
<-voteCh // prevote
|
||||||
@@ -669,7 +669,7 @@ func TestStateLockPOLUnlock(t *testing.T) {
|
|||||||
|
|
||||||
// timeout to new round
|
// timeout to new round
|
||||||
re = <-timeoutWaitCh
|
re = <-timeoutWaitCh
|
||||||
rs = re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
rs = re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
||||||
lockedBlockHash := rs.LockedBlock.Hash()
|
lockedBlockHash := rs.LockedBlock.Hash()
|
||||||
|
|
||||||
//XXX: this isnt guaranteed to get there before the timeoutPropose ...
|
//XXX: this isnt guaranteed to get there before the timeoutPropose ...
|
||||||
@@ -731,7 +731,7 @@ func TestStateLockPOLSafety1(t *testing.T) {
|
|||||||
startTestRound(cs1, cs1.Height, 0)
|
startTestRound(cs1, cs1.Height, 0)
|
||||||
<-newRoundCh
|
<-newRoundCh
|
||||||
re := <-proposalCh
|
re := <-proposalCh
|
||||||
rs := re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
rs := re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
||||||
propBlock := rs.ProposalBlock
|
propBlock := rs.ProposalBlock
|
||||||
|
|
||||||
<-voteCh // prevote
|
<-voteCh // prevote
|
||||||
@@ -781,7 +781,7 @@ func TestStateLockPOLSafety1(t *testing.T) {
|
|||||||
re = <-proposalCh
|
re = <-proposalCh
|
||||||
}
|
}
|
||||||
|
|
||||||
rs = re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
rs = re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
||||||
|
|
||||||
if rs.LockedBlock != nil {
|
if rs.LockedBlock != nil {
|
||||||
panic("we should not be locked!")
|
panic("we should not be locked!")
|
||||||
@@ -1033,7 +1033,7 @@ func TestStateHalt1(t *testing.T) {
|
|||||||
startTestRound(cs1, cs1.Height, 0)
|
startTestRound(cs1, cs1.Height, 0)
|
||||||
<-newRoundCh
|
<-newRoundCh
|
||||||
re := <-proposalCh
|
re := <-proposalCh
|
||||||
rs := re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
rs := re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
||||||
propBlock := rs.ProposalBlock
|
propBlock := rs.ProposalBlock
|
||||||
propBlockParts := propBlock.MakePartSet(partSize)
|
propBlockParts := propBlock.MakePartSet(partSize)
|
||||||
|
|
||||||
@@ -1056,7 +1056,7 @@ func TestStateHalt1(t *testing.T) {
|
|||||||
// timeout to new round
|
// timeout to new round
|
||||||
<-timeoutWaitCh
|
<-timeoutWaitCh
|
||||||
re = <-newRoundCh
|
re = <-newRoundCh
|
||||||
rs = re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
rs = re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
||||||
|
|
||||||
t.Log("### ONTO ROUND 1")
|
t.Log("### ONTO ROUND 1")
|
||||||
/*Round2
|
/*Round2
|
||||||
@@ -1074,7 +1074,7 @@ func TestStateHalt1(t *testing.T) {
|
|||||||
// receiving that precommit should take us straight to commit
|
// receiving that precommit should take us straight to commit
|
||||||
<-newBlockCh
|
<-newBlockCh
|
||||||
re = <-newRoundCh
|
re = <-newRoundCh
|
||||||
rs = re.(types.TMEventData).Unwrap().(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
rs = re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState)
|
||||||
|
|
||||||
if rs.Height != 2 {
|
if rs.Height != 2 {
|
||||||
panic("expected height to increment")
|
panic("expected height to increment")
|
||||||
|
@@ -174,6 +174,26 @@ func (hvs *HeightVoteSet) getVoteSet(round int, type_ byte) *types.VoteSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If a peer claims that it has 2/3 majority for given blockKey, call this.
|
||||||
|
// NOTE: if there are too many peers, or too much peer churn,
|
||||||
|
// this can cause memory issues.
|
||||||
|
// TODO: implement ability to remove peers too
|
||||||
|
func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ byte, peerID p2p.ID, blockID types.BlockID) error {
|
||||||
|
hvs.mtx.Lock()
|
||||||
|
defer hvs.mtx.Unlock()
|
||||||
|
if !types.IsVoteTypeValid(type_) {
|
||||||
|
return fmt.Errorf("SetPeerMaj23: Invalid vote type %v", type_)
|
||||||
|
}
|
||||||
|
voteSet := hvs.getVoteSet(round, type_)
|
||||||
|
if voteSet == nil {
|
||||||
|
return nil // something we don't know about yet
|
||||||
|
}
|
||||||
|
return voteSet.SetPeerMaj23(types.P2PID(peerID), blockID)
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------
|
||||||
|
// string and json
|
||||||
|
|
||||||
func (hvs *HeightVoteSet) String() string {
|
func (hvs *HeightVoteSet) String() string {
|
||||||
return hvs.StringIndented("")
|
return hvs.StringIndented("")
|
||||||
}
|
}
|
||||||
@@ -207,19 +227,35 @@ func (hvs *HeightVoteSet) StringIndented(indent string) string {
|
|||||||
indent)
|
indent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a peer claims that it has 2/3 majority for given blockKey, call this.
|
func (hvs *HeightVoteSet) MarshalJSON() ([]byte, error) {
|
||||||
// NOTE: if there are too many peers, or too much peer churn,
|
|
||||||
// this can cause memory issues.
|
|
||||||
// TODO: implement ability to remove peers too
|
|
||||||
func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ byte, peerID p2p.ID, blockID types.BlockID) error {
|
|
||||||
hvs.mtx.Lock()
|
hvs.mtx.Lock()
|
||||||
defer hvs.mtx.Unlock()
|
defer hvs.mtx.Unlock()
|
||||||
if !types.IsVoteTypeValid(type_) {
|
|
||||||
return fmt.Errorf("SetPeerMaj23: Invalid vote type %v", type_)
|
allVotes := hvs.toAllRoundVotes()
|
||||||
}
|
return cdc.MarshalJSON(allVotes)
|
||||||
voteSet := hvs.getVoteSet(round, type_)
|
}
|
||||||
if voteSet == nil {
|
|
||||||
return nil // something we don't know about yet
|
func (hvs *HeightVoteSet) toAllRoundVotes() []roundVotes {
|
||||||
}
|
totalRounds := hvs.round + 1
|
||||||
return voteSet.SetPeerMaj23(types.P2PID(peerID), blockID)
|
allVotes := make([]roundVotes, totalRounds)
|
||||||
|
// rounds 0 ~ hvs.round inclusive
|
||||||
|
for round := 0; round < totalRounds; round++ {
|
||||||
|
allVotes[round] = roundVotes{
|
||||||
|
Round: round,
|
||||||
|
Prevotes: hvs.roundVoteSets[round].Prevotes.VoteStrings(),
|
||||||
|
PrevotesBitArray: hvs.roundVoteSets[round].Prevotes.BitArrayString(),
|
||||||
|
Precommits: hvs.roundVoteSets[round].Precommits.VoteStrings(),
|
||||||
|
PrecommitsBitArray: hvs.roundVoteSets[round].Precommits.BitArrayString(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: all other peer catchup rounds
|
||||||
|
return allVotes
|
||||||
|
}
|
||||||
|
|
||||||
|
type roundVotes struct {
|
||||||
|
Round int `json:"round"`
|
||||||
|
Prevotes []string `json:"prevotes"`
|
||||||
|
PrevotesBitArray string `json:"prevotes_bit_array"`
|
||||||
|
Precommits []string `json:"precommits"`
|
||||||
|
PrecommitsBitArray string `json:"precommits_bit_array"`
|
||||||
}
|
}
|
||||||
|
@@ -48,7 +48,7 @@ func TestPeerCatchupRounds(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeVoteHR(t *testing.T, height int64, round int, privVals []*types.PrivValidatorFS, valIndex int) *types.Vote {
|
func makeVoteHR(t *testing.T, height int64, round int, privVals []types.PrivValidator, valIndex int) *types.Vote {
|
||||||
privVal := privVals[valIndex]
|
privVal := privVals[valIndex]
|
||||||
vote := &types.Vote{
|
vote := &types.Vote{
|
||||||
ValidatorAddress: privVal.GetAddress(),
|
ValidatorAddress: privVal.GetAddress(),
|
||||||
|
57
consensus/types/peer_round_state.go
Normal file
57
consensus/types/peer_round_state.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tendermint/tendermint/types"
|
||||||
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// PeerRoundState contains the known state of a peer.
|
||||||
|
// NOTE: Read-only when returned by PeerState.GetRoundState().
|
||||||
|
type PeerRoundState struct {
|
||||||
|
Height int64 `json:"height"` // Height peer is at
|
||||||
|
Round int `json:"round"` // Round peer is at, -1 if unknown.
|
||||||
|
Step RoundStepType `json:"step"` // Step peer is at
|
||||||
|
StartTime time.Time `json:"start_time"` // Estimated start of round 0 at this height
|
||||||
|
Proposal bool `json:"proposal"` // True if peer has proposal for this round
|
||||||
|
ProposalBlockPartsHeader types.PartSetHeader `json:"proposal_block_parts_header"` //
|
||||||
|
ProposalBlockParts *cmn.BitArray `json:"proposal_block_parts"` //
|
||||||
|
ProposalPOLRound int `json:"proposal_pol_round"` // Proposal's POL round. -1 if none.
|
||||||
|
ProposalPOL *cmn.BitArray `json:"proposal_pol"` // nil until ProposalPOLMessage received.
|
||||||
|
Prevotes *cmn.BitArray `json:"prevotes"` // All votes peer has for this round
|
||||||
|
Precommits *cmn.BitArray `json:"precommits"` // All precommits peer has for this round
|
||||||
|
LastCommitRound int `json:"last_commit_round"` // Round of commit for last height. -1 if none.
|
||||||
|
LastCommit *cmn.BitArray `json:"last_commit"` // All commit precommits of commit for last height.
|
||||||
|
CatchupCommitRound int `json:"catchup_commit_round"` // Round that we have commit for. Not necessarily unique. -1 if none.
|
||||||
|
CatchupCommit *cmn.BitArray `json:"catchup_commit"` // All commit precommits peer has for this height & CatchupCommitRound
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the PeerRoundState
|
||||||
|
func (prs PeerRoundState) String() string {
|
||||||
|
return prs.StringIndented("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringIndented returns a string representation of the PeerRoundState
|
||||||
|
func (prs PeerRoundState) StringIndented(indent string) string {
|
||||||
|
return fmt.Sprintf(`PeerRoundState{
|
||||||
|
%s %v/%v/%v @%v
|
||||||
|
%s Proposal %v -> %v
|
||||||
|
%s POL %v (round %v)
|
||||||
|
%s Prevotes %v
|
||||||
|
%s Precommits %v
|
||||||
|
%s LastCommit %v (round %v)
|
||||||
|
%s Catchup %v (round %v)
|
||||||
|
%s}`,
|
||||||
|
indent, prs.Height, prs.Round, prs.Step, prs.StartTime,
|
||||||
|
indent, prs.ProposalBlockPartsHeader, prs.ProposalBlockParts,
|
||||||
|
indent, prs.ProposalPOL, prs.ProposalPOLRound,
|
||||||
|
indent, prs.Prevotes,
|
||||||
|
indent, prs.Precommits,
|
||||||
|
indent, prs.LastCommit, prs.LastCommitRound,
|
||||||
|
indent, prs.CatchupCommit, prs.CatchupCommitRound,
|
||||||
|
indent)
|
||||||
|
}
|
@@ -1,57 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/types"
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// PeerRoundState contains the known state of a peer.
|
|
||||||
// NOTE: Read-only when returned by PeerState.GetRoundState().
|
|
||||||
type PeerRoundState struct {
|
|
||||||
Height int64 // Height peer is at
|
|
||||||
Round int // Round peer is at, -1 if unknown.
|
|
||||||
Step RoundStepType // Step peer is at
|
|
||||||
StartTime time.Time // Estimated start of round 0 at this height
|
|
||||||
Proposal bool // True if peer has proposal for this round
|
|
||||||
ProposalBlockPartsHeader types.PartSetHeader //
|
|
||||||
ProposalBlockParts *cmn.BitArray //
|
|
||||||
ProposalPOLRound int // Proposal's POL round. -1 if none.
|
|
||||||
ProposalPOL *cmn.BitArray // nil until ProposalPOLMessage received.
|
|
||||||
Prevotes *cmn.BitArray // All votes peer has for this round
|
|
||||||
Precommits *cmn.BitArray // All precommits peer has for this round
|
|
||||||
LastCommitRound int // Round of commit for last height. -1 if none.
|
|
||||||
LastCommit *cmn.BitArray // All commit precommits of commit for last height.
|
|
||||||
CatchupCommitRound int // Round that we have commit for. Not necessarily unique. -1 if none.
|
|
||||||
CatchupCommit *cmn.BitArray // All commit precommits peer has for this height & CatchupCommitRound
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a string representation of the PeerRoundState
|
|
||||||
func (prs PeerRoundState) String() string {
|
|
||||||
return prs.StringIndented("")
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringIndented returns a string representation of the PeerRoundState
|
|
||||||
func (prs PeerRoundState) StringIndented(indent string) string {
|
|
||||||
return fmt.Sprintf(`PeerRoundState{
|
|
||||||
%s %v/%v/%v @%v
|
|
||||||
%s Proposal %v -> %v
|
|
||||||
%s POL %v (round %v)
|
|
||||||
%s Prevotes %v
|
|
||||||
%s Precommits %v
|
|
||||||
%s LastCommit %v (round %v)
|
|
||||||
%s Catchup %v (round %v)
|
|
||||||
%s}`,
|
|
||||||
indent, prs.Height, prs.Round, prs.Step, prs.StartTime,
|
|
||||||
indent, prs.ProposalBlockPartsHeader, prs.ProposalBlockParts,
|
|
||||||
indent, prs.ProposalPOL, prs.ProposalPOLRound,
|
|
||||||
indent, prs.Prevotes,
|
|
||||||
indent, prs.Precommits,
|
|
||||||
indent, prs.LastCommit, prs.LastCommitRound,
|
|
||||||
indent, prs.CatchupCommit, prs.CatchupCommitRound,
|
|
||||||
indent)
|
|
||||||
}
|
|
@@ -1,10 +1,12 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/types"
|
"github.com/tendermint/tendermint/types"
|
||||||
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
@@ -13,6 +15,7 @@ import (
|
|||||||
// RoundStepType enumerates the state of the consensus state machine
|
// RoundStepType enumerates the state of the consensus state machine
|
||||||
type RoundStepType uint8 // These must be numeric, ordered.
|
type RoundStepType uint8 // These must be numeric, ordered.
|
||||||
|
|
||||||
|
// RoundStepType
|
||||||
const (
|
const (
|
||||||
RoundStepNewHeight = RoundStepType(0x01) // Wait til CommitTime + timeoutCommit
|
RoundStepNewHeight = RoundStepType(0x01) // Wait til CommitTime + timeoutCommit
|
||||||
RoundStepNewRound = RoundStepType(0x02) // Setup new round and go to RoundStepPropose
|
RoundStepNewRound = RoundStepType(0x02) // Setup new round and go to RoundStepPropose
|
||||||
@@ -52,43 +55,66 @@ func (rs RoundStepType) String() string {
|
|||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
// RoundState defines the internal consensus state.
|
// RoundState defines the internal consensus state.
|
||||||
// It is Immutable when returned from ConsensusState.GetRoundState()
|
|
||||||
// TODO: Actually, only the top pointer is copied,
|
|
||||||
// so access to field pointers is still racey
|
|
||||||
// NOTE: Not thread safe. Should only be manipulated by functions downstream
|
// NOTE: Not thread safe. Should only be manipulated by functions downstream
|
||||||
// of the cs.receiveRoutine
|
// of the cs.receiveRoutine
|
||||||
type RoundState struct {
|
type RoundState struct {
|
||||||
Height int64 // Height we are working on
|
Height int64 `json:"height"` // Height we are working on
|
||||||
Round int
|
Round int `json:"round"`
|
||||||
Step RoundStepType
|
Step RoundStepType `json:"step"`
|
||||||
StartTime time.Time
|
StartTime time.Time `json:"start_time"`
|
||||||
CommitTime time.Time // Subjective time when +2/3 precommits for Block at Round were found
|
CommitTime time.Time `json:"commit_time"` // Subjective time when +2/3 precommits for Block at Round were found
|
||||||
Validators *types.ValidatorSet
|
Validators *types.ValidatorSet `json:"validators"`
|
||||||
Proposal *types.Proposal
|
Proposal *types.Proposal `json:"proposal"`
|
||||||
ProposalBlock *types.Block
|
ProposalBlock *types.Block `json:"proposal_block"`
|
||||||
ProposalBlockParts *types.PartSet
|
ProposalBlockParts *types.PartSet `json:"proposal_block_parts"`
|
||||||
LockedRound int
|
LockedRound int `json:"locked_round"`
|
||||||
LockedBlock *types.Block
|
LockedBlock *types.Block `json:"locked_block"`
|
||||||
LockedBlockParts *types.PartSet
|
LockedBlockParts *types.PartSet `json:"locked_block_parts"`
|
||||||
ValidRound int
|
ValidRound int `json:"valid_round"` // Last known round with POL for non-nil valid block.
|
||||||
ValidBlock *types.Block
|
ValidBlock *types.Block `json:"valid_block"` // Last known block of POL mentioned above.
|
||||||
ValidBlockParts *types.PartSet
|
ValidBlockParts *types.PartSet `json:"valid_block_parts"` // Last known block parts of POL metnioned above.
|
||||||
Votes *HeightVoteSet
|
Votes *HeightVoteSet `json:"votes"`
|
||||||
CommitRound int //
|
CommitRound int `json:"commit_round"` //
|
||||||
LastCommit *types.VoteSet // Last precommits at Height-1
|
LastCommit *types.VoteSet `json:"last_commit"` // Last precommits at Height-1
|
||||||
LastValidators *types.ValidatorSet
|
LastValidators *types.ValidatorSet `json:"last_validators"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compressed version of the RoundState for use in RPC
|
||||||
|
type RoundStateSimple struct {
|
||||||
|
HeightRoundStep string `json:"height/round/step"`
|
||||||
|
StartTime time.Time `json:"start_time"`
|
||||||
|
ProposalBlockHash cmn.HexBytes `json:"proposal_block_hash"`
|
||||||
|
LockedBlockHash cmn.HexBytes `json:"locked_block_hash"`
|
||||||
|
ValidBlockHash cmn.HexBytes `json:"valid_block_hash"`
|
||||||
|
Votes json.RawMessage `json:"height_vote_set"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compress the RoundState to RoundStateSimple
|
||||||
|
func (rs *RoundState) RoundStateSimple() RoundStateSimple {
|
||||||
|
votesJSON, err := rs.Votes.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return RoundStateSimple{
|
||||||
|
HeightRoundStep: fmt.Sprintf("%d/%d/%d", rs.Height, rs.Round, rs.Step),
|
||||||
|
StartTime: rs.StartTime,
|
||||||
|
ProposalBlockHash: rs.ProposalBlock.Hash(),
|
||||||
|
LockedBlockHash: rs.LockedBlock.Hash(),
|
||||||
|
ValidBlockHash: rs.ValidBlock.Hash(),
|
||||||
|
Votes: votesJSON,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundStateEvent returns the H/R/S of the RoundState as an event.
|
// RoundStateEvent returns the H/R/S of the RoundState as an event.
|
||||||
func (rs *RoundState) RoundStateEvent() types.EventDataRoundState {
|
func (rs *RoundState) RoundStateEvent() types.EventDataRoundState {
|
||||||
// XXX: copy the RoundState
|
// XXX: copy the RoundState
|
||||||
// if we want to avoid this, we may need synchronous events after all
|
// if we want to avoid this, we may need synchronous events after all
|
||||||
rs_ := *rs
|
rsCopy := *rs
|
||||||
edrs := types.EventDataRoundState{
|
edrs := types.EventDataRoundState{
|
||||||
Height: rs.Height,
|
Height: rs.Height,
|
||||||
Round: rs.Round,
|
Round: rs.Round,
|
||||||
Step: rs.Step.String(),
|
Step: rs.Step.String(),
|
||||||
RoundState: &rs_,
|
RoundState: &rsCopy,
|
||||||
}
|
}
|
||||||
return edrs
|
return edrs
|
||||||
}
|
}
|
||||||
@@ -118,16 +144,16 @@ func (rs *RoundState) StringIndented(indent string) string {
|
|||||||
indent, rs.Height, rs.Round, rs.Step,
|
indent, rs.Height, rs.Round, rs.Step,
|
||||||
indent, rs.StartTime,
|
indent, rs.StartTime,
|
||||||
indent, rs.CommitTime,
|
indent, rs.CommitTime,
|
||||||
indent, rs.Validators.StringIndented(indent+" "),
|
indent, rs.Validators.StringIndented(indent+" "),
|
||||||
indent, rs.Proposal,
|
indent, rs.Proposal,
|
||||||
indent, rs.ProposalBlockParts.StringShort(), rs.ProposalBlock.StringShort(),
|
indent, rs.ProposalBlockParts.StringShort(), rs.ProposalBlock.StringShort(),
|
||||||
indent, rs.LockedRound,
|
indent, rs.LockedRound,
|
||||||
indent, rs.LockedBlockParts.StringShort(), rs.LockedBlock.StringShort(),
|
indent, rs.LockedBlockParts.StringShort(), rs.LockedBlock.StringShort(),
|
||||||
indent, rs.ValidRound,
|
indent, rs.ValidRound,
|
||||||
indent, rs.ValidBlockParts.StringShort(), rs.ValidBlock.StringShort(),
|
indent, rs.ValidBlockParts.StringShort(), rs.ValidBlock.StringShort(),
|
||||||
indent, rs.Votes.StringIndented(indent+" "),
|
indent, rs.Votes.StringIndented(indent+" "),
|
||||||
indent, rs.LastCommit.StringShort(),
|
indent, rs.LastCommit.StringShort(),
|
||||||
indent, rs.LastValidators.StringIndented(indent+" "),
|
indent, rs.LastValidators.StringIndented(indent+" "),
|
||||||
indent)
|
indent)
|
||||||
}
|
}
|
||||||
|
|
95
consensus/types/round_state_test.go
Normal file
95
consensus/types/round_state_test.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tendermint/go-amino"
|
||||||
|
"github.com/tendermint/go-crypto"
|
||||||
|
"github.com/tendermint/tendermint/types"
|
||||||
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkRoundStateDeepCopy(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
|
||||||
|
// Random validators
|
||||||
|
nval, ntxs := 100, 100
|
||||||
|
vset, _ := types.RandValidatorSet(nval, 1)
|
||||||
|
precommits := make([]*types.Vote, nval)
|
||||||
|
blockID := types.BlockID{
|
||||||
|
Hash: cmn.RandBytes(20),
|
||||||
|
PartsHeader: types.PartSetHeader{
|
||||||
|
Hash: cmn.RandBytes(20),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
sig := crypto.SignatureEd25519{}
|
||||||
|
for i := 0; i < nval; i++ {
|
||||||
|
precommits[i] = &types.Vote{
|
||||||
|
ValidatorAddress: types.Address(cmn.RandBytes(20)),
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
BlockID: blockID,
|
||||||
|
Signature: sig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
txs := make([]types.Tx, ntxs)
|
||||||
|
for i := 0; i < ntxs; i++ {
|
||||||
|
txs[i] = cmn.RandBytes(100)
|
||||||
|
}
|
||||||
|
// Random block
|
||||||
|
block := &types.Block{
|
||||||
|
Header: &types.Header{
|
||||||
|
ChainID: cmn.RandStr(12),
|
||||||
|
Time: time.Now(),
|
||||||
|
LastBlockID: blockID,
|
||||||
|
LastCommitHash: cmn.RandBytes(20),
|
||||||
|
DataHash: cmn.RandBytes(20),
|
||||||
|
ValidatorsHash: cmn.RandBytes(20),
|
||||||
|
ConsensusHash: cmn.RandBytes(20),
|
||||||
|
AppHash: cmn.RandBytes(20),
|
||||||
|
LastResultsHash: cmn.RandBytes(20),
|
||||||
|
EvidenceHash: cmn.RandBytes(20),
|
||||||
|
},
|
||||||
|
Data: &types.Data{
|
||||||
|
Txs: txs,
|
||||||
|
},
|
||||||
|
Evidence: types.EvidenceData{},
|
||||||
|
LastCommit: &types.Commit{
|
||||||
|
BlockID: blockID,
|
||||||
|
Precommits: precommits,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
parts := block.MakePartSet(4096)
|
||||||
|
// Random Proposal
|
||||||
|
proposal := &types.Proposal{
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
BlockPartsHeader: types.PartSetHeader{
|
||||||
|
Hash: cmn.RandBytes(20),
|
||||||
|
},
|
||||||
|
POLBlockID: blockID,
|
||||||
|
Signature: sig,
|
||||||
|
}
|
||||||
|
// Random HeightVoteSet
|
||||||
|
// TODO: hvs :=
|
||||||
|
|
||||||
|
rs := &RoundState{
|
||||||
|
StartTime: time.Now(),
|
||||||
|
CommitTime: time.Now(),
|
||||||
|
Validators: vset,
|
||||||
|
Proposal: proposal,
|
||||||
|
ProposalBlock: block,
|
||||||
|
ProposalBlockParts: parts,
|
||||||
|
LockedBlock: block,
|
||||||
|
LockedBlockParts: parts,
|
||||||
|
ValidBlock: block,
|
||||||
|
ValidBlockParts: parts,
|
||||||
|
Votes: nil, // TODO
|
||||||
|
LastCommit: nil, // TODO
|
||||||
|
LastValidators: vset,
|
||||||
|
}
|
||||||
|
b.StartTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
amino.DeepCopy(rs)
|
||||||
|
}
|
||||||
|
}
|
12
consensus/types/wire.go
Normal file
12
consensus/types/wire.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tendermint/go-amino"
|
||||||
|
"github.com/tendermint/go-crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cdc = amino.NewCodec()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
crypto.RegisterAmino(cdc)
|
||||||
|
}
|
@@ -1,7 +1,6 @@
|
|||||||
package consensus
|
package consensus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
@@ -11,7 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
wire "github.com/tendermint/go-wire"
|
amino "github.com/tendermint/go-amino"
|
||||||
"github.com/tendermint/tendermint/types"
|
"github.com/tendermint/tendermint/types"
|
||||||
auto "github.com/tendermint/tmlibs/autofile"
|
auto "github.com/tendermint/tmlibs/autofile"
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
@@ -38,20 +37,21 @@ type EndHeightMessage struct {
|
|||||||
|
|
||||||
type WALMessage interface{}
|
type WALMessage interface{}
|
||||||
|
|
||||||
var _ = wire.RegisterInterface(
|
func RegisterWALMessages(cdc *amino.Codec) {
|
||||||
struct{ WALMessage }{},
|
cdc.RegisterInterface((*WALMessage)(nil), nil)
|
||||||
wire.ConcreteType{types.EventDataRoundState{}, 0x01},
|
cdc.RegisterConcrete(types.EventDataRoundState{}, "tendermint/wal/EventDataRoundState", nil)
|
||||||
wire.ConcreteType{msgInfo{}, 0x02},
|
cdc.RegisterConcrete(msgInfo{}, "tendermint/wal/MsgInfo", nil)
|
||||||
wire.ConcreteType{timeoutInfo{}, 0x03},
|
cdc.RegisterConcrete(timeoutInfo{}, "tendermint/wal/TimeoutInfo", nil)
|
||||||
wire.ConcreteType{EndHeightMessage{}, 0x04},
|
cdc.RegisterConcrete(EndHeightMessage{}, "tendermint/wal/EndHeightMessage", nil)
|
||||||
)
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------
|
//--------------------------------------------------------
|
||||||
// Simple write-ahead logger
|
// Simple write-ahead logger
|
||||||
|
|
||||||
// WAL is an interface for any write-ahead logger.
|
// WAL is an interface for any write-ahead logger.
|
||||||
type WAL interface {
|
type WAL interface {
|
||||||
Save(WALMessage)
|
Write(WALMessage)
|
||||||
|
WriteSync(WALMessage)
|
||||||
Group() *auto.Group
|
Group() *auto.Group
|
||||||
SearchForEndHeight(height int64, options *WALSearchOptions) (gr *auto.GroupReader, found bool, err error)
|
SearchForEndHeight(height int64, options *WALSearchOptions) (gr *auto.GroupReader, found bool, err error)
|
||||||
|
|
||||||
@@ -68,12 +68,11 @@ type baseWAL struct {
|
|||||||
cmn.BaseService
|
cmn.BaseService
|
||||||
|
|
||||||
group *auto.Group
|
group *auto.Group
|
||||||
light bool // ignore block parts
|
|
||||||
|
|
||||||
enc *WALEncoder
|
enc *WALEncoder
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWAL(walFile string, light bool) (*baseWAL, error) {
|
func NewWAL(walFile string) (*baseWAL, error) {
|
||||||
err := cmn.EnsureDir(filepath.Dir(walFile), 0700)
|
err := cmn.EnsureDir(filepath.Dir(walFile), 0700)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to ensure WAL directory is in place")
|
return nil, errors.Wrap(err, "failed to ensure WAL directory is in place")
|
||||||
@@ -85,7 +84,6 @@ func NewWAL(walFile string, light bool) (*baseWAL, error) {
|
|||||||
}
|
}
|
||||||
wal := &baseWAL{
|
wal := &baseWAL{
|
||||||
group: group,
|
group: group,
|
||||||
light: light,
|
|
||||||
enc: NewWALEncoder(group),
|
enc: NewWALEncoder(group),
|
||||||
}
|
}
|
||||||
wal.BaseService = *cmn.NewBaseService(nil, "baseWAL", wal)
|
wal.BaseService = *cmn.NewBaseService(nil, "baseWAL", wal)
|
||||||
@@ -101,40 +99,42 @@ func (wal *baseWAL) OnStart() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if size == 0 {
|
} else if size == 0 {
|
||||||
wal.Save(EndHeightMessage{0})
|
wal.WriteSync(EndHeightMessage{0})
|
||||||
}
|
}
|
||||||
err = wal.group.Start()
|
err = wal.group.Start()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wal *baseWAL) OnStop() {
|
func (wal *baseWAL) OnStop() {
|
||||||
wal.BaseService.OnStop()
|
|
||||||
wal.group.Stop()
|
wal.group.Stop()
|
||||||
|
wal.group.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// called in newStep and for each pass in receiveRoutine
|
// Write is called in newStep and for each receive on the
|
||||||
func (wal *baseWAL) Save(msg WALMessage) {
|
// peerMsgQueue and the timeoutTicker.
|
||||||
|
// NOTE: does not call fsync()
|
||||||
|
func (wal *baseWAL) Write(msg WALMessage) {
|
||||||
if wal == nil {
|
if wal == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if wal.light {
|
|
||||||
// in light mode we only write new steps, timeouts, and our own votes (no proposals, block parts)
|
|
||||||
if mi, ok := msg.(msgInfo); ok {
|
|
||||||
if mi.PeerID != "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the wal message
|
// Write the wal message
|
||||||
if err := wal.enc.Encode(&TimedWALMessage{time.Now(), msg}); err != nil {
|
if err := wal.enc.Encode(&TimedWALMessage{time.Now(), msg}); err != nil {
|
||||||
cmn.PanicQ(cmn.Fmt("Error writing msg to consensus wal: %v \n\nMessage: %v", err, msg))
|
panic(cmn.Fmt("Error writing msg to consensus wal: %v \n\nMessage: %v", err, msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteSync is called when we receive a msg from ourselves
|
||||||
|
// so that we write to disk before sending signed messages.
|
||||||
|
// NOTE: calls fsync()
|
||||||
|
func (wal *baseWAL) WriteSync(msg WALMessage) {
|
||||||
|
if wal == nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: only flush when necessary
|
wal.Write(msg)
|
||||||
if err := wal.group.Flush(); err != nil {
|
if err := wal.group.Flush(); err != nil {
|
||||||
cmn.PanicQ(cmn.Fmt("Error flushing consensus wal buf to file. Error: %v \n", err))
|
panic(cmn.Fmt("Error flushing consensus wal buf to file. Error: %v \n", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,13 +144,14 @@ type WALSearchOptions struct {
|
|||||||
IgnoreDataCorruptionErrors bool
|
IgnoreDataCorruptionErrors bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchForEndHeight searches for the EndHeightMessage with the height and
|
// SearchForEndHeight searches for the EndHeightMessage with the given height
|
||||||
// returns an auto.GroupReader, whenever it was found or not and an error.
|
// and returns an auto.GroupReader, whenever it was found or not and an error.
|
||||||
// Group reader will be nil if found equals false.
|
// Group reader will be nil if found equals false.
|
||||||
//
|
//
|
||||||
// CONTRACT: caller must close group reader.
|
// CONTRACT: caller must close group reader.
|
||||||
func (wal *baseWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (gr *auto.GroupReader, found bool, err error) {
|
func (wal *baseWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (gr *auto.GroupReader, found bool, err error) {
|
||||||
var msg *TimedWALMessage
|
var msg *TimedWALMessage
|
||||||
|
lastHeightFound := int64(-1)
|
||||||
|
|
||||||
// NOTE: starting from the last file in the group because we're usually
|
// NOTE: starting from the last file in the group because we're usually
|
||||||
// searching for the last height. See replay.go
|
// searching for the last height. See replay.go
|
||||||
@@ -166,17 +167,25 @@ func (wal *baseWAL) SearchForEndHeight(height int64, options *WALSearchOptions)
|
|||||||
for {
|
for {
|
||||||
msg, err = dec.Decode()
|
msg, err = dec.Decode()
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
|
// OPTIMISATION: no need to look for height in older files if we've seen h < height
|
||||||
|
if lastHeightFound > 0 && lastHeightFound < height {
|
||||||
|
gr.Close()
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
// check next file
|
// check next file
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if options.IgnoreDataCorruptionErrors && IsDataCorruptionError(err) {
|
if options.IgnoreDataCorruptionErrors && IsDataCorruptionError(err) {
|
||||||
|
wal.Logger.Debug("Corrupted entry. Skipping...", "err", err)
|
||||||
// do nothing
|
// do nothing
|
||||||
|
continue
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
gr.Close()
|
gr.Close()
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if m, ok := msg.Msg.(EndHeightMessage); ok {
|
if m, ok := msg.Msg.(EndHeightMessage); ok {
|
||||||
|
lastHeightFound = m.Height
|
||||||
if m.Height == height { // found
|
if m.Height == height { // found
|
||||||
wal.Logger.Debug("Found", "height", height, "index", index)
|
wal.Logger.Debug("Found", "height", height, "index", index)
|
||||||
return gr, true, nil
|
return gr, true, nil
|
||||||
@@ -193,7 +202,7 @@ func (wal *baseWAL) SearchForEndHeight(height int64, options *WALSearchOptions)
|
|||||||
|
|
||||||
// A WALEncoder writes custom-encoded WAL messages to an output stream.
|
// A WALEncoder writes custom-encoded WAL messages to an output stream.
|
||||||
//
|
//
|
||||||
// Format: 4 bytes CRC sum + 4 bytes length + arbitrary-length value (go-wire encoded)
|
// Format: 4 bytes CRC sum + 4 bytes length + arbitrary-length value (go-amino encoded)
|
||||||
type WALEncoder struct {
|
type WALEncoder struct {
|
||||||
wr io.Writer
|
wr io.Writer
|
||||||
}
|
}
|
||||||
@@ -205,7 +214,7 @@ func NewWALEncoder(wr io.Writer) *WALEncoder {
|
|||||||
|
|
||||||
// Encode writes the custom encoding of v to the stream.
|
// Encode writes the custom encoding of v to the stream.
|
||||||
func (enc *WALEncoder) Encode(v *TimedWALMessage) error {
|
func (enc *WALEncoder) Encode(v *TimedWALMessage) error {
|
||||||
data := wire.BinaryBytes(v)
|
data := cdc.MustMarshalBinaryBare(v)
|
||||||
|
|
||||||
crc := crc32.Checksum(data, crc32c)
|
crc := crc32.Checksum(data, crc32c)
|
||||||
length := uint32(len(data))
|
length := uint32(len(data))
|
||||||
@@ -271,23 +280,17 @@ func (dec *WALDecoder) Decode() (*TimedWALMessage, error) {
|
|||||||
|
|
||||||
b = make([]byte, 4)
|
b = make([]byte, 4)
|
||||||
_, err = dec.rd.Read(b)
|
_, err = dec.rd.Read(b)
|
||||||
if err == io.EOF {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read length: %v", err)
|
return nil, fmt.Errorf("failed to read length: %v", err)
|
||||||
}
|
}
|
||||||
length := binary.BigEndian.Uint32(b)
|
length := binary.BigEndian.Uint32(b)
|
||||||
|
|
||||||
if length > maxMsgSizeBytes {
|
if length > maxMsgSizeBytes {
|
||||||
return nil, DataCorruptionError{fmt.Errorf("length %d exceeded maximum possible value of %d bytes", length, maxMsgSizeBytes)}
|
return nil, fmt.Errorf("length %d exceeded maximum possible value of %d bytes", length, maxMsgSizeBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
data := make([]byte, length)
|
data := make([]byte, length)
|
||||||
_, err = dec.rd.Read(data)
|
_, err = dec.rd.Read(data)
|
||||||
if err == io.EOF {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read data: %v", err)
|
return nil, fmt.Errorf("failed to read data: %v", err)
|
||||||
}
|
}
|
||||||
@@ -298,9 +301,8 @@ func (dec *WALDecoder) Decode() (*TimedWALMessage, error) {
|
|||||||
return nil, DataCorruptionError{fmt.Errorf("checksums do not match: (read: %v, actual: %v)", crc, actualCRC)}
|
return nil, DataCorruptionError{fmt.Errorf("checksums do not match: (read: %v, actual: %v)", crc, actualCRC)}
|
||||||
}
|
}
|
||||||
|
|
||||||
var nn int
|
var res = new(TimedWALMessage) // nolint: gosimple
|
||||||
var res *TimedWALMessage // nolint: gosimple
|
err = cdc.UnmarshalBinaryBare(data, res)
|
||||||
res = wire.ReadBinary(&TimedWALMessage{}, bytes.NewBuffer(data), int(length), &nn, &err).(*TimedWALMessage)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, DataCorruptionError{fmt.Errorf("failed to decode data: %v", err)}
|
return nil, DataCorruptionError{fmt.Errorf("failed to decode data: %v", err)}
|
||||||
}
|
}
|
||||||
@@ -310,8 +312,9 @@ func (dec *WALDecoder) Decode() (*TimedWALMessage, error) {
|
|||||||
|
|
||||||
type nilWAL struct{}
|
type nilWAL struct{}
|
||||||
|
|
||||||
func (nilWAL) Save(m WALMessage) {}
|
func (nilWAL) Write(m WALMessage) {}
|
||||||
func (nilWAL) Group() *auto.Group { return nil }
|
func (nilWAL) WriteSync(m WALMessage) {}
|
||||||
|
func (nilWAL) Group() *auto.Group { return nil }
|
||||||
func (nilWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (gr *auto.GroupReader, found bool, err error) {
|
func (nilWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (gr *auto.GroupReader, found bool, err error) {
|
||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,6 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -14,6 +13,7 @@ import (
|
|||||||
"github.com/tendermint/abci/example/kvstore"
|
"github.com/tendermint/abci/example/kvstore"
|
||||||
bc "github.com/tendermint/tendermint/blockchain"
|
bc "github.com/tendermint/tendermint/blockchain"
|
||||||
cfg "github.com/tendermint/tendermint/config"
|
cfg "github.com/tendermint/tendermint/config"
|
||||||
|
"github.com/tendermint/tendermint/privval"
|
||||||
"github.com/tendermint/tendermint/proxy"
|
"github.com/tendermint/tendermint/proxy"
|
||||||
sm "github.com/tendermint/tendermint/state"
|
sm "github.com/tendermint/tendermint/state"
|
||||||
"github.com/tendermint/tendermint/types"
|
"github.com/tendermint/tendermint/types"
|
||||||
@@ -40,7 +40,7 @@ func WALWithNBlocks(numBlocks int) (data []byte, err error) {
|
|||||||
// COPY PASTE FROM node.go WITH A FEW MODIFICATIONS
|
// COPY PASTE FROM node.go WITH A FEW MODIFICATIONS
|
||||||
// NOTE: we can't import node package because of circular dependency
|
// NOTE: we can't import node package because of circular dependency
|
||||||
privValidatorFile := config.PrivValidatorFile()
|
privValidatorFile := config.PrivValidatorFile()
|
||||||
privValidator := types.LoadOrGenPrivValidatorFS(privValidatorFile)
|
privValidator := privval.LoadOrGenFilePV(privValidatorFile)
|
||||||
genDoc, err := types.GenesisDocFromFile(config.GenesisFile())
|
genDoc, err := types.GenesisDocFromFile(config.GenesisFile())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to read genesis file")
|
return nil, errors.Wrap(err, "failed to read genesis file")
|
||||||
@@ -52,7 +52,7 @@ func WALWithNBlocks(numBlocks int) (data []byte, err error) {
|
|||||||
return nil, errors.Wrap(err, "failed to make genesis state")
|
return nil, errors.Wrap(err, "failed to make genesis state")
|
||||||
}
|
}
|
||||||
blockStore := bc.NewBlockStore(blockStoreDB)
|
blockStore := bc.NewBlockStore(blockStoreDB)
|
||||||
handshaker := NewHandshaker(stateDB, state, blockStore, genDoc.AppState())
|
handshaker := NewHandshaker(stateDB, state, blockStore, genDoc)
|
||||||
proxyApp := proxy.NewAppConns(proxy.NewLocalClientCreator(app), handshaker)
|
proxyApp := proxy.NewAppConns(proxy.NewLocalClientCreator(app), handshaker)
|
||||||
proxyApp.SetLogger(logger.With("module", "proxy"))
|
proxyApp.SetLogger(logger.With("module", "proxy"))
|
||||||
if err := proxyApp.Start(); err != nil {
|
if err := proxyApp.Start(); err != nil {
|
||||||
@@ -65,8 +65,8 @@ func WALWithNBlocks(numBlocks int) (data []byte, err error) {
|
|||||||
return nil, errors.Wrap(err, "failed to start event bus")
|
return nil, errors.Wrap(err, "failed to start event bus")
|
||||||
}
|
}
|
||||||
defer eventBus.Stop()
|
defer eventBus.Stop()
|
||||||
mempool := types.MockMempool{}
|
mempool := sm.MockMempool{}
|
||||||
evpool := types.MockEvidencePool{}
|
evpool := sm.MockEvidencePool{}
|
||||||
blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool)
|
blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool)
|
||||||
consensusState := NewConsensusState(config.Consensus, state.Copy(), blockExec, blockStore, mempool, evpool)
|
consensusState := NewConsensusState(config.Consensus, state.Copy(), blockExec, blockStore, mempool, evpool)
|
||||||
consensusState.SetLogger(logger)
|
consensusState.SetLogger(logger)
|
||||||
@@ -83,7 +83,7 @@ func WALWithNBlocks(numBlocks int) (data []byte, err error) {
|
|||||||
numBlocksWritten := make(chan struct{})
|
numBlocksWritten := make(chan struct{})
|
||||||
wal := newByteBufferWAL(logger, NewWALEncoder(wr), int64(numBlocks), numBlocksWritten)
|
wal := newByteBufferWAL(logger, NewWALEncoder(wr), int64(numBlocks), numBlocksWritten)
|
||||||
// see wal.go#103
|
// see wal.go#103
|
||||||
wal.Save(EndHeightMessage{0})
|
wal.Write(EndHeightMessage{0})
|
||||||
consensusState.wal = wal
|
consensusState.wal = wal
|
||||||
|
|
||||||
if err := consensusState.Start(); err != nil {
|
if err := consensusState.Start(); err != nil {
|
||||||
@@ -116,7 +116,7 @@ func makePathname() string {
|
|||||||
func randPort() int {
|
func randPort() int {
|
||||||
// returns between base and base + spread
|
// returns between base and base + spread
|
||||||
base, spread := 20000, 20000
|
base, spread := 20000, 20000
|
||||||
return base + rand.Intn(spread)
|
return base + cmn.RandIntn(spread)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeAddrs() (string, string, string) {
|
func makeAddrs() (string, string, string) {
|
||||||
@@ -166,7 +166,7 @@ func newByteBufferWAL(logger log.Logger, enc *WALEncoder, nBlocks int64, signalS
|
|||||||
// Save writes message to the internal buffer except when heightToStop is
|
// Save writes message to the internal buffer except when heightToStop is
|
||||||
// reached, in which case it will signal the caller via signalWhenStopsTo and
|
// reached, in which case it will signal the caller via signalWhenStopsTo and
|
||||||
// skip writing.
|
// skip writing.
|
||||||
func (w *byteBufferWAL) Save(m WALMessage) {
|
func (w *byteBufferWAL) Write(m WALMessage) {
|
||||||
if w.stopped {
|
if w.stopped {
|
||||||
w.logger.Debug("WAL already stopped. Not writing message", "msg", m)
|
w.logger.Debug("WAL already stopped. Not writing message", "msg", m)
|
||||||
return
|
return
|
||||||
@@ -189,6 +189,10 @@ func (w *byteBufferWAL) Save(m WALMessage) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *byteBufferWAL) WriteSync(m WALMessage) {
|
||||||
|
w.Write(m)
|
||||||
|
}
|
||||||
|
|
||||||
func (w *byteBufferWAL) Group() *auto.Group {
|
func (w *byteBufferWAL) Group() *auto.Group {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
@@ -3,11 +3,10 @@ package consensus
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"sync"
|
// "sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
wire "github.com/tendermint/go-wire"
|
|
||||||
"github.com/tendermint/tendermint/consensus/types"
|
"github.com/tendermint/tendermint/consensus/types"
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
@@ -36,7 +35,7 @@ func TestWALEncoderDecoder(t *testing.T) {
|
|||||||
decoded, err := dec.Decode()
|
decoded, err := dec.Decode()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, msg.Time.Truncate(time.Millisecond), decoded.Time)
|
assert.Equal(t, msg.Time.UTC(), decoded.Time)
|
||||||
assert.Equal(t, msg.Msg, decoded.Msg)
|
assert.Equal(t, msg.Msg, decoded.Msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,7 +47,7 @@ func TestWALSearchForEndHeight(t *testing.T) {
|
|||||||
}
|
}
|
||||||
walFile := tempWALWithData(walBody)
|
walFile := tempWALWithData(walBody)
|
||||||
|
|
||||||
wal, err := NewWAL(walFile, false)
|
wal, err := NewWAL(walFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -68,6 +67,7 @@ func TestWALSearchForEndHeight(t *testing.T) {
|
|||||||
assert.Equal(t, rs.Height, h+1, cmn.Fmt("wrong height"))
|
assert.Equal(t, rs.Height, h+1, cmn.Fmt("wrong height"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
var initOnce sync.Once
|
var initOnce sync.Once
|
||||||
|
|
||||||
func registerInterfacesOnce() {
|
func registerInterfacesOnce() {
|
||||||
@@ -78,6 +78,7 @@ func registerInterfacesOnce() {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func nBytes(n int) []byte {
|
func nBytes(n int) []byte {
|
||||||
buf := make([]byte, n)
|
buf := make([]byte, n)
|
||||||
@@ -86,7 +87,7 @@ func nBytes(n int) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func benchmarkWalDecode(b *testing.B, n int) {
|
func benchmarkWalDecode(b *testing.B, n int) {
|
||||||
registerInterfacesOnce()
|
// registerInterfacesOnce()
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
enc := NewWALEncoder(buf)
|
enc := NewWALEncoder(buf)
|
||||||
|
14
consensus/wire.go
Normal file
14
consensus/wire.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package consensus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tendermint/go-amino"
|
||||||
|
"github.com/tendermint/go-crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cdc = amino.NewCodec()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterConsensusMessages(cdc)
|
||||||
|
RegisterWALMessages(cdc)
|
||||||
|
crypto.RegisterAmino(cdc)
|
||||||
|
}
|
68
docker-compose.yml
Normal file
68
docker-compose.yml
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
node0:
|
||||||
|
container_name: node0
|
||||||
|
image: "tendermint/localnode"
|
||||||
|
ports:
|
||||||
|
- "46656-46657:46656-46657"
|
||||||
|
environment:
|
||||||
|
- ID=0
|
||||||
|
- LOG=$${LOG:-tendermint.log}
|
||||||
|
volumes:
|
||||||
|
- ./build:/tendermint:Z
|
||||||
|
networks:
|
||||||
|
localnet:
|
||||||
|
ipv4_address: 192.167.10.2
|
||||||
|
|
||||||
|
node1:
|
||||||
|
container_name: node1
|
||||||
|
image: "tendermint/localnode"
|
||||||
|
ports:
|
||||||
|
- "46659-46660:46656-46657"
|
||||||
|
environment:
|
||||||
|
- ID=1
|
||||||
|
- LOG=$${LOG:-tendermint.log}
|
||||||
|
volumes:
|
||||||
|
- ./build:/tendermint:Z
|
||||||
|
networks:
|
||||||
|
localnet:
|
||||||
|
ipv4_address: 192.167.10.3
|
||||||
|
|
||||||
|
node2:
|
||||||
|
container_name: node2
|
||||||
|
image: "tendermint/localnode"
|
||||||
|
environment:
|
||||||
|
- ID=2
|
||||||
|
- LOG=$${LOG:-tendermint.log}
|
||||||
|
ports:
|
||||||
|
- "46661-46662:46656-46657"
|
||||||
|
volumes:
|
||||||
|
- ./build:/tendermint:Z
|
||||||
|
networks:
|
||||||
|
localnet:
|
||||||
|
ipv4_address: 192.167.10.4
|
||||||
|
|
||||||
|
node3:
|
||||||
|
container_name: node3
|
||||||
|
image: "tendermint/localnode"
|
||||||
|
environment:
|
||||||
|
- ID=3
|
||||||
|
- LOG=$${LOG:-tendermint.log}
|
||||||
|
ports:
|
||||||
|
- "46663-46664:46656-46657"
|
||||||
|
volumes:
|
||||||
|
- ./build:/tendermint:Z
|
||||||
|
networks:
|
||||||
|
localnet:
|
||||||
|
ipv4_address: 192.167.10.5
|
||||||
|
|
||||||
|
networks:
|
||||||
|
localnet:
|
||||||
|
driver: bridge
|
||||||
|
ipam:
|
||||||
|
driver: default
|
||||||
|
config:
|
||||||
|
-
|
||||||
|
subnet: 192.167.10.0/16
|
||||||
|
|
@@ -183,6 +183,7 @@ Try running these commands:
|
|||||||
|
|
||||||
> commit
|
> commit
|
||||||
-> code: OK
|
-> code: OK
|
||||||
|
-> data.hex: 0x0000000000000000
|
||||||
|
|
||||||
> deliver_tx "abc"
|
> deliver_tx "abc"
|
||||||
-> code: OK
|
-> code: OK
|
||||||
@@ -194,7 +195,7 @@ Try running these commands:
|
|||||||
|
|
||||||
> commit
|
> commit
|
||||||
-> code: OK
|
-> code: OK
|
||||||
-> data.hex: 0x49DFD15CCDACDEAE9728CB01FBB5E8688CA58B91
|
-> data.hex: 0x0200000000000000
|
||||||
|
|
||||||
> query "abc"
|
> query "abc"
|
||||||
-> code: OK
|
-> code: OK
|
||||||
@@ -208,7 +209,7 @@ Try running these commands:
|
|||||||
|
|
||||||
> commit
|
> commit
|
||||||
-> code: OK
|
-> code: OK
|
||||||
-> data.hex: 0x70102DB32280373FBF3F9F89DA2A20CE2CD62B0B
|
-> data.hex: 0x0400000000000000
|
||||||
|
|
||||||
> query "def"
|
> query "def"
|
||||||
-> code: OK
|
-> code: OK
|
||||||
@@ -301,6 +302,7 @@ In another window, start the ``abci-cli console``:
|
|||||||
|
|
||||||
> set_option serial on
|
> set_option serial on
|
||||||
-> code: OK
|
-> code: OK
|
||||||
|
-> log: OK (SetOption doesn't return anything.)
|
||||||
|
|
||||||
> check_tx 0x00
|
> check_tx 0x00
|
||||||
-> code: OK
|
-> code: OK
|
||||||
|
@@ -1,125 +1,42 @@
|
|||||||
Application Architecture Guide
|
Application Architecture Guide
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
Overview
|
Here we provide a brief guide on the recommended architecture of a Tendermint blockchain
|
||||||
--------
|
application.
|
||||||
|
|
||||||
A blockchain application is more than the consensus engine and the
|
The following diagram provides a superb example:
|
||||||
transaction logic (eg. smart contracts, business logic) as implemented
|
|
||||||
in the ABCI app. There are also (mobile, web, desktop) clients that will
|
|
||||||
need to connect and make use of the app. We will assume for now that you
|
|
||||||
have a well designed transactions and database model, but maybe this
|
|
||||||
will be the topic of another article. This article is more interested in
|
|
||||||
various ways of setting up the "plumbing" and connecting these pieces,
|
|
||||||
and demonstrating some evolving best practices.
|
|
||||||
|
|
||||||
Security
|
https://drive.google.com/open?id=1yR2XpRi9YCY9H9uMfcw8-RMJpvDyvjz9
|
||||||
--------
|
|
||||||
|
|
||||||
A very important aspect when constructing a blockchain is security. The
|
The end-user application here is the Cosmos Voyager, at the bottom left.
|
||||||
consensus model can be DoSed (no consensus possible) by corrupting 1/3
|
Voyager communicates with a REST API exposed by a local Light-Client Daemon.
|
||||||
of the validators and exploited (writing arbitrary blocks) by corrupting
|
The Light-Client Daemon is an application specific program that communicates with
|
||||||
2/3 of the validators. So, while the security is not that of the
|
Tendermint nodes and verifies Tendermint light-client proofs through the Tendermint Core RPC.
|
||||||
"weakest link", you should take care that the "average link" is
|
The Tendermint Core process communicates with a local ABCI application, where the
|
||||||
sufficiently hardened.
|
user query or transaction is actually processed.
|
||||||
|
|
||||||
One big attack surface on the validators is the communication between
|
The ABCI application must be a deterministic result of the Tendermint consensus - any external influence
|
||||||
the ABCI app and the tendermint core. This should be highly protected.
|
on the application state that didn't come through Tendermint could cause a
|
||||||
Ideally, the app and the core are running on the same machine, so no
|
consensus failure. Thus *nothing* should communicate with the application except Tendermint via ABCI.
|
||||||
external agent can target the communication channel. You can use unix
|
|
||||||
sockets (with permissions preventing access from other users), or even
|
|
||||||
compile the two apps into one binary if the ABCI app is also writen in
|
|
||||||
go. If you are unable to do that due to language support, then the ABCI
|
|
||||||
app should bind a TCP connection to localhost (127.0.0.1), which is less
|
|
||||||
efficient and secure, but still not reachable from outside. If you must
|
|
||||||
run the ABCI app and tendermint core on separate machines, make sure you
|
|
||||||
have a secure communication channel (ssh tunnel?)
|
|
||||||
|
|
||||||
Now assuming, you have linked together your app and the core securely,
|
If the application is written in Go, it can be compiled into the Tendermint binary.
|
||||||
you must also make sure no one can get on the machine it is hosted on.
|
Otherwise, it should use a unix socket to communicate with Tendermint.
|
||||||
At this point it is basic network security. Run on a secure operating
|
If it's necessary to use TCP, extra care must be taken to encrypt and authenticate the connection.
|
||||||
system (SELinux?). Limit who has access to the machine (user accounts,
|
|
||||||
but also where the physical machine is hosted). Turn off all services
|
|
||||||
except for ssh, which should only be accessible by some well-guarded
|
|
||||||
public/private key pairs (no password). And maybe even firewall off
|
|
||||||
access to the ports used by the validators, so only known validators can
|
|
||||||
connect.
|
|
||||||
|
|
||||||
There was also a suggestion on slack from @jhon about compiling
|
All reads from the app happen through the Tendermint `/abci_query` endpoint.
|
||||||
everything together with a unikernel for more security, such as
|
All writes to the app happen through the Tendermint `/broadcast_tx_*` endpoints.
|
||||||
`Mirage <https://mirage.io>`__ or
|
|
||||||
`UNIK <https://github.com/emc-advanced-dev/unik>`__.
|
|
||||||
|
|
||||||
Connecting your client to the blockchain
|
The Light-Client Daemon is what provides light clients (end users) with nearly all the security of a full node.
|
||||||
----------------------------------------
|
It formats and broadcasts transactions, and verifies proofs of queries and transaction results.
|
||||||
|
Note that it need not be a daemon - the Light-Client logic could instead be implemented in the same process as the end-user application.
|
||||||
|
|
||||||
Tendermint Core RPC
|
Note for those ABCI applications with weaker security requirements, the functionality of the Light-Client Daemon can be moved
|
||||||
~~~~~~~~~~~~~~~~~~~
|
into the ABCI application process itself. That said, exposing the application process to anything besides Tendermint over ABCI
|
||||||
|
requires extreme caution, as all transactions, and possibly all queries, should still pass through Tendermint.
|
||||||
|
|
||||||
The concept is that the ABCI app is completely hidden from the outside
|
See the following for more extensive documentation:
|
||||||
world and only communicated through a tested and secured `interface
|
- [Interchain Standard for the Light-Client REST API](https://github.com/cosmos/cosmos-sdk/pull/1028)
|
||||||
exposed by the tendermint core <./specification/rpc.html>`__. This interface
|
- [Tendermint RPC Docs](https://tendermint.github.io/slate/)
|
||||||
exposes a lot of data on the block header and consensus process, which
|
- [Tendermint in Production](https://github.com/tendermint/tendermint/pull/1618)
|
||||||
is quite useful for externally verifying the system. It also includes
|
- [Tendermint Basics](https://tendermint.readthedocs.io/en/master/using-tendermint.html)
|
||||||
3(!) methods to broadcast a transaction (propose it for the blockchain,
|
- [ABCI spec](https://github.com/tendermint/abci/blob/master/specification.rst)
|
||||||
and possibly await a response). And one method to query app-specific
|
|
||||||
data from the ABCI application.
|
|
||||||
|
|
||||||
Pros:
|
|
||||||
* Server code already written
|
|
||||||
* Access to block headers to validate merkle proofs (nice for light clients)
|
|
||||||
* Basic read/write functionality is supported
|
|
||||||
|
|
||||||
Cons:
|
|
||||||
* Limited interface to app. All queries must be serialized into
|
|
||||||
[]byte (less expressive than JSON over HTTP) and there is no way to push
|
|
||||||
data from ABCI app to the client (eg. notify me if account X receives a
|
|
||||||
transaction)
|
|
||||||
|
|
||||||
Custom ABCI server
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This was proposed by @wolfposd on slack and demonstrated by
|
|
||||||
`TMChat <https://github.com/wolfposd/TMChat>`__, a sample app. The
|
|
||||||
concept is to write a custom server for your app (with typical REST
|
|
||||||
API/websockets/etc for easy use by a mobile app). This custom server is
|
|
||||||
in the same binary as the ABCI app and data store, so can easily react
|
|
||||||
to complex events there that involve understanding the data format (send
|
|
||||||
a message if my balance drops below 500). All "writes" sent to this
|
|
||||||
server are proxied via websocket/JSON-RPC to tendermint core. When they
|
|
||||||
come back as deliver\_tx over ABCI, they will be written to the data
|
|
||||||
store. For "reads", we can do any queries we wish that are supported by
|
|
||||||
our architecture, using any web technology that is useful. The general
|
|
||||||
architecture is shown in the following diagram:
|
|
||||||
|
|
||||||
Pros: \* Separates application logic from blockchain logic \* Allows
|
|
||||||
much richer, more flexible client-facing API \* Allows pub-sub, watching
|
|
||||||
certain fields, etc.
|
|
||||||
|
|
||||||
Cons: \* Access to ABCI app can be dangerous (be VERY careful not to
|
|
||||||
write unless it comes from the validator node) \* No direct access to
|
|
||||||
the blockchain headers to verify tx \* You must write your own API (but
|
|
||||||
maybe that's a pro...)
|
|
||||||
|
|
||||||
Hybrid solutions
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Likely the least secure but most versatile. The client can access both
|
|
||||||
the tendermint node for all blockchain info, as well as a custom app
|
|
||||||
server, for complex queries and pub-sub on the abci app.
|
|
||||||
|
|
||||||
Pros: All from both above solutions
|
|
||||||
|
|
||||||
Cons: Even more complexity; even more attack vectors (less
|
|
||||||
security)
|
|
||||||
|
|
||||||
Scalability
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Read replica using non-validating nodes? They could forward transactions
|
|
||||||
to the validators (fewer connections, more security), and locally allow
|
|
||||||
all queries in any of the above configurations. Thus, while
|
|
||||||
transaction-processing speed is limited by the speed of the abci app and
|
|
||||||
the number of validators, one should be able to scale our read
|
|
||||||
performance to quite an extent (until the replication process drains too
|
|
||||||
many resources from the validator nodes).
|
|
||||||
|
@@ -178,21 +178,22 @@ connection, to query the local state of the app.
|
|||||||
Mempool Connection
|
Mempool Connection
|
||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
The mempool connection is used *only* for CheckTx requests. Transactions
|
The mempool connection is used *only* for CheckTx requests.
|
||||||
are run using CheckTx in the same order they were received by the
|
Transactions are run using CheckTx in the same order they were
|
||||||
validator. If the CheckTx returns ``OK``, the transaction is kept in
|
received by the validator. If the CheckTx returns ``OK``, the
|
||||||
memory and relayed to other peers in the same order it was received.
|
transaction is kept in memory and relayed to other peers in the same
|
||||||
Otherwise, it is discarded.
|
order it was received. Otherwise, it is discarded.
|
||||||
|
|
||||||
CheckTx requests run concurrently with block processing; so they should
|
CheckTx requests run concurrently with block processing; so they
|
||||||
run against a copy of the main application state which is reset after
|
should run against a copy of the main application state which is reset
|
||||||
every block. This copy is necessary to track transitions made by a
|
after every block. This copy is necessary to track transitions made by
|
||||||
sequence of CheckTx requests before they are included in a block. When a
|
a sequence of CheckTx requests before they are included in a block.
|
||||||
block is committed, the application must ensure to reset the mempool
|
When a block is committed, the application must ensure to reset the
|
||||||
state to the latest committed state. Tendermint Core will then filter
|
mempool state to the latest committed state. Tendermint Core will then
|
||||||
through all transactions in the mempool, removing any that were included
|
filter through all transactions in the mempool, removing any that were
|
||||||
in the block, and re-run the rest using CheckTx against the post-Commit
|
included in the block, and re-run the rest using CheckTx against the
|
||||||
mempool state.
|
post-Commit mempool state (this behaviour can be turned off with
|
||||||
|
``[mempool] recheck = false``).
|
||||||
|
|
||||||
.. container:: toggle
|
.. container:: toggle
|
||||||
|
|
||||||
@@ -226,6 +227,23 @@ mempool state.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Replay Protection
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
To prevent old transactions from being replayed, CheckTx must
|
||||||
|
implement replay protection.
|
||||||
|
|
||||||
|
Tendermint provides the first defence layer by keeping a lightweight
|
||||||
|
in-memory cache of 100k (``[mempool] cache_size``) last transactions in
|
||||||
|
the mempool. If Tendermint is just started or the clients sent more
|
||||||
|
than 100k transactions, old transactions may be sent to the
|
||||||
|
application. So it is important CheckTx implements some logic to
|
||||||
|
handle them.
|
||||||
|
|
||||||
|
There are cases where a transaction will (or may) become valid in some
|
||||||
|
future state, in which case you probably want to disable Tendermint's
|
||||||
|
cache. You can do that by setting ``[mempool] cache_size = 0`` in the
|
||||||
|
config.
|
||||||
|
|
||||||
Consensus Connection
|
Consensus Connection
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@@ -1,128 +1,29 @@
|
|||||||
# ADR 008: PrivValidator
|
# ADR 008: SocketPV
|
||||||
|
|
||||||
## Context
|
Tendermint node's should support only two in-process PrivValidator
|
||||||
|
implementations:
|
||||||
|
|
||||||
The current PrivValidator is monolithic and isn't easily reuseable by alternative signers.
|
- FilePV uses an unencrypted private key in a "priv_validator.json" file - no
|
||||||
|
configuration required (just `tendermint init`).
|
||||||
|
- SocketPV uses a socket to send signing requests to another process - user is
|
||||||
|
responsible for starting that process themselves.
|
||||||
|
|
||||||
For instance, see https://github.com/tendermint/tendermint/issues/673
|
The SocketPV 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 goal is to have a clean PrivValidator interface like:
|
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
|
||||||
type PrivValidator interface {
|
external process. These include:
|
||||||
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
|
|
||||||
|
|
||||||
|
- FilePV will encrypt the private key, and the user must enter password to
|
||||||
|
decrypt key when process is started.
|
||||||
|
- LedgerPV uses a Ledger Nano S to handle all signing.
|
||||||
|
BIN
docs/assets/a_plus_t.png
Normal file
BIN
docs/assets/a_plus_t.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
docs/assets/tm-application-example.png
Normal file
BIN
docs/assets/tm-application-example.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
17
docs/conf.py
17
docs/conf.py
@@ -71,7 +71,7 @@ language = None
|
|||||||
# List of patterns, relative to source directory, that match files and
|
# List of patterns, relative to source directory, that match files and
|
||||||
# directories to ignore when looking for source files.
|
# directories to ignore when looking for source files.
|
||||||
# This patterns also effect to html_static_path and html_extra_path
|
# This patterns also effect to html_static_path and html_extra_path
|
||||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'architecture', 'specification/new-spec', 'examples']
|
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'architecture', 'spec', 'examples']
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
pygments_style = 'sphinx'
|
pygments_style = 'sphinx'
|
||||||
@@ -184,21 +184,10 @@ if os.path.isdir(tools_dir) != True:
|
|||||||
if os.path.isdir(assets_dir) != True:
|
if os.path.isdir(assets_dir) != True:
|
||||||
os.mkdir(assets_dir)
|
os.mkdir(assets_dir)
|
||||||
|
|
||||||
urllib.urlretrieve(tools_repo+tools_branch+'/ansible/README.rst', filename=tools_dir+'/ansible.rst')
|
|
||||||
urllib.urlretrieve(tools_repo+tools_branch+'/ansible/assets/a_plus_t.png', filename=assets_dir+'/a_plus_t.png')
|
|
||||||
|
|
||||||
urllib.urlretrieve(tools_repo+tools_branch+'/docker/README.rst', filename=tools_dir+'/docker.rst')
|
urllib.urlretrieve(tools_repo+tools_branch+'/docker/README.rst', filename=tools_dir+'/docker.rst')
|
||||||
|
|
||||||
urllib.urlretrieve(tools_repo+tools_branch+'/mintnet-kubernetes/README.rst', filename=tools_dir+'/mintnet-kubernetes.rst')
|
urllib.urlretrieve(tools_repo+tools_branch+'/tm-bench/README.rst', filename=tools_dir+'/benchmarking.rst')
|
||||||
urllib.urlretrieve(tools_repo+tools_branch+'/mintnet-kubernetes/assets/gce1.png', filename=assets_dir+'/gce1.png')
|
urllib.urlretrieve(tools_repo+tools_branch+'/tm-monitor/README.rst', filename='tools/monitoring.rst')
|
||||||
urllib.urlretrieve(tools_repo+tools_branch+'/mintnet-kubernetes/assets/gce2.png', filename=assets_dir+'/gce2.png')
|
|
||||||
urllib.urlretrieve(tools_repo+tools_branch+'/mintnet-kubernetes/assets/statefulset.png', filename=assets_dir+'/statefulset.png')
|
|
||||||
urllib.urlretrieve(tools_repo+tools_branch+'/mintnet-kubernetes/assets/t_plus_k.png', filename=assets_dir+'/t_plus_k.png')
|
|
||||||
|
|
||||||
urllib.urlretrieve(tools_repo+tools_branch+'/terraform-digitalocean/README.rst', filename=tools_dir+'/terraform-digitalocean.rst')
|
|
||||||
urllib.urlretrieve(tools_repo+tools_branch+'/tm-bench/README.rst', filename=tools_dir+'/benchmarking-and-monitoring.rst')
|
|
||||||
# the readme for below is included in tm-bench
|
|
||||||
# urllib.urlretrieve('https://raw.githubusercontent.com/tendermint/tools/master/tm-monitor/README.rst', filename='tools/tm-monitor.rst')
|
|
||||||
|
|
||||||
#### abci spec #################################
|
#### abci spec #################################
|
||||||
|
|
||||||
|
@@ -3,88 +3,62 @@ Deploy a Testnet
|
|||||||
|
|
||||||
Now that we've seen how ABCI works, and even played with a few
|
Now that we've seen how ABCI works, and even played with a few
|
||||||
applications on a single validator node, it's time to deploy a test
|
applications on a single validator node, it's time to deploy a test
|
||||||
network to four validator nodes. For this deployment, we'll use the
|
network to four validator nodes.
|
||||||
``basecoin`` application.
|
|
||||||
|
|
||||||
Manual Deployments
|
Manual Deployments
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
It's relatively easy to setup a Tendermint cluster manually. The only
|
It's relatively easy to setup a Tendermint cluster manually. The only
|
||||||
requirements for a particular Tendermint node are a private key for the
|
requirements for a particular Tendermint node are a private key for the
|
||||||
validator, stored as ``priv_validator.json``, and a list of the public
|
validator, stored as ``priv_validator.json``, a node key, stored as
|
||||||
keys of all validators, stored as ``genesis.json``. These files should
|
``node_key.json`` and a list of the public keys of all validators, stored as
|
||||||
be stored in ``~/.tendermint/config``, or wherever the ``$TMHOME`` variable
|
``genesis.json``. These files should be stored in ``~/.tendermint/config``, or
|
||||||
might be set to.
|
wherever the ``$TMHOME`` variable might be set to.
|
||||||
|
|
||||||
Here are the steps to setting up a testnet manually:
|
Here are the steps to setting up a testnet manually:
|
||||||
|
|
||||||
1) Provision nodes on your cloud provider of choice
|
1) Provision nodes on your cloud provider of choice
|
||||||
2) Install Tendermint and the application of interest on all nodes
|
2) Install Tendermint and the application of interest on all nodes
|
||||||
3) Generate a private key for each validator using
|
3) Generate a private key and a node key for each validator using
|
||||||
``tendermint gen_validator``
|
``tendermint init``
|
||||||
4) Compile a list of public keys for each validator into a
|
4) Compile a list of public keys for each validator into a
|
||||||
``genesis.json`` file.
|
``genesis.json`` file and replace the existing file with it.
|
||||||
5) Run ``tendermint node --p2p.persistent_peers=< peer addresses >`` on each node,
|
5) Run ``tendermint node --proxy_app=kvstore --p2p.persistent_peers=< peer addresses >`` on each node,
|
||||||
where ``< peer addresses >`` is a comma separated list of the IP:PORT
|
where ``< peer addresses >`` is a comma separated list of the IP:PORT
|
||||||
combination for each node. The default port for Tendermint is
|
combination for each node. The default port for Tendermint is
|
||||||
``46656``. Thus, if the IP addresses of your nodes were
|
``46656``. Thus, if the IP addresses of your nodes were
|
||||||
``192.168.0.1, 192.168.0.2, 192.168.0.3, 192.168.0.4``, the command
|
``192.168.0.1, 192.168.0.2, 192.168.0.3, 192.168.0.4``, the command
|
||||||
would look like:
|
would look like:
|
||||||
``tendermint node --p2p.persistent_peers=192.168.0.1:46656,192.168.0.2:46656,192.168.0.3:46656,192.168.0.4:46656``.
|
|
||||||
|
::
|
||||||
|
|
||||||
|
tendermint node --proxy_app=kvstore --p2p.persistent_peers=96663a3dd0d7b9d17d4c8211b191af259621c693@192.168.0.1:46656, 429fcf25974313b95673f58d77eacdd434402665@192.168.0.2:46656, 0491d373a8e0fcf1023aaf18c51d6a1d0d4f31bd@192.168.0.3:46656, f9baeaa15fedf5e1ef7448dd60f46c01f1a9e9c4@192.168.0.4:46656
|
||||||
|
|
||||||
After a few seconds, all the nodes should connect to each other and start
|
After a few seconds, all the nodes should connect to each other and start
|
||||||
making blocks! For more information, see the Tendermint Networks section
|
making blocks! For more information, see the Tendermint Networks section
|
||||||
of `the guide to using Tendermint <using-tendermint.html>`__.
|
of `the guide to using Tendermint <using-tendermint.html>`__.
|
||||||
|
|
||||||
|
But wait! Steps 3 and 4 are quite manual. Instead, use `this script <https://github.com/tendermint/tendermint/blob/develop/docs/examples/init_testnet.sh>`__, which does the heavy lifting for you. And it gets better.
|
||||||
|
|
||||||
|
Instead of the previously linked script to initialize the files required for a testnet, we have the ``tendermint testnet`` command. By default, running ``tendermint testnet`` will create all the required files, just like the script. Of course, you'll still need to manually edit some fields in the ``config.toml``. Alternatively, see the available flags to auto-populate the ``config.toml`` with the fields that would otherwise be passed in via flags when running ``tendermint node``. As you might imagine, this command is useful for manual or automated deployments.
|
||||||
|
|
||||||
Automated Deployments
|
Automated Deployments
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
While the manual deployment is easy enough, an automated deployment is
|
The easiest and fastest way to get a testnet up in less than 5 minutes.
|
||||||
usually quicker. The below examples show different tools that can be used
|
|
||||||
for automated deployments.
|
|
||||||
|
|
||||||
Automated Deployment using Kubernetes
|
Local
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^
|
||||||
|
|
||||||
The `mintnet-kubernetes tool <https://github.com/tendermint/tools/tree/master/mintnet-kubernetes>`__
|
With ``docker`` and ``docker-compose`` installed, run the command:
|
||||||
allows automating the deployment of a Tendermint network on an already
|
|
||||||
provisioned Kubernetes cluster. For simple provisioning of a Kubernetes
|
|
||||||
cluster, check out the `Google Cloud Platform <https://cloud.google.com/>`__.
|
|
||||||
|
|
||||||
Automated Deployment using Terraform and Ansible
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
The `terraform-digitalocean tool <https://github.com/tendermint/tools/tree/master/terraform-digitalocean>`__
|
|
||||||
allows creating a set of servers on the DigitalOcean cloud.
|
|
||||||
|
|
||||||
The `ansible playbooks <https://github.com/tendermint/tools/tree/master/ansible>`__
|
|
||||||
allow creating and managing a ``basecoin`` or ``ethermint`` testnet on provisioned servers.
|
|
||||||
|
|
||||||
Package Deployment on Linux for developers
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
The ``tendermint`` and ``basecoin`` applications can be installed from RPM or DEB packages on
|
|
||||||
Linux machines for development purposes. The packages are configured to be validators on the
|
|
||||||
one-node network that the machine represents. The services are not started after installation,
|
|
||||||
this way giving an opportunity to reconfigure the applications before starting.
|
|
||||||
|
|
||||||
The Ansible playbooks in the previous section use this repository to install ``basecoin``.
|
|
||||||
After installation, additional steps are executed to make sure that the multi-node testnet has
|
|
||||||
the right configuration before start.
|
|
||||||
|
|
||||||
Install from the CentOS/RedHat repository:
|
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
rpm --import https://tendermint-packages.interblock.io/centos/7/os/x86_64/RPM-GPG-KEY-Tendermint
|
make localnet-start
|
||||||
wget -O /etc/yum.repos.d/tendermint.repo https://tendermint-packages.interblock.io/centos/7/os/x86_64/tendermint.repo
|
|
||||||
yum install basecoin
|
|
||||||
|
|
||||||
Install from the Debian/Ubuntu repository:
|
from the root of the tendermint repository. This will spin up a 4-node local testnet.
|
||||||
|
|
||||||
::
|
Cloud
|
||||||
|
^^^^^
|
||||||
wget -O - https://tendermint-packages.interblock.io/centos/7/os/x86_64/RPM-GPG-KEY-Tendermint | apt-key add -
|
|
||||||
wget -O /etc/apt/sources.list.d/tendermint.list https://tendermint-packages.interblock.io/debian/tendermint.list
|
|
||||||
apt-get update && apt-get install basecoin
|
|
||||||
|
|
||||||
|
See the `next section <./terraform-and-ansible.html>`__ for details.
|
||||||
|
@@ -2,17 +2,18 @@
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
This is a quick start guide. If you have a vague idea about how Tendermint works
|
This is a quick start guide. If you have a vague idea about how Tendermint
|
||||||
and want to get started right away, continue. Otherwise, [review the documentation](http://tendermint.readthedocs.io/en/master/)
|
works and want to get started right away, continue. Otherwise, [review the
|
||||||
|
documentation](http://tendermint.readthedocs.io/en/master/).
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
### Quick Install
|
### Quick Install
|
||||||
|
|
||||||
On a fresh Ubuntu 16.04 machine can be done with [this script](https://git.io/vNLfY), like so:
|
On a fresh Ubuntu 16.04 machine can be done with [this script](https://git.io/vpgEI), like so:
|
||||||
|
|
||||||
```
|
```
|
||||||
curl -L https://git.io/vxWlX | bash
|
curl -L https://git.io/vpgEI | bash
|
||||||
source ~/.profile
|
source ~/.profile
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ The script is also used to facilitate cluster deployment below.
|
|||||||
### Manual Install
|
### Manual Install
|
||||||
|
|
||||||
Requires:
|
Requires:
|
||||||
- `go` minimum version 1.9
|
- `go` minimum version 1.10
|
||||||
- `$GOPATH` environment variable must be set
|
- `$GOPATH` environment variable must be set
|
||||||
- `$GOPATH/bin` must be on your `$PATH` (see https://github.com/tendermint/tendermint/wiki/Setting-GOPATH)
|
- `$GOPATH/bin` must be on your `$PATH` (see https://github.com/tendermint/tendermint/wiki/Setting-GOPATH)
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ Confirm installation:
|
|||||||
|
|
||||||
```
|
```
|
||||||
$ tendermint version
|
$ tendermint version
|
||||||
0.15.0-381fe19
|
0.18.0-XXXXXXX
|
||||||
```
|
```
|
||||||
|
|
||||||
## Initialization
|
## Initialization
|
||||||
@@ -117,12 +118,14 @@ where the value is returned in hex.
|
|||||||
|
|
||||||
## Cluster of Nodes
|
## Cluster of Nodes
|
||||||
|
|
||||||
First create four Ubuntu cloud machines. The following was tested on Digital Ocean Ubuntu 16.04 x64 (3GB/1CPU, 20GB SSD). We'll refer to their respective IP addresses below as IP1, IP2, IP3, IP4.
|
First create four Ubuntu cloud machines. The following was tested on Digital
|
||||||
|
Ocean Ubuntu 16.04 x64 (3GB/1CPU, 20GB SSD). We'll refer to their respective IP
|
||||||
|
addresses below as IP1, IP2, IP3, IP4.
|
||||||
|
|
||||||
Then, `ssh` into each machine, and execute [this script](https://git.io/vNLfY):
|
Then, `ssh` into each machine, and execute [this script](https://git.io/vNLfY):
|
||||||
|
|
||||||
```
|
```
|
||||||
curl -L https://git.io/vNLfY | bash
|
curl -L https://git.io/vpgEI | bash
|
||||||
source ~/.profile
|
source ~/.profile
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -131,12 +134,16 @@ 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:
|
Next, `cd` into `docs/examples`. Each command below should be run from each node, in sequence:
|
||||||
|
|
||||||
```
|
```
|
||||||
tendermint node --home ./node1 --proxy_app=kvstore --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656
|
tendermint node --home ./node0 --proxy_app=kvstore --p2p.persistent_peers="167b80242c300bf0ccfb3ced3dec60dc2a81776e@IP1:46656,3c7a5920811550c04bf7a0b2f1e02ab52317b5e6@IP2:46656,303a1a4312c30525c99ba66522dd81cca56a361a@IP3:46656,b686c2a7f4b1b46dca96af3a0f31a6a7beae0be4@IP4:46656"
|
||||||
tendermint node --home ./node2 --proxy_app=kvstore --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656
|
tendermint node --home ./node1 --proxy_app=kvstore --p2p.persistent_peers="167b80242c300bf0ccfb3ced3dec60dc2a81776e@IP1:46656,3c7a5920811550c04bf7a0b2f1e02ab52317b5e6@IP2:46656,303a1a4312c30525c99ba66522dd81cca56a361a@IP3:46656,b686c2a7f4b1b46dca96af3a0f31a6a7beae0be4@IP4:46656"
|
||||||
tendermint node --home ./node3 --proxy_app=kvstore --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656
|
tendermint node --home ./node2 --proxy_app=kvstore --p2p.persistent_peers="167b80242c300bf0ccfb3ced3dec60dc2a81776e@IP1:46656,3c7a5920811550c04bf7a0b2f1e02ab52317b5e6@IP2:46656,303a1a4312c30525c99ba66522dd81cca56a361a@IP3:46656,b686c2a7f4b1b46dca96af3a0f31a6a7beae0be4@IP4:46656"
|
||||||
tendermint node --home ./node4 --proxy_app=kvstore --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656
|
tendermint node --home ./node3 --proxy_app=kvstore --p2p.persistent_peers="167b80242c300bf0ccfb3ced3dec60dc2a81776e@IP1:46656,3c7a5920811550c04bf7a0b2f1e02ab52317b5e6@IP2:46656,303a1a4312c30525c99ba66522dd81cca56a361a@IP3:46656,b686c2a7f4b1b46dca96af3a0f31a6a7beae0be4@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.
|
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.
|
||||||
|
|
||||||
Transactions can then be sent as covered in the single, local node example above.
|
Transactions can then be sent as covered in the single, local node example above.
|
||||||
|
69
docs/examples/init_testnet.sh
Normal file
69
docs/examples/init_testnet.sh
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# make all the files
|
||||||
|
tendermint init --home ./tester/node0
|
||||||
|
tendermint init --home ./tester/node1
|
||||||
|
tendermint init --home ./tester/node2
|
||||||
|
tendermint init --home ./tester/node3
|
||||||
|
|
||||||
|
file0=./tester/node0/config/genesis.json
|
||||||
|
file1=./tester/node1/config/genesis.json
|
||||||
|
file2=./tester/node2/config/genesis.json
|
||||||
|
file3=./tester/node3/config/genesis.json
|
||||||
|
|
||||||
|
genesis_time=`cat $file0 | jq '.genesis_time'`
|
||||||
|
chain_id=`cat $file0 | jq '.chain_id'`
|
||||||
|
|
||||||
|
value0=`cat $file0 | jq '.validators[0].pub_key.value'`
|
||||||
|
value1=`cat $file1 | jq '.validators[0].pub_key.value'`
|
||||||
|
value2=`cat $file2 | jq '.validators[0].pub_key.value'`
|
||||||
|
value3=`cat $file3 | jq '.validators[0].pub_key.value'`
|
||||||
|
|
||||||
|
rm $file0
|
||||||
|
rm $file1
|
||||||
|
rm $file2
|
||||||
|
rm $file3
|
||||||
|
|
||||||
|
echo "{
|
||||||
|
\"genesis_time\": $genesis_time,
|
||||||
|
\"chain_id\": $chain_id,
|
||||||
|
\"validators\": [
|
||||||
|
{
|
||||||
|
\"pub_key\": {
|
||||||
|
\"type\": \"AC26791624DE60\",
|
||||||
|
\"value\": $value0
|
||||||
|
},
|
||||||
|
\"power:\": 10,
|
||||||
|
\"name\":, \"\"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\"pub_key\": {
|
||||||
|
\"type\": \"AC26791624DE60\",
|
||||||
|
\"value\": $value1
|
||||||
|
},
|
||||||
|
\"power:\": 10,
|
||||||
|
\"name\":, \"\"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\"pub_key\": {
|
||||||
|
\"type\": \"AC26791624DE60\",
|
||||||
|
\"value\": $value2
|
||||||
|
},
|
||||||
|
\"power:\": 10,
|
||||||
|
\"name\":, \"\"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\"pub_key\": {
|
||||||
|
\"type\": \"AC26791624DE60\",
|
||||||
|
\"value\": $value3
|
||||||
|
},
|
||||||
|
\"power:\": 10,
|
||||||
|
\"name\":, \"\"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\"app_hash\": \"\"
|
||||||
|
}" >> $file0
|
||||||
|
|
||||||
|
cp $file0 $file1
|
||||||
|
cp $file0 $file2
|
||||||
|
cp $file2 $file3
|
@@ -26,7 +26,7 @@ go get $REPO
|
|||||||
cd $GOPATH/src/$REPO
|
cd $GOPATH/src/$REPO
|
||||||
|
|
||||||
## build
|
## build
|
||||||
git checkout v0.17.0
|
git checkout master
|
||||||
make get_tools
|
make get_tools
|
||||||
make get_vendor_deps
|
make get_vendor_deps
|
||||||
make install
|
make install
|
||||||
|
166
docs/examples/node0/config/config.toml
Normal file
166
docs/examples/node0/config/config.toml
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
# This is a TOML config file.
|
||||||
|
# For more information, see https://github.com/toml-lang/toml
|
||||||
|
|
||||||
|
##### main base config options #####
|
||||||
|
|
||||||
|
# TCP or UNIX socket address of the ABCI application,
|
||||||
|
# or the name of an ABCI application compiled in with the Tendermint binary
|
||||||
|
proxy_app = "tcp://127.0.0.1:46658"
|
||||||
|
|
||||||
|
# A custom human readable name for this node
|
||||||
|
moniker = "alpha"
|
||||||
|
|
||||||
|
# If this node is many blocks behind the tip of the chain, FastSync
|
||||||
|
# allows them to catchup quickly by downloading blocks in parallel
|
||||||
|
# and verifying their commits
|
||||||
|
fast_sync = true
|
||||||
|
|
||||||
|
# Database backend: leveldb | memdb
|
||||||
|
db_backend = "leveldb"
|
||||||
|
|
||||||
|
# Database directory
|
||||||
|
db_path = "data"
|
||||||
|
|
||||||
|
# Output level for logging, including package level options
|
||||||
|
log_level = "main:info,state:info,*:error"
|
||||||
|
|
||||||
|
##### additional base config options #####
|
||||||
|
|
||||||
|
# Path to the JSON file containing the initial validator set and other meta data
|
||||||
|
genesis_file = "config/genesis.json"
|
||||||
|
|
||||||
|
# Path to the JSON file containing the private key to use as a validator in the consensus protocol
|
||||||
|
priv_validator_file = "config/priv_validator.json"
|
||||||
|
|
||||||
|
# Path to the JSON file containing the private key to use for node authentication in the p2p protocol
|
||||||
|
node_key_file = "config/node_key.json"
|
||||||
|
|
||||||
|
# Mechanism to connect to the ABCI application: socket | grpc
|
||||||
|
abci = "socket"
|
||||||
|
|
||||||
|
# TCP or UNIX socket address for the profiling server to listen on
|
||||||
|
prof_laddr = ""
|
||||||
|
|
||||||
|
# If true, query the ABCI app on connecting to a new peer
|
||||||
|
# so the app can decide if we should keep the connection or not
|
||||||
|
filter_peers = false
|
||||||
|
|
||||||
|
##### advanced configuration options #####
|
||||||
|
|
||||||
|
##### rpc server configuration options #####
|
||||||
|
[rpc]
|
||||||
|
|
||||||
|
# TCP or UNIX socket address for the RPC server to listen on
|
||||||
|
laddr = "tcp://0.0.0.0:46657"
|
||||||
|
|
||||||
|
# TCP or UNIX socket address for the gRPC server to listen on
|
||||||
|
# NOTE: This server only supports /broadcast_tx_commit
|
||||||
|
grpc_laddr = ""
|
||||||
|
|
||||||
|
# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool
|
||||||
|
unsafe = false
|
||||||
|
|
||||||
|
##### peer to peer configuration options #####
|
||||||
|
[p2p]
|
||||||
|
|
||||||
|
# Address to listen for incoming connections
|
||||||
|
laddr = "tcp://0.0.0.0:46656"
|
||||||
|
|
||||||
|
# Comma separated list of seed nodes to connect to
|
||||||
|
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
|
||||||
|
addr_book_file = "config/addrbook.json"
|
||||||
|
|
||||||
|
# Set true for strict address routability rules
|
||||||
|
addr_book_strict = true
|
||||||
|
|
||||||
|
# Time to wait before flushing messages out on the connection, in ms
|
||||||
|
flush_throttle_timeout = 100
|
||||||
|
|
||||||
|
# Maximum number of peers to connect to
|
||||||
|
max_num_peers = 50
|
||||||
|
|
||||||
|
# Maximum size of a message packet payload, in bytes
|
||||||
|
max_packet_msg_payload_size = 1024
|
||||||
|
|
||||||
|
# Rate at which packets can be sent, in bytes/second
|
||||||
|
send_rate = 512000
|
||||||
|
|
||||||
|
# Rate at which packets can be received, in bytes/second
|
||||||
|
recv_rate = 512000
|
||||||
|
|
||||||
|
# Set true to enable the peer-exchange reactor
|
||||||
|
pex = true
|
||||||
|
|
||||||
|
# Seed mode, in which node constantly crawls the network and looks for
|
||||||
|
# peers. If another node asks it for addresses, it responds and disconnects.
|
||||||
|
#
|
||||||
|
# Does not work if the peer-exchange reactor is disabled.
|
||||||
|
seed_mode = false
|
||||||
|
|
||||||
|
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
||||||
|
private_peer_ids = ""
|
||||||
|
|
||||||
|
##### mempool configuration options #####
|
||||||
|
[mempool]
|
||||||
|
|
||||||
|
recheck = true
|
||||||
|
recheck_empty = true
|
||||||
|
broadcast = true
|
||||||
|
wal_dir = "data/mempool.wal"
|
||||||
|
|
||||||
|
##### consensus configuration options #####
|
||||||
|
[consensus]
|
||||||
|
|
||||||
|
wal_file = "data/cs.wal/wal"
|
||||||
|
|
||||||
|
# All timeouts are in milliseconds
|
||||||
|
timeout_propose = 3000
|
||||||
|
timeout_propose_delta = 500
|
||||||
|
timeout_prevote = 1000
|
||||||
|
timeout_prevote_delta = 500
|
||||||
|
timeout_precommit = 1000
|
||||||
|
timeout_precommit_delta = 500
|
||||||
|
timeout_commit = 1000
|
||||||
|
|
||||||
|
# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0)
|
||||||
|
skip_timeout_commit = false
|
||||||
|
|
||||||
|
# BlockSize
|
||||||
|
max_block_size_txs = 10000
|
||||||
|
max_block_size_bytes = 1
|
||||||
|
|
||||||
|
# EmptyBlocks mode and possible interval between empty blocks in seconds
|
||||||
|
create_empty_blocks = true
|
||||||
|
create_empty_blocks_interval = 0
|
||||||
|
|
||||||
|
# Reactor sleep duration parameters are in milliseconds
|
||||||
|
peer_gossip_sleep_duration = 100
|
||||||
|
peer_query_maj23_sleep_duration = 2000
|
||||||
|
|
||||||
|
##### transactions indexer configuration options #####
|
||||||
|
[tx_index]
|
||||||
|
|
||||||
|
# What indexer to use for transactions
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# 1) "null" (default)
|
||||||
|
# 2) "kv" - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend).
|
||||||
|
indexer = "kv"
|
||||||
|
|
||||||
|
# Comma-separated list of tags to index (by default the only tag is tx hash)
|
||||||
|
#
|
||||||
|
# It's recommended to index only a subset of tags due to possible memory
|
||||||
|
# bloat. This is, of course, depends on the indexer's DB and the volume of
|
||||||
|
# transactions.
|
||||||
|
index_tags = ""
|
||||||
|
|
||||||
|
# When set to true, tells indexer to index all tags. Note this may be not
|
||||||
|
# desirable (see the comment above). IndexTags has a precedence over
|
||||||
|
# IndexAllTags (i.e. when given both, IndexTags will be indexed).
|
||||||
|
index_all_tags = false
|
39
docs/examples/node0/config/genesis.json
Normal file
39
docs/examples/node0/config/genesis.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"genesis_time": "0001-01-01T00:00:00Z",
|
||||||
|
"chain_id": "test-chain-A2i3OZ",
|
||||||
|
"validators": [
|
||||||
|
{
|
||||||
|
"pub_key": {
|
||||||
|
"type": "AC26791624DE60",
|
||||||
|
"value": "D+k4AdjnYPWbB9wmad137Bdpo/kAulOoTRQrLy/Qc4k="
|
||||||
|
},
|
||||||
|
"power": 10,
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pub_key": {
|
||||||
|
"type": "AC26791624DE60",
|
||||||
|
"value": "b56N5GCR1adcVRuENjfKw/mrm2dkhT7wNZXV/SDsKsU="
|
||||||
|
},
|
||||||
|
"power": 10,
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pub_key": {
|
||||||
|
"type": "AC26791624DE60",
|
||||||
|
"value": "IgZDpJvGA0TAamicA8ircy+RX/BkUlj6DXwM791ywIU="
|
||||||
|
},
|
||||||
|
"power": 10,
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pub_key": {
|
||||||
|
"type": "AC26791624DE60",
|
||||||
|
"value": "KGAZfxZvIZ7abbeIQ85U1ECG6+I62KSdaH8ulc0+OiU="
|
||||||
|
},
|
||||||
|
"power": 10,
|
||||||
|
"name": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"app_hash": ""
|
||||||
|
}
|
1
docs/examples/node0/config/node_key.json
Normal file
1
docs/examples/node0/config/node_key.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"priv_key":{"type":"954568A3288910","value":"7lY+k6EDllG8Q9gVbF5313t/ag2YGkBVKdVa0YHJ9xO5k0w3Q/hke0Z7UFT1KgVDGRUEKzwAwwjwFQUvgF0ZWg=="}}
|
14
docs/examples/node0/config/priv_validator.json
Normal file
14
docs/examples/node0/config/priv_validator.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"address": "122A9414774A2FCAD026201DA477EF3F41970EF0",
|
||||||
|
"pub_key": {
|
||||||
|
"type": "AC26791624DE60",
|
||||||
|
"value": "D+k4AdjnYPWbB9wmad137Bdpo/kAulOoTRQrLy/Qc4k="
|
||||||
|
},
|
||||||
|
"last_height": 0,
|
||||||
|
"last_round": 0,
|
||||||
|
"last_step": 0,
|
||||||
|
"priv_key": {
|
||||||
|
"type": "954568A3288910",
|
||||||
|
"value": "YLxp3ho+kySgAnzjBptbxDzSGw2ntGZLsIHQsaVxY/cP6TgB2Odg9ZsH3CZp3XfsF2mj+QC6U6hNFCsvL9BziQ=="
|
||||||
|
}
|
||||||
|
}
|
@@ -1,15 +0,0 @@
|
|||||||
# This is a TOML config file.
|
|
||||||
# For more information, see https://github.com/toml-lang/toml
|
|
||||||
|
|
||||||
proxy_app = "tcp://127.0.0.1:46658"
|
|
||||||
moniker = "penguin"
|
|
||||||
fast_sync = true
|
|
||||||
db_backend = "leveldb"
|
|
||||||
log_level = "state:info,*:error"
|
|
||||||
|
|
||||||
[rpc]
|
|
||||||
laddr = "tcp://0.0.0.0:46657"
|
|
||||||
|
|
||||||
[p2p]
|
|
||||||
laddr = "tcp://0.0.0.0:46656"
|
|
||||||
seeds = ""
|
|
166
docs/examples/node1/config/config.toml
Normal file
166
docs/examples/node1/config/config.toml
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
# This is a TOML config file.
|
||||||
|
# For more information, see https://github.com/toml-lang/toml
|
||||||
|
|
||||||
|
##### main base config options #####
|
||||||
|
|
||||||
|
# TCP or UNIX socket address of the ABCI application,
|
||||||
|
# or the name of an ABCI application compiled in with the Tendermint binary
|
||||||
|
proxy_app = "tcp://127.0.0.1:46658"
|
||||||
|
|
||||||
|
# A custom human readable name for this node
|
||||||
|
moniker = "bravo"
|
||||||
|
|
||||||
|
# If this node is many blocks behind the tip of the chain, FastSync
|
||||||
|
# allows them to catchup quickly by downloading blocks in parallel
|
||||||
|
# and verifying their commits
|
||||||
|
fast_sync = true
|
||||||
|
|
||||||
|
# Database backend: leveldb | memdb
|
||||||
|
db_backend = "leveldb"
|
||||||
|
|
||||||
|
# Database directory
|
||||||
|
db_path = "data"
|
||||||
|
|
||||||
|
# Output level for logging, including package level options
|
||||||
|
log_level = "main:info,state:info,*:error"
|
||||||
|
|
||||||
|
##### additional base config options #####
|
||||||
|
|
||||||
|
# Path to the JSON file containing the initial validator set and other meta data
|
||||||
|
genesis_file = "config/genesis.json"
|
||||||
|
|
||||||
|
# Path to the JSON file containing the private key to use as a validator in the consensus protocol
|
||||||
|
priv_validator_file = "config/priv_validator.json"
|
||||||
|
|
||||||
|
# Path to the JSON file containing the private key to use for node authentication in the p2p protocol
|
||||||
|
node_key_file = "config/node_key.json"
|
||||||
|
|
||||||
|
# Mechanism to connect to the ABCI application: socket | grpc
|
||||||
|
abci = "socket"
|
||||||
|
|
||||||
|
# TCP or UNIX socket address for the profiling server to listen on
|
||||||
|
prof_laddr = ""
|
||||||
|
|
||||||
|
# If true, query the ABCI app on connecting to a new peer
|
||||||
|
# so the app can decide if we should keep the connection or not
|
||||||
|
filter_peers = false
|
||||||
|
|
||||||
|
##### advanced configuration options #####
|
||||||
|
|
||||||
|
##### rpc server configuration options #####
|
||||||
|
[rpc]
|
||||||
|
|
||||||
|
# TCP or UNIX socket address for the RPC server to listen on
|
||||||
|
laddr = "tcp://0.0.0.0:46657"
|
||||||
|
|
||||||
|
# TCP or UNIX socket address for the gRPC server to listen on
|
||||||
|
# NOTE: This server only supports /broadcast_tx_commit
|
||||||
|
grpc_laddr = ""
|
||||||
|
|
||||||
|
# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool
|
||||||
|
unsafe = false
|
||||||
|
|
||||||
|
##### peer to peer configuration options #####
|
||||||
|
[p2p]
|
||||||
|
|
||||||
|
# Address to listen for incoming connections
|
||||||
|
laddr = "tcp://0.0.0.0:46656"
|
||||||
|
|
||||||
|
# Comma separated list of seed nodes to connect to
|
||||||
|
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
|
||||||
|
addr_book_file = "config/addrbook.json"
|
||||||
|
|
||||||
|
# Set true for strict address routability rules
|
||||||
|
addr_book_strict = true
|
||||||
|
|
||||||
|
# Time to wait before flushing messages out on the connection, in ms
|
||||||
|
flush_throttle_timeout = 100
|
||||||
|
|
||||||
|
# Maximum number of peers to connect to
|
||||||
|
max_num_peers = 50
|
||||||
|
|
||||||
|
# Maximum size of a message packet payload, in bytes
|
||||||
|
max_packet_msg_payload_size = 1024
|
||||||
|
|
||||||
|
# Rate at which packets can be sent, in bytes/second
|
||||||
|
send_rate = 512000
|
||||||
|
|
||||||
|
# Rate at which packets can be received, in bytes/second
|
||||||
|
recv_rate = 512000
|
||||||
|
|
||||||
|
# Set true to enable the peer-exchange reactor
|
||||||
|
pex = true
|
||||||
|
|
||||||
|
# Seed mode, in which node constantly crawls the network and looks for
|
||||||
|
# peers. If another node asks it for addresses, it responds and disconnects.
|
||||||
|
#
|
||||||
|
# Does not work if the peer-exchange reactor is disabled.
|
||||||
|
seed_mode = false
|
||||||
|
|
||||||
|
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
||||||
|
private_peer_ids = ""
|
||||||
|
|
||||||
|
##### mempool configuration options #####
|
||||||
|
[mempool]
|
||||||
|
|
||||||
|
recheck = true
|
||||||
|
recheck_empty = true
|
||||||
|
broadcast = true
|
||||||
|
wal_dir = "data/mempool.wal"
|
||||||
|
|
||||||
|
##### consensus configuration options #####
|
||||||
|
[consensus]
|
||||||
|
|
||||||
|
wal_file = "data/cs.wal/wal"
|
||||||
|
|
||||||
|
# All timeouts are in milliseconds
|
||||||
|
timeout_propose = 3000
|
||||||
|
timeout_propose_delta = 500
|
||||||
|
timeout_prevote = 1000
|
||||||
|
timeout_prevote_delta = 500
|
||||||
|
timeout_precommit = 1000
|
||||||
|
timeout_precommit_delta = 500
|
||||||
|
timeout_commit = 1000
|
||||||
|
|
||||||
|
# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0)
|
||||||
|
skip_timeout_commit = false
|
||||||
|
|
||||||
|
# BlockSize
|
||||||
|
max_block_size_txs = 10000
|
||||||
|
max_block_size_bytes = 1
|
||||||
|
|
||||||
|
# EmptyBlocks mode and possible interval between empty blocks in seconds
|
||||||
|
create_empty_blocks = true
|
||||||
|
create_empty_blocks_interval = 0
|
||||||
|
|
||||||
|
# Reactor sleep duration parameters are in milliseconds
|
||||||
|
peer_gossip_sleep_duration = 100
|
||||||
|
peer_query_maj23_sleep_duration = 2000
|
||||||
|
|
||||||
|
##### transactions indexer configuration options #####
|
||||||
|
[tx_index]
|
||||||
|
|
||||||
|
# What indexer to use for transactions
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# 1) "null" (default)
|
||||||
|
# 2) "kv" - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend).
|
||||||
|
indexer = "kv"
|
||||||
|
|
||||||
|
# Comma-separated list of tags to index (by default the only tag is tx hash)
|
||||||
|
#
|
||||||
|
# It's recommended to index only a subset of tags due to possible memory
|
||||||
|
# bloat. This is, of course, depends on the indexer's DB and the volume of
|
||||||
|
# transactions.
|
||||||
|
index_tags = ""
|
||||||
|
|
||||||
|
# When set to true, tells indexer to index all tags. Note this may be not
|
||||||
|
# desirable (see the comment above). IndexTags has a precedence over
|
||||||
|
# IndexAllTags (i.e. when given both, IndexTags will be indexed).
|
||||||
|
index_all_tags = false
|
39
docs/examples/node1/config/genesis.json
Normal file
39
docs/examples/node1/config/genesis.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"genesis_time": "0001-01-01T00:00:00Z",
|
||||||
|
"chain_id": "test-chain-A2i3OZ",
|
||||||
|
"validators": [
|
||||||
|
{
|
||||||
|
"pub_key": {
|
||||||
|
"type": "AC26791624DE60",
|
||||||
|
"value": "D+k4AdjnYPWbB9wmad137Bdpo/kAulOoTRQrLy/Qc4k="
|
||||||
|
},
|
||||||
|
"power": 10,
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pub_key": {
|
||||||
|
"type": "AC26791624DE60",
|
||||||
|
"value": "b56N5GCR1adcVRuENjfKw/mrm2dkhT7wNZXV/SDsKsU="
|
||||||
|
},
|
||||||
|
"power": 10,
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pub_key": {
|
||||||
|
"type": "AC26791624DE60",
|
||||||
|
"value": "IgZDpJvGA0TAamicA8ircy+RX/BkUlj6DXwM791ywIU="
|
||||||
|
},
|
||||||
|
"power": 10,
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pub_key": {
|
||||||
|
"type": "AC26791624DE60",
|
||||||
|
"value": "KGAZfxZvIZ7abbeIQ85U1ECG6+I62KSdaH8ulc0+OiU="
|
||||||
|
},
|
||||||
|
"power": 10,
|
||||||
|
"name": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"app_hash": ""
|
||||||
|
}
|
1
docs/examples/node1/config/node_key.json
Normal file
1
docs/examples/node1/config/node_key.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"priv_key":{"type":"954568A3288910","value":"H71dc/TIG7nTselfa9nG0WRArXLKYnm7P5eFCk2lk8ASKQ3sIHpbdxCSHQD/RcdHe7TiabJeuOssNPvPWiyQEQ=="}}
|
14
docs/examples/node1/config/priv_validator.json
Normal file
14
docs/examples/node1/config/priv_validator.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"address": "BEA1B57F5806CF9AC4D54C8CF806DED5C0F102E1",
|
||||||
|
"pub_key": {
|
||||||
|
"type": "AC26791624DE60",
|
||||||
|
"value": "b56N5GCR1adcVRuENjfKw/mrm2dkhT7wNZXV/SDsKsU="
|
||||||
|
},
|
||||||
|
"last_height": 0,
|
||||||
|
"last_round": 0,
|
||||||
|
"last_step": 0,
|
||||||
|
"priv_key": {
|
||||||
|
"type": "954568A3288910",
|
||||||
|
"value": "o0IqrHSPtd5YqGefodWxpJuRzvuVBjgbH785vbMgk7Vvno3kYJHVp1xVG4Q2N8rD+aubZ2SFPvA1ldX9IOwqxQ=="
|
||||||
|
}
|
||||||
|
}
|
@@ -1,42 +0,0 @@
|
|||||||
{
|
|
||||||
"genesis_time":"0001-01-01T00:00:00Z",
|
|
||||||
"chain_id":"test-chain-wt7apy",
|
|
||||||
"validators":[
|
|
||||||
{
|
|
||||||
"pub_key":{
|
|
||||||
"type":"ed25519",
|
|
||||||
"data":"F08446C80A33E10D620E21450821B58D053778528F2B583D423B3E46EC647D30"
|
|
||||||
},
|
|
||||||
"power":10,
|
|
||||||
"name":"node1"
|
|
||||||
}
|
|
||||||
,
|
|
||||||
{
|
|
||||||
"pub_key":{
|
|
||||||
"type":"ed25519",
|
|
||||||
"data": "A8423F70A9E512643B4B00F7C3701ECAD1F31B0A1FAA45852C41046353B9A07F"
|
|
||||||
},
|
|
||||||
"power":10,
|
|
||||||
"name":"node2"
|
|
||||||
}
|
|
||||||
,
|
|
||||||
{
|
|
||||||
"pub_key":{
|
|
||||||
"type":"ed25519",
|
|
||||||
"data": "E52EFFAEDFE1D618ECDA71DE3B23592B3612CAABA0C10826E4C3120B2198C29A"
|
|
||||||
},
|
|
||||||
"power":10,
|
|
||||||
"name":"node3"
|
|
||||||
}
|
|
||||||
,
|
|
||||||
{
|
|
||||||
"pub_key":{
|
|
||||||
"type":"ed25519",
|
|
||||||
"data": "2B8FC09C07955A02998DFE5AF1AAD1C44115ECA7635FF51A867CF4265D347C07"
|
|
||||||
},
|
|
||||||
"power":10,
|
|
||||||
"name":"node4"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"app_hash":""
|
|
||||||
}
|
|
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"address":"4DC2756029CE0D8F8C6C3E4C3CE6EE8C30AF352F",
|
|
||||||
"pub_key":{
|
|
||||||
"type":"ed25519",
|
|
||||||
"data":"F08446C80A33E10D620E21450821B58D053778528F2B583D423B3E46EC647D30"
|
|
||||||
},
|
|
||||||
"last_height":0,
|
|
||||||
"last_round":0,
|
|
||||||
"last_step":0,
|
|
||||||
"last_signature":null,
|
|
||||||
"priv_key":{
|
|
||||||
"type":"ed25519",
|
|
||||||
"data":"4D3648E1D93C8703E436BFF814728B6BD270CFDFD686DF5385E8ACBEB7BE2D7DF08446C80A33E10D620E21450821B58D053778528F2B583D423B3E46EC647D30"
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,15 +0,0 @@
|
|||||||
# This is a TOML config file.
|
|
||||||
# For more information, see https://github.com/toml-lang/toml
|
|
||||||
|
|
||||||
proxy_app = "tcp://127.0.0.1:46658"
|
|
||||||
moniker = "penguin"
|
|
||||||
fast_sync = true
|
|
||||||
db_backend = "leveldb"
|
|
||||||
log_level = "state:info,*:error"
|
|
||||||
|
|
||||||
[rpc]
|
|
||||||
laddr = "tcp://0.0.0.0:46657"
|
|
||||||
|
|
||||||
[p2p]
|
|
||||||
laddr = "tcp://0.0.0.0:46656"
|
|
||||||
seeds = ""
|
|
166
docs/examples/node2/config/config.toml
Normal file
166
docs/examples/node2/config/config.toml
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
# This is a TOML config file.
|
||||||
|
# For more information, see https://github.com/toml-lang/toml
|
||||||
|
|
||||||
|
##### main base config options #####
|
||||||
|
|
||||||
|
# TCP or UNIX socket address of the ABCI application,
|
||||||
|
# or the name of an ABCI application compiled in with the Tendermint binary
|
||||||
|
proxy_app = "tcp://127.0.0.1:46658"
|
||||||
|
|
||||||
|
# A custom human readable name for this node
|
||||||
|
moniker = "charlie"
|
||||||
|
|
||||||
|
# If this node is many blocks behind the tip of the chain, FastSync
|
||||||
|
# allows them to catchup quickly by downloading blocks in parallel
|
||||||
|
# and verifying their commits
|
||||||
|
fast_sync = true
|
||||||
|
|
||||||
|
# Database backend: leveldb | memdb
|
||||||
|
db_backend = "leveldb"
|
||||||
|
|
||||||
|
# Database directory
|
||||||
|
db_path = "data"
|
||||||
|
|
||||||
|
# Output level for logging, including package level options
|
||||||
|
log_level = "main:info,state:info,*:error"
|
||||||
|
|
||||||
|
##### additional base config options #####
|
||||||
|
|
||||||
|
# Path to the JSON file containing the initial validator set and other meta data
|
||||||
|
genesis_file = "config/genesis.json"
|
||||||
|
|
||||||
|
# Path to the JSON file containing the private key to use as a validator in the consensus protocol
|
||||||
|
priv_validator_file = "config/priv_validator.json"
|
||||||
|
|
||||||
|
# Path to the JSON file containing the private key to use for node authentication in the p2p protocol
|
||||||
|
node_key_file = "config/node_key.json"
|
||||||
|
|
||||||
|
# Mechanism to connect to the ABCI application: socket | grpc
|
||||||
|
abci = "socket"
|
||||||
|
|
||||||
|
# TCP or UNIX socket address for the profiling server to listen on
|
||||||
|
prof_laddr = ""
|
||||||
|
|
||||||
|
# If true, query the ABCI app on connecting to a new peer
|
||||||
|
# so the app can decide if we should keep the connection or not
|
||||||
|
filter_peers = false
|
||||||
|
|
||||||
|
##### advanced configuration options #####
|
||||||
|
|
||||||
|
##### rpc server configuration options #####
|
||||||
|
[rpc]
|
||||||
|
|
||||||
|
# TCP or UNIX socket address for the RPC server to listen on
|
||||||
|
laddr = "tcp://0.0.0.0:46657"
|
||||||
|
|
||||||
|
# TCP or UNIX socket address for the gRPC server to listen on
|
||||||
|
# NOTE: This server only supports /broadcast_tx_commit
|
||||||
|
grpc_laddr = ""
|
||||||
|
|
||||||
|
# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool
|
||||||
|
unsafe = false
|
||||||
|
|
||||||
|
##### peer to peer configuration options #####
|
||||||
|
[p2p]
|
||||||
|
|
||||||
|
# Address to listen for incoming connections
|
||||||
|
laddr = "tcp://0.0.0.0:46656"
|
||||||
|
|
||||||
|
# Comma separated list of seed nodes to connect to
|
||||||
|
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
|
||||||
|
addr_book_file = "config/addrbook.json"
|
||||||
|
|
||||||
|
# Set true for strict address routability rules
|
||||||
|
addr_book_strict = true
|
||||||
|
|
||||||
|
# Time to wait before flushing messages out on the connection, in ms
|
||||||
|
flush_throttle_timeout = 100
|
||||||
|
|
||||||
|
# Maximum number of peers to connect to
|
||||||
|
max_num_peers = 50
|
||||||
|
|
||||||
|
# Maximum size of a message packet payload, in bytes
|
||||||
|
max_packet_msg_payload_size = 1024
|
||||||
|
|
||||||
|
# Rate at which packets can be sent, in bytes/second
|
||||||
|
send_rate = 512000
|
||||||
|
|
||||||
|
# Rate at which packets can be received, in bytes/second
|
||||||
|
recv_rate = 512000
|
||||||
|
|
||||||
|
# Set true to enable the peer-exchange reactor
|
||||||
|
pex = true
|
||||||
|
|
||||||
|
# Seed mode, in which node constantly crawls the network and looks for
|
||||||
|
# peers. If another node asks it for addresses, it responds and disconnects.
|
||||||
|
#
|
||||||
|
# Does not work if the peer-exchange reactor is disabled.
|
||||||
|
seed_mode = false
|
||||||
|
|
||||||
|
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
||||||
|
private_peer_ids = ""
|
||||||
|
|
||||||
|
##### mempool configuration options #####
|
||||||
|
[mempool]
|
||||||
|
|
||||||
|
recheck = true
|
||||||
|
recheck_empty = true
|
||||||
|
broadcast = true
|
||||||
|
wal_dir = "data/mempool.wal"
|
||||||
|
|
||||||
|
##### consensus configuration options #####
|
||||||
|
[consensus]
|
||||||
|
|
||||||
|
wal_file = "data/cs.wal/wal"
|
||||||
|
|
||||||
|
# All timeouts are in milliseconds
|
||||||
|
timeout_propose = 3000
|
||||||
|
timeout_propose_delta = 500
|
||||||
|
timeout_prevote = 1000
|
||||||
|
timeout_prevote_delta = 500
|
||||||
|
timeout_precommit = 1000
|
||||||
|
timeout_precommit_delta = 500
|
||||||
|
timeout_commit = 1000
|
||||||
|
|
||||||
|
# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0)
|
||||||
|
skip_timeout_commit = false
|
||||||
|
|
||||||
|
# BlockSize
|
||||||
|
max_block_size_txs = 10000
|
||||||
|
max_block_size_bytes = 1
|
||||||
|
|
||||||
|
# EmptyBlocks mode and possible interval between empty blocks in seconds
|
||||||
|
create_empty_blocks = true
|
||||||
|
create_empty_blocks_interval = 0
|
||||||
|
|
||||||
|
# Reactor sleep duration parameters are in milliseconds
|
||||||
|
peer_gossip_sleep_duration = 100
|
||||||
|
peer_query_maj23_sleep_duration = 2000
|
||||||
|
|
||||||
|
##### transactions indexer configuration options #####
|
||||||
|
[tx_index]
|
||||||
|
|
||||||
|
# What indexer to use for transactions
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# 1) "null" (default)
|
||||||
|
# 2) "kv" - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend).
|
||||||
|
indexer = "kv"
|
||||||
|
|
||||||
|
# Comma-separated list of tags to index (by default the only tag is tx hash)
|
||||||
|
#
|
||||||
|
# It's recommended to index only a subset of tags due to possible memory
|
||||||
|
# bloat. This is, of course, depends on the indexer's DB and the volume of
|
||||||
|
# transactions.
|
||||||
|
index_tags = ""
|
||||||
|
|
||||||
|
# When set to true, tells indexer to index all tags. Note this may be not
|
||||||
|
# desirable (see the comment above). IndexTags has a precedence over
|
||||||
|
# IndexAllTags (i.e. when given both, IndexTags will be indexed).
|
||||||
|
index_all_tags = false
|
39
docs/examples/node2/config/genesis.json
Normal file
39
docs/examples/node2/config/genesis.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"genesis_time": "0001-01-01T00:00:00Z",
|
||||||
|
"chain_id": "test-chain-A2i3OZ",
|
||||||
|
"validators": [
|
||||||
|
{
|
||||||
|
"pub_key": {
|
||||||
|
"type": "AC26791624DE60",
|
||||||
|
"value": "D+k4AdjnYPWbB9wmad137Bdpo/kAulOoTRQrLy/Qc4k="
|
||||||
|
},
|
||||||
|
"power": 10,
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pub_key": {
|
||||||
|
"type": "AC26791624DE60",
|
||||||
|
"value": "b56N5GCR1adcVRuENjfKw/mrm2dkhT7wNZXV/SDsKsU="
|
||||||
|
},
|
||||||
|
"power": 10,
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pub_key": {
|
||||||
|
"type": "AC26791624DE60",
|
||||||
|
"value": "IgZDpJvGA0TAamicA8ircy+RX/BkUlj6DXwM791ywIU="
|
||||||
|
},
|
||||||
|
"power": 10,
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pub_key": {
|
||||||
|
"type": "AC26791624DE60",
|
||||||
|
"value": "KGAZfxZvIZ7abbeIQ85U1ECG6+I62KSdaH8ulc0+OiU="
|
||||||
|
},
|
||||||
|
"power": 10,
|
||||||
|
"name": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"app_hash": ""
|
||||||
|
}
|
1
docs/examples/node2/config/node_key.json
Normal file
1
docs/examples/node2/config/node_key.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"priv_key":{"type":"954568A3288910","value":"COHZ/Y2cWGWxJNkRwtpQBt5sYvOnb6Gpz0lO46XERRJFBIdSWD5x1UMGRSTmnvW1ec5G4bMdg6zUZKOZD+vVPg=="}}
|
14
docs/examples/node2/config/priv_validator.json
Normal file
14
docs/examples/node2/config/priv_validator.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"address": "F0AA266949FB29ADA0B679C27889ED930BD1BDA1",
|
||||||
|
"pub_key": {
|
||||||
|
"type": "AC26791624DE60",
|
||||||
|
"value": "IgZDpJvGA0TAamicA8ircy+RX/BkUlj6DXwM791ywIU="
|
||||||
|
},
|
||||||
|
"last_height": 0,
|
||||||
|
"last_round": 0,
|
||||||
|
"last_step": 0,
|
||||||
|
"priv_key": {
|
||||||
|
"type": "954568A3288910",
|
||||||
|
"value": "khADeZ5K/8u/L99DFaZNRq8V5g+EHWbwfqFjhCrppaAiBkOkm8YDRMBqaJwDyKtzL5Ff8GRSWPoNfAzv3XLAhQ=="
|
||||||
|
}
|
||||||
|
}
|
@@ -1,42 +0,0 @@
|
|||||||
{
|
|
||||||
"genesis_time":"0001-01-01T00:00:00Z",
|
|
||||||
"chain_id":"test-chain-wt7apy",
|
|
||||||
"validators":[
|
|
||||||
{
|
|
||||||
"pub_key":{
|
|
||||||
"type":"ed25519",
|
|
||||||
"data":"F08446C80A33E10D620E21450821B58D053778528F2B583D423B3E46EC647D30"
|
|
||||||
},
|
|
||||||
"power":10,
|
|
||||||
"name":"node1"
|
|
||||||
}
|
|
||||||
,
|
|
||||||
{
|
|
||||||
"pub_key":{
|
|
||||||
"type":"ed25519",
|
|
||||||
"data": "A8423F70A9E512643B4B00F7C3701ECAD1F31B0A1FAA45852C41046353B9A07F"
|
|
||||||
},
|
|
||||||
"power":10,
|
|
||||||
"name":"node2"
|
|
||||||
}
|
|
||||||
,
|
|
||||||
{
|
|
||||||
"pub_key":{
|
|
||||||
"type":"ed25519",
|
|
||||||
"data": "E52EFFAEDFE1D618ECDA71DE3B23592B3612CAABA0C10826E4C3120B2198C29A"
|
|
||||||
},
|
|
||||||
"power":10,
|
|
||||||
"name":"node3"
|
|
||||||
}
|
|
||||||
,
|
|
||||||
{
|
|
||||||
"pub_key":{
|
|
||||||
"type":"ed25519",
|
|
||||||
"data": "2B8FC09C07955A02998DFE5AF1AAD1C44115ECA7635FF51A867CF4265D347C07"
|
|
||||||
},
|
|
||||||
"power":10,
|
|
||||||
"name":"node4"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"app_hash":""
|
|
||||||
}
|
|
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"address": "DD6C63A762608A9DDD4A845657743777F63121D6",
|
|
||||||
"pub_key": {
|
|
||||||
"type": "ed25519",
|
|
||||||
"data": "A8423F70A9E512643B4B00F7C3701ECAD1F31B0A1FAA45852C41046353B9A07F"
|
|
||||||
},
|
|
||||||
"last_height": 0,
|
|
||||||
"last_round": 0,
|
|
||||||
"last_step": 0,
|
|
||||||
"last_signature": null,
|
|
||||||
"priv_key": {
|
|
||||||
"type": "ed25519",
|
|
||||||
"data": "7B0DE666FF5E9B437D284BCE767F612381890C018B93B0A105D2E829A568DA6FA8423F70A9E512643B4B00F7C3701ECAD1F31B0A1FAA45852C41046353B9A07F"
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,15 +0,0 @@
|
|||||||
# This is a TOML config file.
|
|
||||||
# For more information, see https://github.com/toml-lang/toml
|
|
||||||
|
|
||||||
proxy_app = "tcp://127.0.0.1:46658"
|
|
||||||
moniker = "penguin"
|
|
||||||
fast_sync = true
|
|
||||||
db_backend = "leveldb"
|
|
||||||
log_level = "state:info,*:error"
|
|
||||||
|
|
||||||
[rpc]
|
|
||||||
laddr = "tcp://0.0.0.0:46657"
|
|
||||||
|
|
||||||
[p2p]
|
|
||||||
laddr = "tcp://0.0.0.0:46656"
|
|
||||||
seeds = ""
|
|
166
docs/examples/node3/config/config.toml
Normal file
166
docs/examples/node3/config/config.toml
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
# This is a TOML config file.
|
||||||
|
# For more information, see https://github.com/toml-lang/toml
|
||||||
|
|
||||||
|
##### main base config options #####
|
||||||
|
|
||||||
|
# TCP or UNIX socket address of the ABCI application,
|
||||||
|
# or the name of an ABCI application compiled in with the Tendermint binary
|
||||||
|
proxy_app = "tcp://127.0.0.1:46658"
|
||||||
|
|
||||||
|
# A custom human readable name for this node
|
||||||
|
moniker = "delta"
|
||||||
|
|
||||||
|
# If this node is many blocks behind the tip of the chain, FastSync
|
||||||
|
# allows them to catchup quickly by downloading blocks in parallel
|
||||||
|
# and verifying their commits
|
||||||
|
fast_sync = true
|
||||||
|
|
||||||
|
# Database backend: leveldb | memdb
|
||||||
|
db_backend = "leveldb"
|
||||||
|
|
||||||
|
# Database directory
|
||||||
|
db_path = "data"
|
||||||
|
|
||||||
|
# Output level for logging, including package level options
|
||||||
|
log_level = "main:info,state:info,*:error"
|
||||||
|
|
||||||
|
##### additional base config options #####
|
||||||
|
|
||||||
|
# Path to the JSON file containing the initial validator set and other meta data
|
||||||
|
genesis_file = "config/genesis.json"
|
||||||
|
|
||||||
|
# Path to the JSON file containing the private key to use as a validator in the consensus protocol
|
||||||
|
priv_validator_file = "config/priv_validator.json"
|
||||||
|
|
||||||
|
# Path to the JSON file containing the private key to use for node authentication in the p2p protocol
|
||||||
|
node_key_file = "config/node_key.json"
|
||||||
|
|
||||||
|
# Mechanism to connect to the ABCI application: socket | grpc
|
||||||
|
abci = "socket"
|
||||||
|
|
||||||
|
# TCP or UNIX socket address for the profiling server to listen on
|
||||||
|
prof_laddr = ""
|
||||||
|
|
||||||
|
# If true, query the ABCI app on connecting to a new peer
|
||||||
|
# so the app can decide if we should keep the connection or not
|
||||||
|
filter_peers = false
|
||||||
|
|
||||||
|
##### advanced configuration options #####
|
||||||
|
|
||||||
|
##### rpc server configuration options #####
|
||||||
|
[rpc]
|
||||||
|
|
||||||
|
# TCP or UNIX socket address for the RPC server to listen on
|
||||||
|
laddr = "tcp://0.0.0.0:46657"
|
||||||
|
|
||||||
|
# TCP or UNIX socket address for the gRPC server to listen on
|
||||||
|
# NOTE: This server only supports /broadcast_tx_commit
|
||||||
|
grpc_laddr = ""
|
||||||
|
|
||||||
|
# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool
|
||||||
|
unsafe = false
|
||||||
|
|
||||||
|
##### peer to peer configuration options #####
|
||||||
|
[p2p]
|
||||||
|
|
||||||
|
# Address to listen for incoming connections
|
||||||
|
laddr = "tcp://0.0.0.0:46656"
|
||||||
|
|
||||||
|
# Comma separated list of seed nodes to connect to
|
||||||
|
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
|
||||||
|
addr_book_file = "config/addrbook.json"
|
||||||
|
|
||||||
|
# Set true for strict address routability rules
|
||||||
|
addr_book_strict = true
|
||||||
|
|
||||||
|
# Time to wait before flushing messages out on the connection, in ms
|
||||||
|
flush_throttle_timeout = 100
|
||||||
|
|
||||||
|
# Maximum number of peers to connect to
|
||||||
|
max_num_peers = 50
|
||||||
|
|
||||||
|
# Maximum size of a message packet payload, in bytes
|
||||||
|
max_packet_msg_payload_size = 1024
|
||||||
|
|
||||||
|
# Rate at which packets can be sent, in bytes/second
|
||||||
|
send_rate = 512000
|
||||||
|
|
||||||
|
# Rate at which packets can be received, in bytes/second
|
||||||
|
recv_rate = 512000
|
||||||
|
|
||||||
|
# Set true to enable the peer-exchange reactor
|
||||||
|
pex = true
|
||||||
|
|
||||||
|
# Seed mode, in which node constantly crawls the network and looks for
|
||||||
|
# peers. If another node asks it for addresses, it responds and disconnects.
|
||||||
|
#
|
||||||
|
# Does not work if the peer-exchange reactor is disabled.
|
||||||
|
seed_mode = false
|
||||||
|
|
||||||
|
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
||||||
|
private_peer_ids = ""
|
||||||
|
|
||||||
|
##### mempool configuration options #####
|
||||||
|
[mempool]
|
||||||
|
|
||||||
|
recheck = true
|
||||||
|
recheck_empty = true
|
||||||
|
broadcast = true
|
||||||
|
wal_dir = "data/mempool.wal"
|
||||||
|
|
||||||
|
##### consensus configuration options #####
|
||||||
|
[consensus]
|
||||||
|
|
||||||
|
wal_file = "data/cs.wal/wal"
|
||||||
|
|
||||||
|
# All timeouts are in milliseconds
|
||||||
|
timeout_propose = 3000
|
||||||
|
timeout_propose_delta = 500
|
||||||
|
timeout_prevote = 1000
|
||||||
|
timeout_prevote_delta = 500
|
||||||
|
timeout_precommit = 1000
|
||||||
|
timeout_precommit_delta = 500
|
||||||
|
timeout_commit = 1000
|
||||||
|
|
||||||
|
# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0)
|
||||||
|
skip_timeout_commit = false
|
||||||
|
|
||||||
|
# BlockSize
|
||||||
|
max_block_size_txs = 10000
|
||||||
|
max_block_size_bytes = 1
|
||||||
|
|
||||||
|
# EmptyBlocks mode and possible interval between empty blocks in seconds
|
||||||
|
create_empty_blocks = true
|
||||||
|
create_empty_blocks_interval = 0
|
||||||
|
|
||||||
|
# Reactor sleep duration parameters are in milliseconds
|
||||||
|
peer_gossip_sleep_duration = 100
|
||||||
|
peer_query_maj23_sleep_duration = 2000
|
||||||
|
|
||||||
|
##### transactions indexer configuration options #####
|
||||||
|
[tx_index]
|
||||||
|
|
||||||
|
# What indexer to use for transactions
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# 1) "null" (default)
|
||||||
|
# 2) "kv" - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend).
|
||||||
|
indexer = "kv"
|
||||||
|
|
||||||
|
# Comma-separated list of tags to index (by default the only tag is tx hash)
|
||||||
|
#
|
||||||
|
# It's recommended to index only a subset of tags due to possible memory
|
||||||
|
# bloat. This is, of course, depends on the indexer's DB and the volume of
|
||||||
|
# transactions.
|
||||||
|
index_tags = ""
|
||||||
|
|
||||||
|
# When set to true, tells indexer to index all tags. Note this may be not
|
||||||
|
# desirable (see the comment above). IndexTags has a precedence over
|
||||||
|
# IndexAllTags (i.e. when given both, IndexTags will be indexed).
|
||||||
|
index_all_tags = false
|
39
docs/examples/node3/config/genesis.json
Normal file
39
docs/examples/node3/config/genesis.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"genesis_time": "0001-01-01T00:00:00Z",
|
||||||
|
"chain_id": "test-chain-A2i3OZ",
|
||||||
|
"validators": [
|
||||||
|
{
|
||||||
|
"pub_key": {
|
||||||
|
"type": "AC26791624DE60",
|
||||||
|
"value": "D+k4AdjnYPWbB9wmad137Bdpo/kAulOoTRQrLy/Qc4k="
|
||||||
|
},
|
||||||
|
"power": 10,
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pub_key": {
|
||||||
|
"type": "AC26791624DE60",
|
||||||
|
"value": "b56N5GCR1adcVRuENjfKw/mrm2dkhT7wNZXV/SDsKsU="
|
||||||
|
},
|
||||||
|
"power": 10,
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pub_key": {
|
||||||
|
"type": "AC26791624DE60",
|
||||||
|
"value": "IgZDpJvGA0TAamicA8ircy+RX/BkUlj6DXwM791ywIU="
|
||||||
|
},
|
||||||
|
"power": 10,
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pub_key": {
|
||||||
|
"type": "AC26791624DE60",
|
||||||
|
"value": "KGAZfxZvIZ7abbeIQ85U1ECG6+I62KSdaH8ulc0+OiU="
|
||||||
|
},
|
||||||
|
"power": 10,
|
||||||
|
"name": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"app_hash": ""
|
||||||
|
}
|
1
docs/examples/node3/config/node_key.json
Normal file
1
docs/examples/node3/config/node_key.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"priv_key":{"type":"954568A3288910","value":"9Y9xp/tUJJ6pHTF5SUV0bGKYSdVbFtMHu+Lr8S0JBSZAwneaejnfOEU1LMKOnQ07skrDUaJcj5di3jAyjxJzqg=="}}
|
14
docs/examples/node3/config/priv_validator.json
Normal file
14
docs/examples/node3/config/priv_validator.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"address": "9A1A6914EB5F4FF0269C7EEEE627C27310CC64F9",
|
||||||
|
"pub_key": {
|
||||||
|
"type": "AC26791624DE60",
|
||||||
|
"value": "KGAZfxZvIZ7abbeIQ85U1ECG6+I62KSdaH8ulc0+OiU="
|
||||||
|
},
|
||||||
|
"last_height": 0,
|
||||||
|
"last_round": 0,
|
||||||
|
"last_step": 0,
|
||||||
|
"priv_key": {
|
||||||
|
"type": "954568A3288910",
|
||||||
|
"value": "jb52LZ5gp+eQ8nJlFK1z06nBMp1gD8ICmyzdM1icGOgoYBl/Fm8hntptt4hDzlTUQIbr4jrYpJ1ofy6VzT46JQ=="
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user