mirror of
https://github.com/fluencelabs/tendermint
synced 2025-07-16 04:41:59 +00:00
Compare commits
70 Commits
v0.27.0-de
...
v0.28.0-be
Author | SHA1 | Date | |
---|---|---|---|
|
2e8de115db | ||
|
84cfb974ac | ||
|
908242ad91 | ||
|
1895cde590 | ||
|
be00cd1add | ||
|
a6011c007d | ||
|
ef94a322b8 | ||
|
7f607d0ce2 | ||
|
81c51cd4fc | ||
|
51094f9417 | ||
|
7644d27307 | ||
|
764cfe33aa | ||
|
616c3a4bae | ||
|
04e97f599a | ||
|
56a4fb4d72 | ||
|
49017a5787 | ||
|
6a80412a01 | ||
|
2348f38927 | ||
|
41e2eeee9c | ||
|
a88e283a9d | ||
|
1e1ca15bcc | ||
|
c6604b5a9b | ||
|
c510f823e7 | ||
|
daddebac29 | ||
|
30f346fe44 | ||
|
4d8f29f79c | ||
|
2182f6a702 | ||
|
a06912b579 | ||
|
0ff715125b | ||
|
385977d5e8 | ||
|
0138530df2 | ||
|
0533c73a50 | ||
|
1beb45511c | ||
|
4a568fcedb | ||
|
b3141d7d02 | ||
|
9a6dd96cba | ||
|
9fa959619a | ||
|
1f09818770 | ||
|
e4806f980b | ||
|
b53a2712df | ||
|
a75dab492c | ||
|
7c9e767e1f | ||
|
f82a8ff73a | ||
|
ae275d791e | ||
|
f5cca9f121 | ||
|
3fbe9f235a | ||
|
f7e463f6d3 | ||
|
bc2a9b20c0 | ||
|
9e075d8dd5 | ||
|
8003786c9a | ||
|
2594cec116 | ||
|
df32ea4be5 | ||
|
f69e2c6d6c | ||
|
d5d0d2bd77 | ||
|
41eaf0e31d | ||
|
68b467886a | ||
|
2f64717bb5 | ||
|
c4a1cfc5c2 | ||
|
0f96bea41d | ||
|
9c236ffd6c | ||
|
9f8761d105 | ||
|
5413c11150 | ||
|
a14fd8eba0 | ||
|
1bb7e31d63 | ||
|
222b8978c8 | ||
|
d9a1aad5c5 | ||
|
8ef0c2681d | ||
|
c4d93fd27b | ||
|
dc2a338d96 | ||
|
725ed7969a |
@@ -3,10 +3,17 @@ version: 2
|
||||
defaults: &defaults
|
||||
working_directory: /go/src/github.com/tendermint/tendermint
|
||||
docker:
|
||||
- image: circleci/golang:1.10.3
|
||||
- image: circleci/golang:1.11.4
|
||||
environment:
|
||||
GOBIN: /tmp/workspace/bin
|
||||
|
||||
docs_update_config: &docs_update_config
|
||||
working_directory: ~/repo
|
||||
docker:
|
||||
- image: tendermint/docs_deployment
|
||||
environment:
|
||||
AWS_REGION: us-east-1
|
||||
|
||||
jobs:
|
||||
setup_dependencies:
|
||||
<<: *defaults
|
||||
@@ -339,10 +346,25 @@ jobs:
|
||||
name: upload
|
||||
command: bash .circleci/codecov.sh -f coverage.txt
|
||||
|
||||
deploy_docs:
|
||||
<<: *docs_update_config
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Trigger website build
|
||||
command: |
|
||||
chamber exec tendermint -- start_website_build
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
test-suite:
|
||||
jobs:
|
||||
- deploy_docs:
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- develop
|
||||
- setup_dependencies
|
||||
- lint:
|
||||
requires:
|
||||
|
184
CHANGELOG.md
184
CHANGELOG.md
@@ -1,13 +1,191 @@
|
||||
# Changelog
|
||||
|
||||
## v0.28.0
|
||||
|
||||
*January 14th, 2019*
|
||||
|
||||
Special thanks to external contributors on this release:
|
||||
@fmauricios, @gianfelipe93, @husio, @needkane, @srmo, @yutianwu
|
||||
|
||||
This release is primarily about upgrades to the `privval` system -
|
||||
separating the `priv_validator.json` into distinct config and data files, and
|
||||
refactoring the socket validator to support reconnections.
|
||||
|
||||
See [UPGRADING.md](UPGRADING.md) for more details.
|
||||
|
||||
### BREAKING CHANGES:
|
||||
|
||||
* CLI/RPC/Config
|
||||
- [cli] Removed `node` `--proxy_app=dummy` option. Use `kvstore` (`persistent_kvstore`) instead.
|
||||
- [cli] Renamed `node` `--proxy_app=nilapp` to `--proxy_app=noop`.
|
||||
- [config] [\#2992](https://github.com/tendermint/tendermint/issues/2992) `allow_duplicate_ip` is now set to false
|
||||
- [privval] [\#1181](https://github.com/tendermint/tendermint/issues/1181) Split immutable and mutable parts of `priv_validator.json`
|
||||
(@yutianwu)
|
||||
- [privval] [\#2926](https://github.com/tendermint/tendermint/issues/2926) Split up `PubKeyMsg` into `PubKeyRequest` and `PubKeyResponse` to be consistent with other message types
|
||||
- [privval] [\#2923](https://github.com/tendermint/tendermint/issues/2923) Listen for unix socket connections instead of dialing them
|
||||
|
||||
* Apps
|
||||
|
||||
* Go API
|
||||
- [types] [\#2981](https://github.com/tendermint/tendermint/issues/2981) Remove `PrivValidator.GetAddress()`
|
||||
|
||||
* Blockchain Protocol
|
||||
|
||||
* P2P Protocol
|
||||
|
||||
### FEATURES:
|
||||
- [rpc] [\#3052](https://github.com/tendermint/tendermint/issues/3052) Include peer's remote IP in `/net_info`
|
||||
|
||||
### IMPROVEMENTS:
|
||||
- [consensus] [\#3086](https://github.com/tendermint/tendermint/issues/3086) Log peerID on ignored votes (@srmo)
|
||||
- [docs] [\#3061](https://github.com/tendermint/tendermint/issues/3061) Added spec on signing consensus msgs at
|
||||
./docs/spec/consensus/signing.md
|
||||
- [privval] [\#2948](https://github.com/tendermint/tendermint/issues/2948) Memoize pubkey so it's only requested once on startup
|
||||
- [privval] [\#2923](https://github.com/tendermint/tendermint/issues/2923) Retry RemoteSigner connections on error
|
||||
|
||||
### BUG FIXES:
|
||||
|
||||
- [types] [\#2926](https://github.com/tendermint/tendermint/issues/2926) Do not panic if retrieving the private validator's public key fails
|
||||
- [rpc] [\#3053](https://github.com/tendermint/tendermint/issues/3053) Fix internal error in `/tx_search` when results are empty
|
||||
(@gianfelipe93)
|
||||
- [crypto/multisig] [\#3102](https://github.com/tendermint/tendermint/issues/3102) Fix multisig keys address length
|
||||
- [crypto/encoding] [\#3101](https://github.com/tendermint/tendermint/issues/3101) Fix `PubKeyMultisigThreshold` unmarshalling into `crypto.PubKey` interface
|
||||
- [build] [\#3085](https://github.com/tendermint/tendermint/issues/3085) Fix `Version` field in build scripts (@husio)
|
||||
- [p2p/conn] [\#3111](https://github.com/tendermint/tendermint/issues/3111) Make SecretConnection thread safe
|
||||
|
||||
## v0.27.4
|
||||
|
||||
*December 21st, 2018*
|
||||
|
||||
### BUG FIXES:
|
||||
|
||||
- [mempool] [\#3036](https://github.com/tendermint/tendermint/issues/3036) Fix
|
||||
LRU cache by popping the least recently used item when the cache is full,
|
||||
not the most recently used one!
|
||||
|
||||
## v0.27.3
|
||||
|
||||
*December 16th, 2018*
|
||||
|
||||
### BREAKING CHANGES:
|
||||
|
||||
* Go API
|
||||
|
||||
- [dep] [\#3027](https://github.com/tendermint/tendermint/issues/3027) Revert to mainline Go crypto library, eliminating the modified
|
||||
`bcrypt.GenerateFromPassword`
|
||||
|
||||
## v0.27.2
|
||||
|
||||
*December 16th, 2018*
|
||||
|
||||
### IMPROVEMENTS:
|
||||
|
||||
- [node] [\#3025](https://github.com/tendermint/tendermint/issues/3025) Validate NodeInfo addresses on startup.
|
||||
|
||||
### BUG FIXES:
|
||||
|
||||
- [p2p] [\#3025](https://github.com/tendermint/tendermint/pull/3025) Revert to using defers in addrbook. Fixes deadlocks in pex and consensus upon invalid ExternalAddr/ListenAddr configuration.
|
||||
|
||||
## v0.27.1
|
||||
|
||||
*December 15th, 2018*
|
||||
|
||||
Special thanks to external contributors on this release:
|
||||
@danil-lashin, @hleb-albau, @james-ray, @leo-xinwang
|
||||
|
||||
### FEATURES:
|
||||
- [rpc] [\#2964](https://github.com/tendermint/tendermint/issues/2964) Add `UnconfirmedTxs(limit)` and `NumUnconfirmedTxs()` methods to HTTP/Local clients (@danil-lashin)
|
||||
- [docs] [\#3004](https://github.com/tendermint/tendermint/issues/3004) Enable full-text search on docs pages
|
||||
|
||||
### IMPROVEMENTS:
|
||||
- [consensus] [\#2971](https://github.com/tendermint/tendermint/issues/2971) Return error if ValidatorSet is empty after InitChain
|
||||
(@leo-xinwang)
|
||||
- [ci/cd] [\#3005](https://github.com/tendermint/tendermint/issues/3005) Updated CircleCI job to trigger website build when docs are updated
|
||||
- [docs] Various updates
|
||||
|
||||
### BUG FIXES:
|
||||
- [cmd] [\#2983](https://github.com/tendermint/tendermint/issues/2983) `testnet` command always sets `addr_book_strict = false`
|
||||
- [config] [\#2980](https://github.com/tendermint/tendermint/issues/2980) Fix CORS options formatting
|
||||
- [kv indexer] [\#2912](https://github.com/tendermint/tendermint/issues/2912) Don't ignore key when executing CONTAINS
|
||||
- [mempool] [\#2961](https://github.com/tendermint/tendermint/issues/2961) Call `notifyTxsAvailable` if there're txs left after committing a block, but recheck=false
|
||||
- [mempool] [\#2994](https://github.com/tendermint/tendermint/issues/2994) Reject txs with negative GasWanted
|
||||
- [p2p] [\#2990](https://github.com/tendermint/tendermint/issues/2990) Fix a bug where seeds don't disconnect from a peer after 3h
|
||||
- [consensus] [\#3006](https://github.com/tendermint/tendermint/issues/3006) Save state after InitChain only when stateHeight is also 0 (@james-ray)
|
||||
|
||||
## v0.27.0
|
||||
|
||||
*December 5th, 2018*
|
||||
|
||||
Special thanks to external contributors on this release:
|
||||
@danil-lashin, @srmo
|
||||
|
||||
Special thanks to @dlguddus for discovering a [major
|
||||
issue](https://github.com/tendermint/tendermint/issues/2718#issuecomment-440888677)
|
||||
in the proposer selection algorithm.
|
||||
|
||||
Friendly reminder, we have a [bug bounty
|
||||
program](https://hackerone.com/tendermint).
|
||||
|
||||
This release is primarily about fixes to the proposer selection algorithm
|
||||
in preparation for the [Cosmos Game of
|
||||
Stakes](https://blog.cosmos.network/the-game-of-stakes-is-open-for-registration-83a404746ee6).
|
||||
It also makes use of the `ConsensusParams.Validator.PubKeyTypes` to restrict the
|
||||
key types that can be used by validators, and removes the `Heartbeat` consensus
|
||||
message.
|
||||
|
||||
### BREAKING CHANGES:
|
||||
|
||||
* CLI/RPC/Config
|
||||
- [rpc] [\#2932](https://github.com/tendermint/tendermint/issues/2932) Rename `accum` to `proposer_priority`
|
||||
|
||||
* Go API
|
||||
- [db] [\#2913](https://github.com/tendermint/tendermint/pull/2913)
|
||||
ReverseIterator API change: start < end, and end is exclusive.
|
||||
- [types] [\#2932](https://github.com/tendermint/tendermint/issues/2932) Rename `Validator.Accum` to `Validator.ProposerPriority`
|
||||
|
||||
* Blockchain Protocol
|
||||
- [state] [\#2714](https://github.com/tendermint/tendermint/issues/2714) Validators can now only use pubkeys allowed within
|
||||
ConsensusParams.Validator.PubKeyTypes
|
||||
|
||||
* P2P Protocol
|
||||
- [consensus] [\#2871](https://github.com/tendermint/tendermint/issues/2871)
|
||||
Remove *ProposalHeartbeat* message as it serves no real purpose (@srmo)
|
||||
- [state] Fixes for proposer selection:
|
||||
- [\#2785](https://github.com/tendermint/tendermint/issues/2785) Accum for new validators is `-1.125*totalVotingPower` instead of 0
|
||||
- [\#2941](https://github.com/tendermint/tendermint/issues/2941) val.Accum is preserved during ValidatorSet.Update to avoid being
|
||||
reset to 0
|
||||
|
||||
### IMPROVEMENTS:
|
||||
|
||||
- [state] [\#2929](https://github.com/tendermint/tendermint/issues/2929) Minor refactor of updateState logic (@danil-lashin)
|
||||
- [node] [\#2959](https://github.com/tendermint/tendermint/issues/2959) Allow node to start even if software's BlockProtocol is
|
||||
different from state's BlockProtocol
|
||||
- [pex] [\#2959](https://github.com/tendermint/tendermint/issues/2959) Pex reactor logger uses `module=pex`
|
||||
|
||||
### BUG FIXES:
|
||||
|
||||
- [p2p] [\#2968](https://github.com/tendermint/tendermint/issues/2968) Panic on transport error rather than continuing to run but not
|
||||
accept new connections
|
||||
- [p2p] [\#2969](https://github.com/tendermint/tendermint/issues/2969) Fix mismatch in peer count between `/net_info` and the prometheus
|
||||
metrics
|
||||
- [rpc] [\#2408](https://github.com/tendermint/tendermint/issues/2408) `/broadcast_tx_commit`: Fix "interface conversion: interface {} in nil, not EventDataTx" panic (could happen if somebody sent a tx using `/broadcast_tx_commit` while Tendermint was being stopped)
|
||||
- [state] [\#2785](https://github.com/tendermint/tendermint/issues/2785) Fix accum for new validators to be `-1.125*totalVotingPower`
|
||||
instead of 0, forcing them to wait before becoming the proposer. Also:
|
||||
- do not batch clip
|
||||
- keep accums averaged near 0
|
||||
- [txindex/kv] [\#2925](https://github.com/tendermint/tendermint/issues/2925) Don't return false positives when range searching for a prefix of a tag value
|
||||
- [types] [\#2938](https://github.com/tendermint/tendermint/issues/2938) Fix regression in v0.26.4 where we panic on empty
|
||||
genDoc.Validators
|
||||
- [types] [\#2941](https://github.com/tendermint/tendermint/issues/2941) Preserve val.Accum during ValidatorSet.Update to avoid it being
|
||||
reset to 0 every time a validator is updated
|
||||
|
||||
## v0.26.4
|
||||
|
||||
*November 27th, 2018*
|
||||
|
||||
Special thanks to external contributors on this release:
|
||||
ackratos, goolAdapter, james-ray, joe-bowman, kostko,
|
||||
nagarajmanjunath, tomtau
|
||||
|
||||
@ackratos, @goolAdapter, @james-ray, @joe-bowman, @kostko,
|
||||
@nagarajmanjunath, @tomtau
|
||||
|
||||
Friendly reminder, we have a [bug bounty
|
||||
program](https://hackerone.com/tendermint).
|
||||
|
@@ -1,48 +1,23 @@
|
||||
# Pending
|
||||
|
||||
## v0.27.0
|
||||
## v0.29.0
|
||||
|
||||
*TBD*
|
||||
|
||||
Special thanks to external contributors on this release:
|
||||
|
||||
Friendly reminder, we have a [bug bounty
|
||||
program](https://hackerone.com/tendermint).
|
||||
|
||||
### BREAKING CHANGES:
|
||||
|
||||
* CLI/RPC/Config
|
||||
- [rpc] \#2932 Rename `accum` to `proposer_priority`
|
||||
|
||||
* Apps
|
||||
|
||||
* Go API
|
||||
- [db] [\#2913](https://github.com/tendermint/tendermint/pull/2913)
|
||||
ReverseIterator API change -- start < end, and end is exclusive.
|
||||
- [types] \#2932 Rename `Validator.Accum` to `Validator.ProposerPriority`
|
||||
|
||||
* Blockchain Protocol
|
||||
- [state] \#2714 Validators can now only use pubkeys allowed within
|
||||
ConsensusParams.ValidatorParams
|
||||
|
||||
* P2P Protocol
|
||||
- [consensus] [\#2871](https://github.com/tendermint/tendermint/issues/2871)
|
||||
Remove *ProposalHeartbeat* message as it serves no real purpose
|
||||
- [state] Fixes for proposer selection:
|
||||
- \#2785 Accum for new validators is `-1.125*totalVotingPower` instead of 0
|
||||
- \#2941 val.Accum is preserved during ValidatorSet.Update to avoid being
|
||||
reset to 0
|
||||
|
||||
### FEATURES:
|
||||
|
||||
### IMPROVEMENTS:
|
||||
|
||||
### BUG FIXES:
|
||||
- [types] \#2938 Fix regression in v0.26.4 where we panic on empty
|
||||
genDoc.Validators
|
||||
- [state] \#2785 Fix accum for new validators to be `-1.125*totalVotingPower`
|
||||
instead of 0, forcing them to wait before becoming the proposer. Also:
|
||||
- do not batch clip
|
||||
- keep accums averaged near 0
|
||||
- [types] \#2941 Preserve val.Accum during ValidatorSet.Update to avoid it being
|
||||
reset to 0 every time a validator is updated
|
||||
|
@@ -3,7 +3,7 @@ set -e
|
||||
|
||||
# Get the tag from the version, or try to figure it out.
|
||||
if [ -z "$TAG" ]; then
|
||||
TAG=$(awk -F\" '/Version =/ { print $2; exit }' < ../version/version.go)
|
||||
TAG=$(awk -F\" '/TMCoreSemVer =/ { print $2; exit }' < ../version/version.go)
|
||||
fi
|
||||
if [ -z "$TAG" ]; then
|
||||
echo "Please specify a tag."
|
||||
|
@@ -3,7 +3,7 @@ set -e
|
||||
|
||||
# Get the tag from the version, or try to figure it out.
|
||||
if [ -z "$TAG" ]; then
|
||||
TAG=$(awk -F\" '/Version =/ { print $2; exit }' < ../version/version.go)
|
||||
TAG=$(awk -F\" '/TMCoreSemVer =/ { print $2; exit }' < ../version/version.go)
|
||||
fi
|
||||
if [ -z "$TAG" ]; then
|
||||
echo "Please specify a tag."
|
||||
|
5
Gopkg.lock
generated
5
Gopkg.lock
generated
@@ -376,7 +376,7 @@
|
||||
version = "v0.14.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:72b71e3a29775e5752ed7a8012052a3dee165e27ec18cedddae5288058f09acf"
|
||||
digest = "1:00d2b3e64cdc3fa69aa250dfbe4cc38c4837d4f37e62279be2ae52107ffbbb44"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"bcrypt",
|
||||
@@ -397,8 +397,7 @@
|
||||
"salsa20/salsa",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "3764759f34a542a3aef74d6b02e35be7ab893bba"
|
||||
source = "github.com/tendermint/crypto"
|
||||
revision = "505ab145d0a99da450461ae2c1a9f6cd10d1f447"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d36f55a999540d29b6ea3c2ea29d71c76b1d9853fdcd3e5c5cb4836f2ba118f1"
|
||||
|
@@ -81,8 +81,7 @@
|
||||
|
||||
[[constraint]]
|
||||
name = "golang.org/x/crypto"
|
||||
source = "github.com/tendermint/crypto"
|
||||
revision = "3764759f34a542a3aef74d6b02e35be7ab893bba"
|
||||
revision = "505ab145d0a99da450461ae2c1a9f6cd10d1f447"
|
||||
|
||||
[[override]]
|
||||
name = "github.com/jmhodges/levigo"
|
||||
|
1
Makefile
1
Makefile
@@ -294,6 +294,7 @@ build-linux:
|
||||
build-docker-localnode:
|
||||
cd networks/local
|
||||
make
|
||||
cd -
|
||||
|
||||
# Run a 4-node testnet locally
|
||||
localnet-start: localnet-stop
|
||||
|
95
README.md
95
README.md
@@ -1,8 +1,8 @@
|
||||
# Tendermint
|
||||
|
||||
[Byzantine-Fault Tolerant](https://en.wikipedia.org/wiki/Byzantine_fault_tolerance)
|
||||
[State Machine Replication](https://en.wikipedia.org/wiki/State_machine_replication).
|
||||
Or [Blockchain](https://en.wikipedia.org/wiki/Blockchain_(database)) for short.
|
||||
[State Machines](https://en.wikipedia.org/wiki/State_machine_replication).
|
||||
Or [Blockchain](https://en.wikipedia.org/wiki/Blockchain_(database)), for short.
|
||||
|
||||
[](https://github.com/tendermint/tendermint/releases/latest)
|
||||
[
|
||||
- [Remote cluster using terraform and ansible](/docs/networks/terraform-and-ansible.md)
|
||||
- [Join the Cosmos testnet](https://cosmos.network/testnet)
|
||||
|
||||
## Resources
|
||||
|
||||
### Tendermint Core
|
||||
|
||||
For details about the blockchain data structures and the p2p protocols, see the
|
||||
the [Tendermint specification](/docs/spec).
|
||||
|
||||
For details on using the software, see the [documentation](/docs/) which is also
|
||||
hosted at: https://tendermint.com/docs/
|
||||
|
||||
### Tools
|
||||
|
||||
Benchmarking and monitoring is provided by `tm-bench` and `tm-monitor`, respectively.
|
||||
Their code is found [here](/tools) and these binaries need to be built seperately.
|
||||
Additional documentation is found [here](/docs/tools).
|
||||
|
||||
### Sub-projects
|
||||
|
||||
* [Amino](http://github.com/tendermint/go-amino), a reflection-based improvement on proto3
|
||||
* [IAVL](http://github.com/tendermint/iavl), Merkleized IAVL+ Tree implementation
|
||||
|
||||
### Applications
|
||||
|
||||
* [Cosmos SDK](http://github.com/cosmos/cosmos-sdk); a cryptocurrency application framework
|
||||
* [Ethermint](http://github.com/cosmos/ethermint); Ethereum on Tendermint
|
||||
* [Many more](https://tendermint.com/ecosystem)
|
||||
|
||||
### Research
|
||||
|
||||
* [The latest gossip on BFT consensus](https://arxiv.org/abs/1807.04938)
|
||||
* [Master's Thesis on Tendermint](https://atrium.lib.uoguelph.ca/xmlui/handle/10214/9769)
|
||||
* [Original Whitepaper](https://tendermint.com/static/docs/tendermint.pdf)
|
||||
* [Blog](https://blog.cosmos.network/tendermint/home)
|
||||
|
||||
## Contributing
|
||||
|
||||
Yay open source! Please see our [contributing guidelines](CONTRIBUTING.md).
|
||||
Please abide by the [Code of Conduct](CODE_OF_CONDUCT.md) in all interactions,
|
||||
and the [contributing guidelines](CONTRIBUTING.md) when submitting code.
|
||||
|
||||
Join the larger community on the [forum](https://forum.cosmos.network/) and the [chat](https://riot.im/app/#/room/#tendermint:matrix.org).
|
||||
|
||||
To learn more about the structure of the software, watch the [Developer
|
||||
Sessions](https://www.youtube.com/playlist?list=PLdQIb0qr3pnBbG5ZG-0gr3zM86_s8Rpqv)
|
||||
and read some [Architectural
|
||||
Decision Records](https://github.com/tendermint/tendermint/tree/master/docs/architecture).
|
||||
|
||||
Learn more by reading the code and comparing it to the
|
||||
[specification](https://github.com/tendermint/tendermint/tree/develop/docs/spec).
|
||||
|
||||
## Versioning
|
||||
|
||||
### SemVer
|
||||
### Semantic Versioning
|
||||
|
||||
Tendermint uses [SemVer](http://semver.org/) to determine when and how the version changes.
|
||||
Tendermint uses [Semantic Versioning](http://semver.org/) to determine when and how the version changes.
|
||||
According to SemVer, anything in the public API can change at any time before version 1.0.0
|
||||
|
||||
To provide some stability to Tendermint users in these 0.X.X days, the MINOR version is used
|
||||
@@ -145,8 +122,40 @@ data into the new chain.
|
||||
However, any bump in the PATCH version should be compatible with existing histories
|
||||
(if not please open an [issue](https://github.com/tendermint/tendermint/issues)).
|
||||
|
||||
For more information on upgrading, see [here](./UPGRADING.md)
|
||||
For more information on upgrading, see [UPGRADING.md](./UPGRADING.md)
|
||||
|
||||
## Code of Conduct
|
||||
## Resources
|
||||
|
||||
### Tendermint Core
|
||||
|
||||
For details about the blockchain data structures and the p2p protocols, see the
|
||||
[Tendermint specification](/docs/spec).
|
||||
|
||||
For details on using the software, see the [documentation](/docs/) which is also
|
||||
hosted at: https://tendermint.com/docs/
|
||||
|
||||
### Tools
|
||||
|
||||
Benchmarking and monitoring is provided by `tm-bench` and `tm-monitor`, respectively.
|
||||
Their code is found [here](/tools) and these binaries need to be built seperately.
|
||||
Additional documentation is found [here](/docs/tools).
|
||||
|
||||
### Sub-projects
|
||||
|
||||
* [Amino](http://github.com/tendermint/go-amino), reflection-based proto3, with
|
||||
interfaces
|
||||
* [IAVL](http://github.com/tendermint/iavl), Merkleized IAVL+ Tree implementation
|
||||
|
||||
### Applications
|
||||
|
||||
* [Cosmos SDK](http://github.com/cosmos/cosmos-sdk); a cryptocurrency application framework
|
||||
* [Ethermint](http://github.com/cosmos/ethermint); Ethereum on Tendermint
|
||||
* [Many more](https://tendermint.com/ecosystem)
|
||||
|
||||
### Research
|
||||
|
||||
* [The latest gossip on BFT consensus](https://arxiv.org/abs/1807.04938)
|
||||
* [Master's Thesis on Tendermint](https://atrium.lib.uoguelph.ca/xmlui/handle/10214/9769)
|
||||
* [Original Whitepaper](https://tendermint.com/static/docs/tendermint.pdf)
|
||||
* [Blog](https://blog.cosmos.network/tendermint/home)
|
||||
|
||||
Please read, understand and adhere to our [code of conduct](CODE_OF_CONDUCT.md).
|
||||
|
95
UPGRADING.md
95
UPGRADING.md
@@ -3,9 +3,100 @@
|
||||
This guide provides steps to be followed when you upgrade your applications to
|
||||
a newer version of Tendermint Core.
|
||||
|
||||
## v0.28.0
|
||||
|
||||
This release breaks the format for the `priv_validator.json` file
|
||||
and the protocol used for the external validator process.
|
||||
It is compatible with v0.27.0 blockchains (neither the BlockProtocol or the
|
||||
P2PProtocol have changed).
|
||||
|
||||
Please read carefully for details about upgrading.
|
||||
|
||||
XXX: Backup your `config/priv_validator.json`
|
||||
before proceeding.
|
||||
|
||||
### `priv_validator.json`
|
||||
|
||||
The `config/priv_validator.json` is now two files:
|
||||
`config/priv_validator_key.json` and `data/priv_validator_state.json`.
|
||||
The former contains the key material, the later contains the details on the last
|
||||
thing signed.
|
||||
|
||||
When running v0.28.0 for the first time, it will back up any pre-existing
|
||||
`priv_validator.json` file and proceed to split it into the two new files.
|
||||
Upgrading should happen automatically without problem.
|
||||
|
||||
To upgrade manually, use the provided `privValUpgrade.go` script, with exact paths for the old
|
||||
`priv_validator.json` and the locations for the two new files. It's recomended
|
||||
to use the default paths, of `config/priv_validator_key.json` and
|
||||
`data/priv_validator_state.json`, respectively:
|
||||
|
||||
```
|
||||
go run scripts/privValUpgrade.go <old-path> <new-key-path> <new-state-path>
|
||||
```
|
||||
|
||||
### External validator signers
|
||||
|
||||
The Unix and TCP implementations of the remote signing validator
|
||||
have been consolidated into a single implementation.
|
||||
Thus in both cases, the external process is expected to dial
|
||||
Tendermint. This is different from how Unix sockets used to work, where
|
||||
Tendermint dialed the external process.
|
||||
|
||||
The `PubKeyMsg` was also split into two for consistency with other message
|
||||
types.
|
||||
|
||||
Note that the TCP sockets don't yet use a persistent key,
|
||||
so while they're encrypted, they can't yet be properly authenticated.
|
||||
See [#3105](https://github.com/tendermint/tendermint/issues/3105).
|
||||
Note the Unix socket has neither encryption nor authentication, but will
|
||||
add a shared-secret in [#3099](https://github.com/tendermint/tendermint/issues/3099).
|
||||
|
||||
|
||||
## v0.27.0
|
||||
|
||||
This release contains some breaking changes to the block and p2p protocols,
|
||||
but does not change any core data structures, so it should be compatible with
|
||||
existing blockchains from the v0.26 series that only used Ed25519 validator keys.
|
||||
Blockchains using Secp256k1 for validators will not be compatible. This is due
|
||||
to the fact that we now enforce which key types validators can use as a
|
||||
consensus param. The default is Ed25519, and Secp256k1 must be activated
|
||||
explicitly.
|
||||
|
||||
It is recommended to upgrade all nodes at once to avoid incompatibilities at the
|
||||
peer layer - namely, the heartbeat consensus message has been removed (only
|
||||
relevant if `create_empty_blocks=false` or `create_empty_blocks_interval > 0`),
|
||||
and the proposer selection algorithm has changed. Since proposer information is
|
||||
never included in the blockchain, this change only affects the peer layer.
|
||||
|
||||
### Go API Changes
|
||||
|
||||
#### libs/db
|
||||
|
||||
The ReverseIterator API has changed the meaning of `start` and `end`.
|
||||
Before, iteration was from `start` to `end`, where
|
||||
`start > end`. Now, iteration is from `end` to `start`, where `start < end`.
|
||||
The iterator also excludes `end`. This change allows a simplified and more
|
||||
intuitive logic, aligning the semantic meaning of `start` and `end` in the
|
||||
`Iterator` and `ReverseIterator`.
|
||||
|
||||
### Applications
|
||||
|
||||
This release enforces a new consensus parameter, the
|
||||
ValidatorParams.PubKeyTypes. Applications must ensure that they only return
|
||||
validator updates with the allowed PubKeyTypes. If a validator update includes a
|
||||
pubkey type that is not included in the ConsensusParams.Validator.PubKeyTypes,
|
||||
block execution will fail and the consensus will halt.
|
||||
|
||||
By default, only Ed25519 pubkeys may be used for validators. Enabling
|
||||
Secp256k1 requires explicit modification of the ConsensusParams.
|
||||
Please update your application accordingly (ie. restrict validators to only be
|
||||
able to use Ed25519 keys, or explicitly add additional key types to the genesis
|
||||
file).
|
||||
|
||||
## v0.26.0
|
||||
|
||||
New 0.26.0 release contains a lot of changes to core data types and protocols. It is not
|
||||
This release contains a lot of changes to core data types and protocols. It is not
|
||||
compatible to the old versions and there is no straight forward way to update
|
||||
old data to be compatible with the new version.
|
||||
|
||||
@@ -67,7 +158,7 @@ For more information, see:
|
||||
|
||||
### Go API Changes
|
||||
|
||||
#### crypto.merkle
|
||||
#### crypto/merkle
|
||||
|
||||
The `merkle.Hasher` interface was removed. Functions which used to take `Hasher`
|
||||
now simply take `[]byte`. This means that any objects being Merklized should be
|
||||
|
@@ -58,7 +58,7 @@ var RootCmd = &cobra.Command{
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
switch cmd.Use {
|
||||
case "counter", "kvstore", "dummy": // for the examples apps, don't pre-run
|
||||
case "counter", "kvstore": // for the examples apps, don't pre-run
|
||||
return nil
|
||||
case "version": // skip running for version command
|
||||
return nil
|
||||
@@ -127,10 +127,6 @@ func addCounterFlags() {
|
||||
counterCmd.PersistentFlags().BoolVarP(&flagSerial, "serial", "", false, "enforce incrementing (serial) transactions")
|
||||
}
|
||||
|
||||
func addDummyFlags() {
|
||||
dummyCmd.PersistentFlags().StringVarP(&flagPersist, "persist", "", "", "directory to use for a database")
|
||||
}
|
||||
|
||||
func addKVStoreFlags() {
|
||||
kvstoreCmd.PersistentFlags().StringVarP(&flagPersist, "persist", "", "", "directory to use for a database")
|
||||
}
|
||||
@@ -152,10 +148,6 @@ func addCommands() {
|
||||
// examples
|
||||
addCounterFlags()
|
||||
RootCmd.AddCommand(counterCmd)
|
||||
// deprecated, left for backwards compatibility
|
||||
addDummyFlags()
|
||||
RootCmd.AddCommand(dummyCmd)
|
||||
// replaces dummy, see issue #196
|
||||
addKVStoreFlags()
|
||||
RootCmd.AddCommand(kvstoreCmd)
|
||||
}
|
||||
@@ -291,18 +283,6 @@ var counterCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
// deprecated, left for backwards compatibility
|
||||
var dummyCmd = &cobra.Command{
|
||||
Use: "dummy",
|
||||
Deprecated: "use: [abci-cli kvstore] instead",
|
||||
Short: "ABCI demo example",
|
||||
Long: "ABCI demo example",
|
||||
Args: cobra.ExactArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return cmdKVStore(cmd, args)
|
||||
},
|
||||
}
|
||||
|
||||
var kvstoreCmd = &cobra.Command{
|
||||
Use: "kvstore",
|
||||
Short: "ABCI demo example",
|
||||
|
@@ -432,11 +432,7 @@ type bcBlockResponseMessage struct {
|
||||
|
||||
// ValidateBasic performs basic validation.
|
||||
func (m *bcBlockResponseMessage) ValidateBasic() error {
|
||||
if err := m.Block.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return m.Block.ValidateBasic()
|
||||
}
|
||||
|
||||
func (m *bcBlockResponseMessage) String() string {
|
||||
|
@@ -42,7 +42,7 @@ func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.G
|
||||
}
|
||||
|
||||
func makeVote(header *types.Header, blockID types.BlockID, valset *types.ValidatorSet, privVal types.PrivValidator) *types.Vote {
|
||||
addr := privVal.GetAddress()
|
||||
addr := privVal.GetPubKey().Address()
|
||||
idx, _ := valset.GetByAddress(addr)
|
||||
vote := &types.Vote{
|
||||
ValidatorAddress: addr,
|
||||
|
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
@@ -13,9 +14,10 @@ import (
|
||||
|
||||
func main() {
|
||||
var (
|
||||
addr = flag.String("addr", ":26659", "Address of client to connect to")
|
||||
chainID = flag.String("chain-id", "mychain", "chain id")
|
||||
privValPath = flag.String("priv", "", "priv val file path")
|
||||
addr = flag.String("addr", ":26659", "Address of client to connect to")
|
||||
chainID = flag.String("chain-id", "mychain", "chain id")
|
||||
privValKeyPath = flag.String("priv-key", "", "priv val key file path")
|
||||
privValStatePath = flag.String("priv-state", "", "priv val state file path")
|
||||
|
||||
logger = log.NewTMLogger(
|
||||
log.NewSyncWriter(os.Stdout),
|
||||
@@ -27,18 +29,26 @@ func main() {
|
||||
"Starting private validator",
|
||||
"addr", *addr,
|
||||
"chainID", *chainID,
|
||||
"privPath", *privValPath,
|
||||
"privKeyPath", *privValKeyPath,
|
||||
"privStatePath", *privValStatePath,
|
||||
)
|
||||
|
||||
pv := privval.LoadFilePV(*privValPath)
|
||||
pv := privval.LoadFilePV(*privValKeyPath, *privValStatePath)
|
||||
|
||||
rs := privval.NewRemoteSigner(
|
||||
logger,
|
||||
*chainID,
|
||||
*addr,
|
||||
pv,
|
||||
ed25519.GenPrivKey(),
|
||||
)
|
||||
var dialer privval.Dialer
|
||||
protocol, address := cmn.ProtocolAndAddress(*addr)
|
||||
switch protocol {
|
||||
case "unix":
|
||||
dialer = privval.DialUnixFn(address)
|
||||
case "tcp":
|
||||
connTimeout := 3 * time.Second // TODO
|
||||
dialer = privval.DialTCPFn(address, connTimeout, ed25519.GenPrivKey())
|
||||
default:
|
||||
logger.Error("Unknown protocol", "protocol", protocol)
|
||||
return
|
||||
}
|
||||
|
||||
rs := privval.NewRemoteSigner(logger, *chainID, pv, dialer)
|
||||
err := rs.Start()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@@ -17,7 +17,7 @@ var GenValidatorCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func genValidator(cmd *cobra.Command, args []string) {
|
||||
pv := privval.GenFilePV("")
|
||||
pv := privval.GenFilePV("", "")
|
||||
jsbz, err := cdc.MarshalJSON(pv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
@@ -26,15 +25,18 @@ func initFiles(cmd *cobra.Command, args []string) error {
|
||||
|
||||
func initFilesWithConfig(config *cfg.Config) error {
|
||||
// private validator
|
||||
privValFile := config.PrivValidatorFile()
|
||||
privValKeyFile := config.PrivValidatorKeyFile()
|
||||
privValStateFile := config.PrivValidatorStateFile()
|
||||
var pv *privval.FilePV
|
||||
if cmn.FileExists(privValFile) {
|
||||
pv = privval.LoadFilePV(privValFile)
|
||||
logger.Info("Found private validator", "path", privValFile)
|
||||
if cmn.FileExists(privValKeyFile) {
|
||||
pv = privval.LoadFilePV(privValKeyFile, privValStateFile)
|
||||
logger.Info("Found private validator", "keyFile", privValKeyFile,
|
||||
"stateFile", privValStateFile)
|
||||
} else {
|
||||
pv = privval.GenFilePV(privValFile)
|
||||
pv = privval.GenFilePV(privValKeyFile, privValStateFile)
|
||||
pv.Save()
|
||||
logger.Info("Generated private validator", "path", privValFile)
|
||||
logger.Info("Generated private validator", "keyFile", privValKeyFile,
|
||||
"stateFile", privValStateFile)
|
||||
}
|
||||
|
||||
nodeKeyFile := config.NodeKeyFile()
|
||||
@@ -57,9 +59,10 @@ func initFilesWithConfig(config *cfg.Config) error {
|
||||
GenesisTime: tmtime.Now(),
|
||||
ConsensusParams: types.DefaultConsensusParams(),
|
||||
}
|
||||
key := pv.GetPubKey()
|
||||
genDoc.Validators = []types.GenesisValidator{{
|
||||
Address: pv.GetPubKey().Address(),
|
||||
PubKey: pv.GetPubKey(),
|
||||
Address: key.Address(),
|
||||
PubKey: key,
|
||||
Power: 10,
|
||||
}}
|
||||
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
"github.com/tendermint/tendermint/privval"
|
||||
)
|
||||
@@ -27,36 +28,41 @@ var ResetPrivValidatorCmd = &cobra.Command{
|
||||
// XXX: this is totally unsafe.
|
||||
// it's only suitable for testnets.
|
||||
func resetAll(cmd *cobra.Command, args []string) {
|
||||
ResetAll(config.DBDir(), config.P2P.AddrBookFile(), config.PrivValidatorFile(), logger)
|
||||
ResetAll(config.DBDir(), config.P2P.AddrBookFile(), config.PrivValidatorKeyFile(),
|
||||
config.PrivValidatorStateFile(), logger)
|
||||
}
|
||||
|
||||
// XXX: this is totally unsafe.
|
||||
// it's only suitable for testnets.
|
||||
func resetPrivValidator(cmd *cobra.Command, args []string) {
|
||||
resetFilePV(config.PrivValidatorFile(), logger)
|
||||
resetFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile(), logger)
|
||||
}
|
||||
|
||||
// ResetAll removes the privValidator and address book files plus all data.
|
||||
// ResetAll removes address book files plus all data, and resets the privValdiator data.
|
||||
// Exported so other CLI tools can use it.
|
||||
func ResetAll(dbDir, addrBookFile, privValFile string, logger log.Logger) {
|
||||
resetFilePV(privValFile, logger)
|
||||
func ResetAll(dbDir, addrBookFile, privValKeyFile, privValStateFile string, logger log.Logger) {
|
||||
removeAddrBook(addrBookFile, logger)
|
||||
if err := os.RemoveAll(dbDir); err == nil {
|
||||
logger.Info("Removed all blockchain history", "dir", dbDir)
|
||||
} else {
|
||||
logger.Error("Error removing all blockchain history", "dir", dbDir, "err", err)
|
||||
}
|
||||
// recreate the dbDir since the privVal state needs to live there
|
||||
cmn.EnsureDir(dbDir, 0700)
|
||||
resetFilePV(privValKeyFile, privValStateFile, logger)
|
||||
}
|
||||
|
||||
func resetFilePV(privValFile string, logger log.Logger) {
|
||||
if _, err := os.Stat(privValFile); err == nil {
|
||||
pv := privval.LoadFilePV(privValFile)
|
||||
func resetFilePV(privValKeyFile, privValStateFile string, logger log.Logger) {
|
||||
if _, err := os.Stat(privValKeyFile); err == nil {
|
||||
pv := privval.LoadFilePVEmptyState(privValKeyFile, privValStateFile)
|
||||
pv.Reset()
|
||||
logger.Info("Reset private validator file to genesis state", "file", privValFile)
|
||||
logger.Info("Reset private validator file to genesis state", "keyFile", privValKeyFile,
|
||||
"stateFile", privValStateFile)
|
||||
} else {
|
||||
pv := privval.GenFilePV(privValFile)
|
||||
pv := privval.GenFilePV(privValKeyFile, privValStateFile)
|
||||
pv.Save()
|
||||
logger.Info("Generated private validator file", "file", privValFile)
|
||||
logger.Info("Generated private validator file", "file", "keyFile", privValKeyFile,
|
||||
"stateFile", privValStateFile)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -24,7 +24,7 @@ func AddNodeFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().Bool("fast_sync", config.FastSync, "Fast blockchain syncing")
|
||||
|
||||
// abci flags
|
||||
cmd.Flags().String("proxy_app", config.ProxyApp, "Proxy app address, or 'nilapp' or 'kvstore' for local testing.")
|
||||
cmd.Flags().String("proxy_app", config.ProxyApp, "Proxy app address, or one of: 'kvstore', 'persistent_kvstore', 'counter', 'counter_serial' or 'noop' for local testing.")
|
||||
cmd.Flags().String("abci", config.ABCI, "Specify abci transport (socket | grpc)")
|
||||
|
||||
// rpc flags
|
||||
|
@@ -16,7 +16,7 @@ var ShowValidatorCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func showValidator(cmd *cobra.Command, args []string) {
|
||||
privValidator := privval.LoadOrGenFilePV(config.PrivValidatorFile())
|
||||
privValidator := privval.LoadOrGenFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile())
|
||||
pubKeyJSONBytes, _ := cdc.MarshalJSON(privValidator.GetPubKey())
|
||||
fmt.Println(string(pubKeyJSONBytes))
|
||||
}
|
||||
|
@@ -85,11 +85,18 @@ func testnetFiles(cmd *cobra.Command, args []string) error {
|
||||
_ = os.RemoveAll(outputDir)
|
||||
return err
|
||||
}
|
||||
err = os.MkdirAll(filepath.Join(nodeDir, "data"), nodeDirPerm)
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(outputDir)
|
||||
return err
|
||||
}
|
||||
|
||||
initFilesWithConfig(config)
|
||||
|
||||
pvFile := filepath.Join(nodeDir, config.BaseConfig.PrivValidator)
|
||||
pv := privval.LoadFilePV(pvFile)
|
||||
pvKeyFile := filepath.Join(nodeDir, config.BaseConfig.PrivValidatorKey)
|
||||
pvStateFile := filepath.Join(nodeDir, config.BaseConfig.PrivValidatorState)
|
||||
|
||||
pv := privval.LoadFilePV(pvKeyFile, pvStateFile)
|
||||
genVals[i] = types.GenesisValidator{
|
||||
Address: pv.GetPubKey().Address(),
|
||||
PubKey: pv.GetPubKey(),
|
||||
@@ -127,14 +134,32 @@ func testnetFiles(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Gather persistent peer addresses.
|
||||
var (
|
||||
persistentPeers string
|
||||
err error
|
||||
)
|
||||
if populatePersistentPeers {
|
||||
err := populatePersistentPeersInConfigAndWriteIt(config)
|
||||
persistentPeers, err = persistentPeersString(config)
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(outputDir)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Overwrite default config.
|
||||
for i := 0; i < nValidators+nNonValidators; i++ {
|
||||
nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i))
|
||||
config.SetRoot(nodeDir)
|
||||
config.P2P.AddrBookStrict = false
|
||||
config.P2P.AllowDuplicateIP = true
|
||||
if populatePersistentPeers {
|
||||
config.P2P.PersistentPeers = persistentPeers
|
||||
}
|
||||
|
||||
cfg.WriteConfigFile(filepath.Join(nodeDir, "config", "config.toml"), config)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully initialized %v node directories\n", nValidators+nNonValidators)
|
||||
return nil
|
||||
}
|
||||
@@ -157,28 +182,16 @@ func hostnameOrIP(i int) string {
|
||||
return fmt.Sprintf("%s%d", hostnamePrefix, i)
|
||||
}
|
||||
|
||||
func populatePersistentPeersInConfigAndWriteIt(config *cfg.Config) error {
|
||||
func persistentPeersString(config *cfg.Config) (string, error) {
|
||||
persistentPeers := make([]string, nValidators+nNonValidators)
|
||||
for i := 0; i < nValidators+nNonValidators; i++ {
|
||||
nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i))
|
||||
config.SetRoot(nodeDir)
|
||||
nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile())
|
||||
if err != nil {
|
||||
return err
|
||||
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, fmt.Sprintf("%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
|
||||
return strings.Join(persistentPeers, ","), nil
|
||||
}
|
||||
|
@@ -35,15 +35,24 @@ var (
|
||||
defaultConfigFileName = "config.toml"
|
||||
defaultGenesisJSONName = "genesis.json"
|
||||
|
||||
defaultPrivValName = "priv_validator.json"
|
||||
defaultPrivValKeyName = "priv_validator_key.json"
|
||||
defaultPrivValStateName = "priv_validator_state.json"
|
||||
|
||||
defaultNodeKeyName = "node_key.json"
|
||||
defaultAddrBookName = "addrbook.json"
|
||||
|
||||
defaultConfigFilePath = filepath.Join(defaultConfigDir, defaultConfigFileName)
|
||||
defaultGenesisJSONPath = filepath.Join(defaultConfigDir, defaultGenesisJSONName)
|
||||
defaultPrivValPath = filepath.Join(defaultConfigDir, defaultPrivValName)
|
||||
defaultNodeKeyPath = filepath.Join(defaultConfigDir, defaultNodeKeyName)
|
||||
defaultAddrBookPath = filepath.Join(defaultConfigDir, defaultAddrBookName)
|
||||
defaultConfigFilePath = filepath.Join(defaultConfigDir, defaultConfigFileName)
|
||||
defaultGenesisJSONPath = filepath.Join(defaultConfigDir, defaultGenesisJSONName)
|
||||
defaultPrivValKeyPath = filepath.Join(defaultConfigDir, defaultPrivValKeyName)
|
||||
defaultPrivValStatePath = filepath.Join(defaultDataDir, defaultPrivValStateName)
|
||||
|
||||
defaultNodeKeyPath = filepath.Join(defaultConfigDir, defaultNodeKeyName)
|
||||
defaultAddrBookPath = filepath.Join(defaultConfigDir, defaultAddrBookName)
|
||||
)
|
||||
|
||||
var (
|
||||
oldPrivVal = "priv_validator.json"
|
||||
oldPrivValPath = filepath.Join(defaultConfigDir, oldPrivVal)
|
||||
)
|
||||
|
||||
// Config defines the top level configuration for a Tendermint node
|
||||
@@ -160,7 +169,10 @@ type BaseConfig struct {
|
||||
Genesis string `mapstructure:"genesis_file"`
|
||||
|
||||
// Path to the JSON file containing the private key to use as a validator in the consensus protocol
|
||||
PrivValidator string `mapstructure:"priv_validator_file"`
|
||||
PrivValidatorKey string `mapstructure:"priv_validator_key_file"`
|
||||
|
||||
// Path to the JSON file containing the last sign state of a validator
|
||||
PrivValidatorState string `mapstructure:"priv_validator_state_file"`
|
||||
|
||||
// TCP or UNIX socket address for Tendermint to listen on for
|
||||
// connections from an external PrivValidator process
|
||||
@@ -183,19 +195,20 @@ type BaseConfig struct {
|
||||
// DefaultBaseConfig returns a default base configuration for a Tendermint node
|
||||
func DefaultBaseConfig() BaseConfig {
|
||||
return BaseConfig{
|
||||
Genesis: defaultGenesisJSONPath,
|
||||
PrivValidator: defaultPrivValPath,
|
||||
NodeKey: defaultNodeKeyPath,
|
||||
Moniker: defaultMoniker,
|
||||
ProxyApp: "tcp://127.0.0.1:26658",
|
||||
ABCI: "socket",
|
||||
LogLevel: DefaultPackageLogLevels(),
|
||||
LogFormat: LogFormatPlain,
|
||||
ProfListenAddress: "",
|
||||
FastSync: true,
|
||||
FilterPeers: false,
|
||||
DBBackend: "leveldb",
|
||||
DBPath: "data",
|
||||
Genesis: defaultGenesisJSONPath,
|
||||
PrivValidatorKey: defaultPrivValKeyPath,
|
||||
PrivValidatorState: defaultPrivValStatePath,
|
||||
NodeKey: defaultNodeKeyPath,
|
||||
Moniker: defaultMoniker,
|
||||
ProxyApp: "tcp://127.0.0.1:26658",
|
||||
ABCI: "socket",
|
||||
LogLevel: DefaultPackageLogLevels(),
|
||||
LogFormat: LogFormatPlain,
|
||||
ProfListenAddress: "",
|
||||
FastSync: true,
|
||||
FilterPeers: false,
|
||||
DBBackend: "leveldb",
|
||||
DBPath: "data",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,9 +231,20 @@ func (cfg BaseConfig) GenesisFile() string {
|
||||
return rootify(cfg.Genesis, cfg.RootDir)
|
||||
}
|
||||
|
||||
// PrivValidatorFile returns the full path to the priv_validator.json file
|
||||
func (cfg BaseConfig) PrivValidatorFile() string {
|
||||
return rootify(cfg.PrivValidator, cfg.RootDir)
|
||||
// PrivValidatorKeyFile returns the full path to the priv_validator_key.json file
|
||||
func (cfg BaseConfig) PrivValidatorKeyFile() string {
|
||||
return rootify(cfg.PrivValidatorKey, cfg.RootDir)
|
||||
}
|
||||
|
||||
// PrivValidatorFile returns the full path to the priv_validator_state.json file
|
||||
func (cfg BaseConfig) PrivValidatorStateFile() string {
|
||||
return rootify(cfg.PrivValidatorState, cfg.RootDir)
|
||||
}
|
||||
|
||||
// OldPrivValidatorFile returns the full path of the priv_validator.json from pre v0.28.0.
|
||||
// TODO: eventually remove.
|
||||
func (cfg BaseConfig) OldPrivValidatorFile() string {
|
||||
return rootify(oldPrivValPath, cfg.RootDir)
|
||||
}
|
||||
|
||||
// NodeKeyFile returns the full path to the node_key.json file
|
||||
@@ -283,7 +307,7 @@ type RPCConfig struct {
|
||||
|
||||
// Maximum number of simultaneous connections.
|
||||
// Does not include RPC (HTTP&WebSocket) connections. See max_open_connections
|
||||
// If you want to accept more significant number than the default, make sure
|
||||
// If you want to accept a larger number than the default, make sure
|
||||
// you increase your OS limits.
|
||||
// 0 - unlimited.
|
||||
GRPCMaxOpenConnections int `mapstructure:"grpc_max_open_connections"`
|
||||
@@ -293,7 +317,7 @@ type RPCConfig struct {
|
||||
|
||||
// Maximum number of simultaneous connections (including WebSocket).
|
||||
// Does not include gRPC connections. See grpc_max_open_connections
|
||||
// If you want to accept more significant number than the default, make sure
|
||||
// If you want to accept a larger number than the default, make sure
|
||||
// you increase your OS limits.
|
||||
// 0 - unlimited.
|
||||
// Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files}
|
||||
@@ -434,7 +458,7 @@ func DefaultP2PConfig() *P2PConfig {
|
||||
RecvRate: 5120000, // 5 mB/s
|
||||
PexReactor: true,
|
||||
SeedMode: false,
|
||||
AllowDuplicateIP: true, // so non-breaking yet
|
||||
AllowDuplicateIP: false,
|
||||
HandshakeTimeout: 20 * time.Second,
|
||||
DialTimeout: 3 * time.Second,
|
||||
TestDialFail: false,
|
||||
@@ -774,12 +798,12 @@ type InstrumentationConfig struct {
|
||||
PrometheusListenAddr string `mapstructure:"prometheus_listen_addr"`
|
||||
|
||||
// Maximum number of simultaneous connections.
|
||||
// If you want to accept more significant number than the default, make sure
|
||||
// If you want to accept a larger number than the default, make sure
|
||||
// you increase your OS limits.
|
||||
// 0 - unlimited.
|
||||
MaxOpenConnections int `mapstructure:"max_open_connections"`
|
||||
|
||||
// Tendermint instrumentation namespace.
|
||||
// Instrumentation namespace.
|
||||
Namespace string `mapstructure:"namespace"`
|
||||
}
|
||||
|
||||
|
@@ -95,7 +95,10 @@ log_format = "{{ .BaseConfig.LogFormat }}"
|
||||
genesis_file = "{{ js .BaseConfig.Genesis }}"
|
||||
|
||||
# Path to the JSON file containing the private key to use as a validator in the consensus protocol
|
||||
priv_validator_file = "{{ js .BaseConfig.PrivValidator }}"
|
||||
priv_validator_key_file = "{{ js .BaseConfig.PrivValidatorKey }}"
|
||||
|
||||
# Path to the JSON file containing the last sign state of a validator
|
||||
priv_validator_state_file = "{{ js .BaseConfig.PrivValidatorState }}"
|
||||
|
||||
# TCP or UNIX socket address for Tendermint to listen on for
|
||||
# connections from an external PrivValidator process
|
||||
@@ -125,13 +128,13 @@ laddr = "{{ .RPC.ListenAddress }}"
|
||||
# A list of origins a cross-domain request can be executed from
|
||||
# Default value '[]' disables cors support
|
||||
# Use '["*"]' to allow any origin
|
||||
cors_allowed_origins = "{{ .RPC.CORSAllowedOrigins }}"
|
||||
cors_allowed_origins = [{{ range .RPC.CORSAllowedOrigins }}{{ printf "%q, " . }}{{end}}]
|
||||
|
||||
# A list of methods the client is allowed to use with cross-domain requests
|
||||
cors_allowed_methods = "{{ .RPC.CORSAllowedMethods }}"
|
||||
cors_allowed_methods = [{{ range .RPC.CORSAllowedMethods }}{{ printf "%q, " . }}{{end}}]
|
||||
|
||||
# A list of non simple headers the client is allowed to use with cross-domain requests
|
||||
cors_allowed_headers = "{{ .RPC.CORSAllowedHeaders }}"
|
||||
cors_allowed_headers = [{{ range .RPC.CORSAllowedHeaders }}{{ printf "%q, " . }}{{end}}]
|
||||
|
||||
# TCP or UNIX socket address for the gRPC server to listen on
|
||||
# NOTE: This server only supports /broadcast_tx_commit
|
||||
@@ -139,7 +142,7 @@ grpc_laddr = "{{ .RPC.GRPCListenAddress }}"
|
||||
|
||||
# Maximum number of simultaneous connections.
|
||||
# Does not include RPC (HTTP&WebSocket) connections. See max_open_connections
|
||||
# If you want to accept more significant number than the default, make sure
|
||||
# If you want to accept a larger number than the default, make sure
|
||||
# you increase your OS limits.
|
||||
# 0 - unlimited.
|
||||
# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files}
|
||||
@@ -151,7 +154,7 @@ unsafe = {{ .RPC.Unsafe }}
|
||||
|
||||
# Maximum number of simultaneous connections (including WebSocket).
|
||||
# Does not include gRPC connections. See grpc_max_open_connections
|
||||
# If you want to accept more significant number than the default, make sure
|
||||
# If you want to accept a larger number than the default, make sure
|
||||
# you increase your OS limits.
|
||||
# 0 - unlimited.
|
||||
# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files}
|
||||
@@ -269,8 +272,8 @@ blocktime_iota = "{{ .Consensus.BlockTimeIota }}"
|
||||
# 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).
|
||||
# 1) "null"
|
||||
# 2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend).
|
||||
indexer = "{{ .TxIndex.Indexer }}"
|
||||
|
||||
# Comma-separated list of tags to index (by default the only tag is "tx.hash")
|
||||
@@ -302,7 +305,7 @@ prometheus = {{ .Instrumentation.Prometheus }}
|
||||
prometheus_listen_addr = "{{ .Instrumentation.PrometheusListenAddr }}"
|
||||
|
||||
# Maximum number of simultaneous connections.
|
||||
# If you want to accept more significant number than the default, make sure
|
||||
# If you want to accept a larger number than the default, make sure
|
||||
# you increase your OS limits.
|
||||
# 0 - unlimited.
|
||||
max_open_connections = {{ .Instrumentation.MaxOpenConnections }}
|
||||
@@ -342,7 +345,8 @@ func ResetTestRoot(testName string) *Config {
|
||||
baseConfig := DefaultBaseConfig()
|
||||
configFilePath := filepath.Join(rootDir, defaultConfigFilePath)
|
||||
genesisFilePath := filepath.Join(rootDir, baseConfig.Genesis)
|
||||
privFilePath := filepath.Join(rootDir, baseConfig.PrivValidator)
|
||||
privKeyFilePath := filepath.Join(rootDir, baseConfig.PrivValidatorKey)
|
||||
privStateFilePath := filepath.Join(rootDir, baseConfig.PrivValidatorState)
|
||||
|
||||
// Write default config file if missing.
|
||||
if !cmn.FileExists(configFilePath) {
|
||||
@@ -352,7 +356,8 @@ func ResetTestRoot(testName string) *Config {
|
||||
cmn.MustWriteFile(genesisFilePath, []byte(testGenesis), 0644)
|
||||
}
|
||||
// we always overwrite the priv val
|
||||
cmn.MustWriteFile(privFilePath, []byte(testPrivValidator), 0644)
|
||||
cmn.MustWriteFile(privKeyFilePath, []byte(testPrivValidatorKey), 0644)
|
||||
cmn.MustWriteFile(privStateFilePath, []byte(testPrivValidatorState), 0644)
|
||||
|
||||
config := TestConfig().SetRoot(rootDir)
|
||||
return config
|
||||
@@ -374,7 +379,7 @@ var testGenesis = `{
|
||||
"app_hash": ""
|
||||
}`
|
||||
|
||||
var testPrivValidator = `{
|
||||
var testPrivValidatorKey = `{
|
||||
"address": "A3258DCBF45DCA0DF052981870F2D1441A36D145",
|
||||
"pub_key": {
|
||||
"type": "tendermint/PubKeyEd25519",
|
||||
@@ -383,8 +388,11 @@ var testPrivValidator = `{
|
||||
"priv_key": {
|
||||
"type": "tendermint/PrivKeyEd25519",
|
||||
"value": "EVkqJO/jIXp3rkASXfh9YnyToYXRXhBr6g9cQVxPFnQBP/5povV4HTjvsy530kybxKHwEi85iU8YL0qQhSYVoQ=="
|
||||
},
|
||||
"last_height": "0",
|
||||
"last_round": "0",
|
||||
"last_step": 0
|
||||
}
|
||||
}`
|
||||
|
||||
var testPrivValidatorState = `{
|
||||
"height": "0",
|
||||
"round": "0",
|
||||
"step": 0
|
||||
}`
|
||||
|
@@ -60,7 +60,7 @@ func TestEnsureTestRoot(t *testing.T) {
|
||||
|
||||
// TODO: make sure the cfg returned and testconfig are the same!
|
||||
baseConfig := DefaultBaseConfig()
|
||||
ensureFiles(t, rootDir, defaultDataDir, baseConfig.Genesis, baseConfig.PrivValidator)
|
||||
ensureFiles(t, rootDir, defaultDataDir, baseConfig.Genesis, baseConfig.PrivValidatorKey, baseConfig.PrivValidatorState)
|
||||
}
|
||||
|
||||
func checkConfig(configFile string) bool {
|
||||
|
@@ -6,14 +6,18 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/log/term"
|
||||
|
||||
abcicli "github.com/tendermint/tendermint/abci/client"
|
||||
"github.com/tendermint/tendermint/abci/example/counter"
|
||||
"github.com/tendermint/tendermint/abci/example/kvstore"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
bc "github.com/tendermint/tendermint/blockchain"
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
@@ -27,11 +31,6 @@ import (
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
|
||||
"github.com/tendermint/tendermint/abci/example/counter"
|
||||
"github.com/tendermint/tendermint/abci/example/kvstore"
|
||||
|
||||
"github.com/go-kit/kit/log/term"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -72,9 +71,10 @@ func NewValidatorStub(privValidator types.PrivValidator, valIndex int) *validato
|
||||
}
|
||||
|
||||
func (vs *validatorStub) signVote(voteType types.SignedMsgType, hash []byte, header types.PartSetHeader) (*types.Vote, error) {
|
||||
addr := vs.PrivValidator.GetPubKey().Address()
|
||||
vote := &types.Vote{
|
||||
ValidatorIndex: vs.Index,
|
||||
ValidatorAddress: vs.PrivValidator.GetAddress(),
|
||||
ValidatorAddress: addr,
|
||||
Height: vs.Height,
|
||||
Round: vs.Round,
|
||||
Timestamp: tmtime.Now(),
|
||||
@@ -151,8 +151,9 @@ func signAddVotes(to *ConsensusState, voteType types.SignedMsgType, hash []byte,
|
||||
|
||||
func validatePrevote(t *testing.T, cs *ConsensusState, round int, privVal *validatorStub, blockHash []byte) {
|
||||
prevotes := cs.Votes.Prevotes(round)
|
||||
address := privVal.GetPubKey().Address()
|
||||
var vote *types.Vote
|
||||
if vote = prevotes.GetByAddress(privVal.GetAddress()); vote == nil {
|
||||
if vote = prevotes.GetByAddress(address); vote == nil {
|
||||
panic("Failed to find prevote from validator")
|
||||
}
|
||||
if blockHash == nil {
|
||||
@@ -168,8 +169,9 @@ func validatePrevote(t *testing.T, cs *ConsensusState, round int, privVal *valid
|
||||
|
||||
func validateLastPrecommit(t *testing.T, cs *ConsensusState, privVal *validatorStub, blockHash []byte) {
|
||||
votes := cs.LastCommit
|
||||
address := privVal.GetPubKey().Address()
|
||||
var vote *types.Vote
|
||||
if vote = votes.GetByAddress(privVal.GetAddress()); vote == nil {
|
||||
if vote = votes.GetByAddress(address); vote == nil {
|
||||
panic("Failed to find precommit from validator")
|
||||
}
|
||||
if !bytes.Equal(vote.BlockID.Hash, blockHash) {
|
||||
@@ -179,8 +181,9 @@ func validateLastPrecommit(t *testing.T, cs *ConsensusState, privVal *validatorS
|
||||
|
||||
func validatePrecommit(t *testing.T, cs *ConsensusState, thisRound, lockRound int, privVal *validatorStub, votedBlockHash, lockedBlockHash []byte) {
|
||||
precommits := cs.Votes.Precommits(thisRound)
|
||||
address := privVal.GetPubKey().Address()
|
||||
var vote *types.Vote
|
||||
if vote = precommits.GetByAddress(privVal.GetAddress()); vote == nil {
|
||||
if vote = precommits.GetByAddress(address); vote == nil {
|
||||
panic("Failed to find precommit from validator")
|
||||
}
|
||||
|
||||
@@ -281,9 +284,10 @@ func newConsensusStateWithConfigAndBlockStore(thisConfig *cfg.Config, state sm.S
|
||||
}
|
||||
|
||||
func loadPrivValidator(config *cfg.Config) *privval.FilePV {
|
||||
privValidatorFile := config.PrivValidatorFile()
|
||||
ensureDir(path.Dir(privValidatorFile), 0700)
|
||||
privValidator := privval.LoadOrGenFilePV(privValidatorFile)
|
||||
privValidatorKeyFile := config.PrivValidatorKeyFile()
|
||||
ensureDir(filepath.Dir(privValidatorKeyFile), 0700)
|
||||
privValidatorStateFile := config.PrivValidatorStateFile()
|
||||
privValidator := privval.LoadOrGenFilePV(privValidatorKeyFile, privValidatorStateFile)
|
||||
privValidator.Reset()
|
||||
return privValidator
|
||||
}
|
||||
@@ -591,7 +595,7 @@ func randConsensusNet(nValidators int, testName string, tickerFunc func() Timeou
|
||||
for _, opt := range configOpts {
|
||||
opt(thisConfig)
|
||||
}
|
||||
ensureDir(path.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal
|
||||
ensureDir(filepath.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal
|
||||
app := appFunc()
|
||||
vals := types.TM2PB.ValidatorUpdates(state.Validators)
|
||||
app.InitChain(abci.RequestInitChain{Validators: vals})
|
||||
@@ -612,16 +616,21 @@ func randConsensusNetWithPeers(nValidators, nPeers int, testName string, tickerF
|
||||
stateDB := dbm.NewMemDB() // each state needs its own db
|
||||
state, _ := sm.LoadStateFromDBOrGenesisDoc(stateDB, genDoc)
|
||||
thisConfig := ResetConfig(fmt.Sprintf("%s_%d", testName, i))
|
||||
ensureDir(path.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal
|
||||
ensureDir(filepath.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal
|
||||
var privVal types.PrivValidator
|
||||
if i < nValidators {
|
||||
privVal = privVals[i]
|
||||
} else {
|
||||
tempFile, err := ioutil.TempFile("", "priv_validator_")
|
||||
tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
privVal = privval.GenFilePV(tempFile.Name())
|
||||
tempStateFile, err := ioutil.TempFile("", "priv_validator_state_")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
privVal = privval.GenFilePV(tempKeyFile.Name(), tempStateFile.Name())
|
||||
}
|
||||
|
||||
app := appFunc()
|
||||
|
@@ -143,7 +143,8 @@ func TestReactorWithEvidence(t *testing.T) {
|
||||
// mock the evidence pool
|
||||
// everyone includes evidence of another double signing
|
||||
vIdx := (i + 1) % nValidators
|
||||
evpool := newMockEvidencePool(privVals[vIdx].GetAddress())
|
||||
addr := privVals[vIdx].GetPubKey().Address()
|
||||
evpool := newMockEvidencePool(addr)
|
||||
|
||||
// Make ConsensusState
|
||||
blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyAppConnCon, mempool, evpool)
|
||||
@@ -268,7 +269,8 @@ func TestReactorVotingPowerChange(t *testing.T) {
|
||||
// map of active validators
|
||||
activeVals := make(map[string]struct{})
|
||||
for i := 0; i < nVals; i++ {
|
||||
activeVals[string(css[i].privValidator.GetAddress())] = struct{}{}
|
||||
addr := css[i].privValidator.GetPubKey().Address()
|
||||
activeVals[string(addr)] = struct{}{}
|
||||
}
|
||||
|
||||
// wait till everyone makes block 1
|
||||
@@ -331,7 +333,8 @@ func TestReactorValidatorSetChanges(t *testing.T) {
|
||||
// map of active validators
|
||||
activeVals := make(map[string]struct{})
|
||||
for i := 0; i < nVals; i++ {
|
||||
activeVals[string(css[i].privValidator.GetAddress())] = struct{}{}
|
||||
addr := css[i].privValidator.GetPubKey().Address()
|
||||
activeVals[string(addr)] = struct{}{}
|
||||
}
|
||||
|
||||
// wait till everyone makes block 1
|
||||
|
@@ -11,7 +11,6 @@ import (
|
||||
"time"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/version"
|
||||
//auto "github.com/tendermint/tendermint/libs/autofile"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
@@ -20,6 +19,7 @@ import (
|
||||
"github.com/tendermint/tendermint/proxy"
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/tendermint/version"
|
||||
)
|
||||
|
||||
var crc32c = crc32.MakeTable(crc32.Castagnoli)
|
||||
@@ -247,6 +247,7 @@ func (h *Handshaker) Handshake(proxyApp proxy.AppConns) error {
|
||||
|
||||
// Set AppVersion on the state.
|
||||
h.initialState.Version.Consensus.App = version.Protocol(res.AppVersion)
|
||||
sm.SaveState(h.stateDB, h.initialState)
|
||||
|
||||
// Replay blocks up to the latest in the blockstore.
|
||||
_, err = h.ReplayBlocks(h.initialState, appHash, blockHeight, proxyApp)
|
||||
@@ -295,19 +296,27 @@ func (h *Handshaker) ReplayBlocks(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If the app returned validators or consensus params, update the state.
|
||||
if len(res.Validators) > 0 {
|
||||
vals, err := types.PB2TM.ValidatorUpdates(res.Validators)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if stateBlockHeight == 0 { //we only update state when we are in initial state
|
||||
// If the app returned validators or consensus params, update the state.
|
||||
if len(res.Validators) > 0 {
|
||||
vals, err := types.PB2TM.ValidatorUpdates(res.Validators)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state.Validators = types.NewValidatorSet(vals)
|
||||
state.NextValidators = types.NewValidatorSet(vals)
|
||||
} else {
|
||||
// If validator set is not set in genesis and still empty after InitChain, exit.
|
||||
if len(h.genDoc.Validators) == 0 {
|
||||
return nil, fmt.Errorf("Validator set is nil in genesis and still empty after InitChain")
|
||||
}
|
||||
}
|
||||
state.Validators = types.NewValidatorSet(vals)
|
||||
state.NextValidators = types.NewValidatorSet(vals)
|
||||
|
||||
if res.ConsensusParams != nil {
|
||||
state.ConsensusParams = types.PB2TM.ConsensusParams(res.ConsensusParams)
|
||||
}
|
||||
sm.SaveState(h.stateDB, state)
|
||||
}
|
||||
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.
|
||||
|
@@ -319,7 +319,7 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) {
|
||||
walFile := tempWALWithData(walBody)
|
||||
config.Consensus.SetWalFile(walFile)
|
||||
|
||||
privVal := privval.LoadFilePV(config.PrivValidatorFile())
|
||||
privVal := privval.LoadFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile())
|
||||
|
||||
wal, err := NewWAL(walFile)
|
||||
require.NoError(t, err)
|
||||
@@ -633,7 +633,7 @@ func TestInitChainUpdateValidators(t *testing.T) {
|
||||
clientCreator := proxy.NewLocalClientCreator(app)
|
||||
|
||||
config := ResetConfig("proxy_test_")
|
||||
privVal := privval.LoadFilePV(config.PrivValidatorFile())
|
||||
privVal := privval.LoadFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile())
|
||||
stateDB, state, store := stateAndStore(config, privVal.GetPubKey(), 0x0)
|
||||
|
||||
oldValAddr := state.Validators.Validators[0].Address
|
||||
@@ -659,12 +659,6 @@ func TestInitChainUpdateValidators(t *testing.T) {
|
||||
assert.Equal(t, newValAddr, expectValAddr)
|
||||
}
|
||||
|
||||
func newInitChainApp(vals []abci.ValidatorUpdate) *initChainApp {
|
||||
return &initChainApp{
|
||||
vals: vals,
|
||||
}
|
||||
}
|
||||
|
||||
// returns the vals on InitChain
|
||||
type initChainApp struct {
|
||||
abci.BaseApplication
|
||||
|
@@ -2,13 +2,14 @@ package consensus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
"github.com/tendermint/tendermint/libs/fail"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
@@ -829,13 +830,14 @@ func (cs *ConsensusState) enterPropose(height int64, round int) {
|
||||
}
|
||||
|
||||
// if not a validator, we're done
|
||||
if !cs.Validators.HasAddress(cs.privValidator.GetAddress()) {
|
||||
logger.Debug("This node is not a validator", "addr", cs.privValidator.GetAddress(), "vals", cs.Validators)
|
||||
address := cs.privValidator.GetPubKey().Address()
|
||||
if !cs.Validators.HasAddress(address) {
|
||||
logger.Debug("This node is not a validator", "addr", address, "vals", cs.Validators)
|
||||
return
|
||||
}
|
||||
logger.Debug("This node is a validator")
|
||||
|
||||
if cs.isProposer() {
|
||||
if cs.isProposer(address) {
|
||||
logger.Info("enterPropose: Our turn to propose", "proposer", cs.Validators.GetProposer().Address, "privValidator", cs.privValidator)
|
||||
cs.decideProposal(height, round)
|
||||
} else {
|
||||
@@ -843,8 +845,8 @@ func (cs *ConsensusState) enterPropose(height int64, round int) {
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *ConsensusState) isProposer() bool {
|
||||
return bytes.Equal(cs.Validators.GetProposer().Address, cs.privValidator.GetAddress())
|
||||
func (cs *ConsensusState) isProposer(address []byte) bool {
|
||||
return bytes.Equal(cs.Validators.GetProposer().Address, address)
|
||||
}
|
||||
|
||||
func (cs *ConsensusState) defaultDecideProposal(height int64, round int) {
|
||||
@@ -929,7 +931,7 @@ func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts
|
||||
cs.state.Validators.Size(),
|
||||
len(evidence),
|
||||
), maxGas)
|
||||
proposerAddr := cs.privValidator.GetAddress()
|
||||
proposerAddr := cs.privValidator.GetPubKey().Address()
|
||||
block, parts := cs.state.MakeBlock(cs.Height, txs, commit, evidence, proposerAddr)
|
||||
|
||||
return block, parts
|
||||
@@ -1474,7 +1476,8 @@ func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerID p2p.ID) (bool, err
|
||||
if err == ErrVoteHeightMismatch {
|
||||
return added, err
|
||||
} else if voteErr, ok := err.(*types.ErrVoteConflictingVotes); ok {
|
||||
if bytes.Equal(vote.ValidatorAddress, cs.privValidator.GetAddress()) {
|
||||
addr := cs.privValidator.GetPubKey().Address()
|
||||
if bytes.Equal(vote.ValidatorAddress, addr) {
|
||||
cs.Logger.Error("Found conflicting vote from ourselves. Did you unsafe_reset a validator?", "height", vote.Height, "round", vote.Round, "type", vote.Type)
|
||||
return added, err
|
||||
}
|
||||
@@ -1526,7 +1529,7 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool,
|
||||
// Not necessarily a bad peer, but not favourable behaviour.
|
||||
if vote.Height != cs.Height {
|
||||
err = ErrVoteHeightMismatch
|
||||
cs.Logger.Info("Vote ignored and not added", "voteHeight", vote.Height, "csHeight", cs.Height, "err", err)
|
||||
cs.Logger.Info("Vote ignored and not added", "voteHeight", vote.Height, "csHeight", cs.Height, "peerID", peerID)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1639,7 +1642,7 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool,
|
||||
}
|
||||
|
||||
func (cs *ConsensusState) signVote(type_ types.SignedMsgType, hash []byte, header types.PartSetHeader) (*types.Vote, error) {
|
||||
addr := cs.privValidator.GetAddress()
|
||||
addr := cs.privValidator.GetPubKey().Address()
|
||||
valIndex, _ := cs.Validators.GetByAddress(addr)
|
||||
|
||||
vote := &types.Vote{
|
||||
@@ -1675,7 +1678,7 @@ func (cs *ConsensusState) voteTime() time.Time {
|
||||
// sign the vote and publish on internalMsgQueue
|
||||
func (cs *ConsensusState) signAddVote(type_ types.SignedMsgType, hash []byte, header types.PartSetHeader) *types.Vote {
|
||||
// if we don't have a key or we're not in the validator set, do nothing
|
||||
if cs.privValidator == nil || !cs.Validators.HasAddress(cs.privValidator.GetAddress()) {
|
||||
if cs.privValidator == nil || !cs.Validators.HasAddress(cs.privValidator.GetPubKey().Address()) {
|
||||
return nil
|
||||
}
|
||||
vote, err := cs.signVote(type_, hash, header)
|
||||
|
@@ -73,7 +73,8 @@ func TestStateProposerSelection0(t *testing.T) {
|
||||
|
||||
// Commit a block and ensure proposer for the next height is correct.
|
||||
prop := cs1.GetRoundState().Validators.GetProposer()
|
||||
if !bytes.Equal(prop.Address, cs1.privValidator.GetAddress()) {
|
||||
address := cs1.privValidator.GetPubKey().Address()
|
||||
if !bytes.Equal(prop.Address, address) {
|
||||
t.Fatalf("expected proposer to be validator %d. Got %X", 0, prop.Address)
|
||||
}
|
||||
|
||||
@@ -87,7 +88,8 @@ func TestStateProposerSelection0(t *testing.T) {
|
||||
ensureNewRound(newRoundCh, height+1, 0)
|
||||
|
||||
prop = cs1.GetRoundState().Validators.GetProposer()
|
||||
if !bytes.Equal(prop.Address, vss[1].GetAddress()) {
|
||||
addr := vss[1].GetPubKey().Address()
|
||||
if !bytes.Equal(prop.Address, addr) {
|
||||
panic(fmt.Sprintf("expected proposer to be validator %d. Got %X", 1, prop.Address))
|
||||
}
|
||||
}
|
||||
@@ -110,7 +112,8 @@ func TestStateProposerSelection2(t *testing.T) {
|
||||
// everyone just votes nil. we get a new proposer each round
|
||||
for i := 0; i < len(vss); i++ {
|
||||
prop := cs1.GetRoundState().Validators.GetProposer()
|
||||
correctProposer := vss[(i+round)%len(vss)].GetAddress()
|
||||
addr := vss[(i+round)%len(vss)].GetPubKey().Address()
|
||||
correctProposer := addr
|
||||
if !bytes.Equal(prop.Address, correctProposer) {
|
||||
panic(fmt.Sprintf("expected RoundState.Validators.GetProposer() to be validator %d. Got %X", (i+2)%len(vss), prop.Address))
|
||||
}
|
||||
@@ -505,7 +508,8 @@ func TestStateLockPOLRelock(t *testing.T) {
|
||||
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||
proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal)
|
||||
voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress())
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
newBlockCh := subscribe(cs1.eventBus, types.EventQueryNewBlockHeader)
|
||||
|
||||
@@ -596,7 +600,8 @@ func TestStateLockPOLUnlock(t *testing.T) {
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
unlockCh := subscribe(cs1.eventBus, types.EventQueryUnlock)
|
||||
voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress())
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
|
||||
// everything done from perspective of cs1
|
||||
|
||||
@@ -689,7 +694,8 @@ func TestStateLockPOLSafety1(t *testing.T) {
|
||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress())
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
|
||||
// start round and wait for propose and prevote
|
||||
startTestRound(cs1, cs1.Height, round)
|
||||
@@ -805,7 +811,8 @@ func TestStateLockPOLSafety2(t *testing.T) {
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
unlockCh := subscribe(cs1.eventBus, types.EventQueryUnlock)
|
||||
voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress())
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
|
||||
// the block for R0: gets polkad but we miss it
|
||||
// (even though we signed it, shhh)
|
||||
@@ -896,7 +903,8 @@ func TestProposeValidBlock(t *testing.T) {
|
||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
unlockCh := subscribe(cs1.eventBus, types.EventQueryUnlock)
|
||||
voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress())
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
|
||||
// start round and wait for propose and prevote
|
||||
startTestRound(cs1, cs1.Height, round)
|
||||
@@ -982,7 +990,8 @@ func TestSetValidBlockOnDelayedPrevote(t *testing.T) {
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
validBlockCh := subscribe(cs1.eventBus, types.EventQueryValidBlock)
|
||||
voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress())
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
|
||||
// start round and wait for propose and prevote
|
||||
startTestRound(cs1, cs1.Height, round)
|
||||
@@ -1041,7 +1050,8 @@ func TestSetValidBlockOnDelayedProposal(t *testing.T) {
|
||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
validBlockCh := subscribe(cs1.eventBus, types.EventQueryValidBlock)
|
||||
voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress())
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal)
|
||||
|
||||
round = round + 1 // move to round in which P0 is not proposer
|
||||
@@ -1111,7 +1121,8 @@ func TestWaitingTimeoutProposeOnNewRound(t *testing.T) {
|
||||
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress())
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
|
||||
// start round
|
||||
startTestRound(cs1, height, round)
|
||||
@@ -1144,7 +1155,8 @@ func TestRoundSkipOnNilPolkaFromHigherRound(t *testing.T) {
|
||||
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress())
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
|
||||
// start round
|
||||
startTestRound(cs1, height, round)
|
||||
@@ -1177,7 +1189,8 @@ func TestWaitTimeoutProposeOnNilPolkaForTheCurrentRound(t *testing.T) {
|
||||
|
||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress())
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
|
||||
// start round in which PO is not proposer
|
||||
startTestRound(cs1, height, round)
|
||||
@@ -1361,7 +1374,8 @@ func TestStateHalt1(t *testing.T) {
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||
newBlockCh := subscribe(cs1.eventBus, types.EventQueryNewBlock)
|
||||
voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress())
|
||||
addr := cs1.privValidator.GetPubKey().Address()
|
||||
voteCh := subscribeToVoter(cs1, addr)
|
||||
|
||||
// start round and wait for propose and prevote
|
||||
startTestRound(cs1, height, round)
|
||||
|
@@ -50,8 +50,9 @@ func TestPeerCatchupRounds(t *testing.T) {
|
||||
|
||||
func makeVoteHR(t *testing.T, height int64, round int, privVals []types.PrivValidator, valIndex int) *types.Vote {
|
||||
privVal := privVals[valIndex]
|
||||
addr := privVal.GetPubKey().Address()
|
||||
vote := &types.Vote{
|
||||
ValidatorAddress: privVal.GetAddress(),
|
||||
ValidatorAddress: addr,
|
||||
ValidatorIndex: valIndex,
|
||||
Height: height,
|
||||
Round: round,
|
||||
|
@@ -40,8 +40,9 @@ func WALGenerateNBlocks(wr io.Writer, numBlocks int) (err error) {
|
||||
// COPY PASTE FROM node.go WITH A FEW MODIFICATIONS
|
||||
// NOTE: we can't import node package because of circular dependency.
|
||||
// NOTE: we don't do handshake so need to set state.Version.Consensus.App directly.
|
||||
privValidatorFile := config.PrivValidatorFile()
|
||||
privValidator := privval.LoadOrGenFilePV(privValidatorFile)
|
||||
privValidatorKeyFile := config.PrivValidatorKeyFile()
|
||||
privValidatorStateFile := config.PrivValidatorStateFile()
|
||||
privValidator := privval.LoadOrGenFilePV(privValidatorKeyFile, privValidatorStateFile)
|
||||
genDoc, err := types.GenesisDocFromFile(config.GenesisFile())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read genesis file")
|
||||
|
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"golang.org/x/crypto/openpgp/armor" // forked to github.com/tendermint/crypto
|
||||
"golang.org/x/crypto/openpgp/armor"
|
||||
)
|
||||
|
||||
func EncodeArmor(blockType string, headers map[string]string, data []byte) string {
|
||||
|
@@ -7,7 +7,7 @@ import (
|
||||
"io"
|
||||
|
||||
amino "github.com/tendermint/go-amino"
|
||||
"golang.org/x/crypto/ed25519" // forked to github.com/tendermint/crypto
|
||||
"golang.org/x/crypto/ed25519"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||
@@ -18,8 +18,8 @@ import (
|
||||
var _ crypto.PrivKey = PrivKeyEd25519{}
|
||||
|
||||
const (
|
||||
PrivKeyAminoRoute = "tendermint/PrivKeyEd25519"
|
||||
PubKeyAminoRoute = "tendermint/PubKeyEd25519"
|
||||
PrivKeyAminoName = "tendermint/PrivKeyEd25519"
|
||||
PubKeyAminoName = "tendermint/PubKeyEd25519"
|
||||
// Size of an Edwards25519 signature. Namely the size of a compressed
|
||||
// Edwards25519 point, and a field element. Both of which are 32 bytes.
|
||||
SignatureSize = 64
|
||||
@@ -30,11 +30,11 @@ var cdc = amino.NewCodec()
|
||||
func init() {
|
||||
cdc.RegisterInterface((*crypto.PubKey)(nil), nil)
|
||||
cdc.RegisterConcrete(PubKeyEd25519{},
|
||||
PubKeyAminoRoute, nil)
|
||||
PubKeyAminoName, nil)
|
||||
|
||||
cdc.RegisterInterface((*crypto.PrivKey)(nil), nil)
|
||||
cdc.RegisterConcrete(PrivKeyEd25519{},
|
||||
PrivKeyAminoRoute, nil)
|
||||
PrivKeyAminoName, nil)
|
||||
}
|
||||
|
||||
// PrivKeyEd25519 implements crypto.PrivKey.
|
||||
|
@@ -12,11 +12,11 @@ import (
|
||||
|
||||
var cdc = amino.NewCodec()
|
||||
|
||||
// routeTable is used to map public key concrete types back
|
||||
// to their amino routes. This should eventually be handled
|
||||
// nameTable is used to map public key concrete types back
|
||||
// to their registered amino names. This should eventually be handled
|
||||
// by amino. Example usage:
|
||||
// routeTable[reflect.TypeOf(ed25519.PubKeyEd25519{})] = ed25519.PubKeyAminoRoute
|
||||
var routeTable = make(map[reflect.Type]string, 3)
|
||||
// nameTable[reflect.TypeOf(ed25519.PubKeyEd25519{})] = ed25519.PubKeyAminoName
|
||||
var nameTable = make(map[reflect.Type]string, 3)
|
||||
|
||||
func init() {
|
||||
// NOTE: It's important that there be no conflicts here,
|
||||
@@ -29,16 +29,16 @@ func init() {
|
||||
|
||||
// TODO: Have amino provide a way to go from concrete struct to route directly.
|
||||
// Its currently a private API
|
||||
routeTable[reflect.TypeOf(ed25519.PubKeyEd25519{})] = ed25519.PubKeyAminoRoute
|
||||
routeTable[reflect.TypeOf(secp256k1.PubKeySecp256k1{})] = secp256k1.PubKeyAminoRoute
|
||||
routeTable[reflect.TypeOf(&multisig.PubKeyMultisigThreshold{})] = multisig.PubKeyMultisigThresholdAminoRoute
|
||||
nameTable[reflect.TypeOf(ed25519.PubKeyEd25519{})] = ed25519.PubKeyAminoName
|
||||
nameTable[reflect.TypeOf(secp256k1.PubKeySecp256k1{})] = secp256k1.PubKeyAminoName
|
||||
nameTable[reflect.TypeOf(multisig.PubKeyMultisigThreshold{})] = multisig.PubKeyMultisigThresholdAminoRoute
|
||||
}
|
||||
|
||||
// PubkeyAminoRoute returns the amino route of a pubkey
|
||||
// PubkeyAminoName returns the amino route of a pubkey
|
||||
// cdc is currently passed in, as eventually this will not be using
|
||||
// a package level codec.
|
||||
func PubkeyAminoRoute(cdc *amino.Codec, key crypto.PubKey) (string, bool) {
|
||||
route, found := routeTable[reflect.TypeOf(key)]
|
||||
func PubkeyAminoName(cdc *amino.Codec, key crypto.PubKey) (string, bool) {
|
||||
route, found := nameTable[reflect.TypeOf(key)]
|
||||
return route, found
|
||||
}
|
||||
|
||||
@@ -47,17 +47,17 @@ func RegisterAmino(cdc *amino.Codec) {
|
||||
// These are all written here instead of
|
||||
cdc.RegisterInterface((*crypto.PubKey)(nil), nil)
|
||||
cdc.RegisterConcrete(ed25519.PubKeyEd25519{},
|
||||
ed25519.PubKeyAminoRoute, nil)
|
||||
ed25519.PubKeyAminoName, nil)
|
||||
cdc.RegisterConcrete(secp256k1.PubKeySecp256k1{},
|
||||
secp256k1.PubKeyAminoRoute, nil)
|
||||
secp256k1.PubKeyAminoName, nil)
|
||||
cdc.RegisterConcrete(multisig.PubKeyMultisigThreshold{},
|
||||
multisig.PubKeyMultisigThresholdAminoRoute, nil)
|
||||
|
||||
cdc.RegisterInterface((*crypto.PrivKey)(nil), nil)
|
||||
cdc.RegisterConcrete(ed25519.PrivKeyEd25519{},
|
||||
ed25519.PrivKeyAminoRoute, nil)
|
||||
ed25519.PrivKeyAminoName, nil)
|
||||
cdc.RegisterConcrete(secp256k1.PrivKeySecp256k1{},
|
||||
secp256k1.PrivKeyAminoRoute, nil)
|
||||
secp256k1.PrivKeyAminoName, nil)
|
||||
}
|
||||
|
||||
func PrivKeyFromBytes(privKeyBytes []byte) (privKey crypto.PrivKey, err error) {
|
||||
|
@@ -128,18 +128,18 @@ func TestPubKeyInvalidDataProperReturnsEmpty(t *testing.T) {
|
||||
require.Nil(t, pk)
|
||||
}
|
||||
|
||||
func TestPubkeyAminoRoute(t *testing.T) {
|
||||
func TestPubkeyAminoName(t *testing.T) {
|
||||
tests := []struct {
|
||||
key crypto.PubKey
|
||||
want string
|
||||
found bool
|
||||
}{
|
||||
{ed25519.PubKeyEd25519{}, ed25519.PubKeyAminoRoute, true},
|
||||
{secp256k1.PubKeySecp256k1{}, secp256k1.PubKeyAminoRoute, true},
|
||||
{&multisig.PubKeyMultisigThreshold{}, multisig.PubKeyMultisigThresholdAminoRoute, true},
|
||||
{ed25519.PubKeyEd25519{}, ed25519.PubKeyAminoName, true},
|
||||
{secp256k1.PubKeySecp256k1{}, secp256k1.PubKeyAminoName, true},
|
||||
{multisig.PubKeyMultisigThreshold{}, multisig.PubKeyMultisigThresholdAminoRoute, true},
|
||||
}
|
||||
for i, tc := range tests {
|
||||
got, found := PubkeyAminoRoute(cdc, tc.key)
|
||||
got, found := PubkeyAminoName(cdc, tc.key)
|
||||
require.Equal(t, tc.found, found, "not equal on tc %d", i)
|
||||
if tc.found {
|
||||
require.Equal(t, tc.want, got, "not equal on tc %d", i)
|
||||
|
@@ -3,7 +3,7 @@ package crypto
|
||||
import (
|
||||
"crypto/sha256"
|
||||
|
||||
"golang.org/x/crypto/ripemd160" // forked to github.com/tendermint/crypto
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
)
|
||||
|
||||
func Sha256(bytes []byte) []byte {
|
||||
|
@@ -2,7 +2,6 @@ package multisig
|
||||
|
||||
import (
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||
)
|
||||
|
||||
// PubKeyMultisigThreshold implements a K of N threshold multisig.
|
||||
@@ -11,7 +10,7 @@ type PubKeyMultisigThreshold struct {
|
||||
PubKeys []crypto.PubKey `json:"pubkeys"`
|
||||
}
|
||||
|
||||
var _ crypto.PubKey = &PubKeyMultisigThreshold{}
|
||||
var _ crypto.PubKey = PubKeyMultisigThreshold{}
|
||||
|
||||
// NewPubKeyMultisigThreshold returns a new PubKeyMultisigThreshold.
|
||||
// Panics if len(pubkeys) < k or 0 >= k.
|
||||
@@ -22,7 +21,7 @@ func NewPubKeyMultisigThreshold(k int, pubkeys []crypto.PubKey) crypto.PubKey {
|
||||
if len(pubkeys) < k {
|
||||
panic("threshold k of n multisignature: len(pubkeys) < k")
|
||||
}
|
||||
return &PubKeyMultisigThreshold{uint(k), pubkeys}
|
||||
return PubKeyMultisigThreshold{uint(k), pubkeys}
|
||||
}
|
||||
|
||||
// VerifyBytes expects sig to be an amino encoded version of a MultiSignature.
|
||||
@@ -31,8 +30,8 @@ func NewPubKeyMultisigThreshold(k int, pubkeys []crypto.PubKey) crypto.PubKey {
|
||||
// and all signatures are valid. (Not just k of the signatures)
|
||||
// The multisig uses a bitarray, so multiple signatures for the same key is not
|
||||
// a concern.
|
||||
func (pk *PubKeyMultisigThreshold) VerifyBytes(msg []byte, marshalledSig []byte) bool {
|
||||
var sig *Multisignature
|
||||
func (pk PubKeyMultisigThreshold) VerifyBytes(msg []byte, marshalledSig []byte) bool {
|
||||
var sig Multisignature
|
||||
err := cdc.UnmarshalBinaryBare(marshalledSig, &sig)
|
||||
if err != nil {
|
||||
return false
|
||||
@@ -64,19 +63,19 @@ func (pk *PubKeyMultisigThreshold) VerifyBytes(msg []byte, marshalledSig []byte)
|
||||
}
|
||||
|
||||
// Bytes returns the amino encoded version of the PubKeyMultisigThreshold
|
||||
func (pk *PubKeyMultisigThreshold) Bytes() []byte {
|
||||
func (pk PubKeyMultisigThreshold) Bytes() []byte {
|
||||
return cdc.MustMarshalBinaryBare(pk)
|
||||
}
|
||||
|
||||
// Address returns tmhash(PubKeyMultisigThreshold.Bytes())
|
||||
func (pk *PubKeyMultisigThreshold) Address() crypto.Address {
|
||||
return crypto.Address(tmhash.Sum(pk.Bytes()))
|
||||
func (pk PubKeyMultisigThreshold) Address() crypto.Address {
|
||||
return crypto.AddressHash(pk.Bytes())
|
||||
}
|
||||
|
||||
// Equals returns true iff pk and other both have the same number of keys, and
|
||||
// all constituent keys are the same, and in the same order.
|
||||
func (pk *PubKeyMultisigThreshold) Equals(other crypto.PubKey) bool {
|
||||
otherKey, sameType := other.(*PubKeyMultisigThreshold)
|
||||
func (pk PubKeyMultisigThreshold) Equals(other crypto.PubKey) bool {
|
||||
otherKey, sameType := other.(PubKeyMultisigThreshold)
|
||||
if !sameType {
|
||||
return false
|
||||
}
|
||||
|
@@ -82,7 +82,7 @@ func TestMultiSigPubKeyEquality(t *testing.T) {
|
||||
msg := []byte{1, 2, 3, 4}
|
||||
pubkeys, _ := generatePubKeysAndSignatures(5, msg)
|
||||
multisigKey := NewPubKeyMultisigThreshold(2, pubkeys)
|
||||
var unmarshalledMultisig *PubKeyMultisigThreshold
|
||||
var unmarshalledMultisig PubKeyMultisigThreshold
|
||||
cdc.MustUnmarshalBinaryBare(multisigKey.Bytes(), &unmarshalledMultisig)
|
||||
require.True(t, multisigKey.Equals(unmarshalledMultisig))
|
||||
|
||||
@@ -95,6 +95,29 @@ func TestMultiSigPubKeyEquality(t *testing.T) {
|
||||
require.False(t, multisigKey.Equals(multisigKey2))
|
||||
}
|
||||
|
||||
func TestAddress(t *testing.T) {
|
||||
msg := []byte{1, 2, 3, 4}
|
||||
pubkeys, _ := generatePubKeysAndSignatures(5, msg)
|
||||
multisigKey := NewPubKeyMultisigThreshold(2, pubkeys)
|
||||
require.Len(t, multisigKey.Address().Bytes(), 20)
|
||||
}
|
||||
|
||||
func TestPubKeyMultisigThresholdAminoToIface(t *testing.T) {
|
||||
msg := []byte{1, 2, 3, 4}
|
||||
pubkeys, _ := generatePubKeysAndSignatures(5, msg)
|
||||
multisigKey := NewPubKeyMultisigThreshold(2, pubkeys)
|
||||
|
||||
ab, err := cdc.MarshalBinaryLengthPrefixed(multisigKey)
|
||||
require.NoError(t, err)
|
||||
// like other crypto.Pubkey implementations (e.g. ed25519.PubKeyEd25519),
|
||||
// PubKeyMultisigThreshold should be deserializable into a crypto.PubKey:
|
||||
var pubKey crypto.PubKey
|
||||
err = cdc.UnmarshalBinaryLengthPrefixed(ab, &pubKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, multisigKey, pubKey)
|
||||
}
|
||||
|
||||
func generatePubKeysAndSignatures(n int, msg []byte) (pubkeys []crypto.PubKey, signatures [][]byte) {
|
||||
pubkeys = make([]crypto.PubKey, n)
|
||||
signatures = make([][]byte, n)
|
||||
|
@@ -20,7 +20,7 @@ func init() {
|
||||
cdc.RegisterConcrete(PubKeyMultisigThreshold{},
|
||||
PubKeyMultisigThresholdAminoRoute, nil)
|
||||
cdc.RegisterConcrete(ed25519.PubKeyEd25519{},
|
||||
ed25519.PubKeyAminoRoute, nil)
|
||||
ed25519.PubKeyAminoName, nil)
|
||||
cdc.RegisterConcrete(secp256k1.PubKeySecp256k1{},
|
||||
secp256k1.PubKeyAminoRoute, nil)
|
||||
secp256k1.PubKeyAminoName, nil)
|
||||
}
|
||||
|
@@ -9,15 +9,15 @@ import (
|
||||
|
||||
secp256k1 "github.com/tendermint/btcd/btcec"
|
||||
amino "github.com/tendermint/go-amino"
|
||||
"golang.org/x/crypto/ripemd160" // forked to github.com/tendermint/crypto
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
|
||||
//-------------------------------------
|
||||
const (
|
||||
PrivKeyAminoRoute = "tendermint/PrivKeySecp256k1"
|
||||
PubKeyAminoRoute = "tendermint/PubKeySecp256k1"
|
||||
PrivKeyAminoName = "tendermint/PrivKeySecp256k1"
|
||||
PubKeyAminoName = "tendermint/PubKeySecp256k1"
|
||||
)
|
||||
|
||||
var cdc = amino.NewCodec()
|
||||
@@ -25,11 +25,11 @@ var cdc = amino.NewCodec()
|
||||
func init() {
|
||||
cdc.RegisterInterface((*crypto.PubKey)(nil), nil)
|
||||
cdc.RegisterConcrete(PubKeySecp256k1{},
|
||||
PubKeyAminoRoute, nil)
|
||||
PubKeyAminoName, nil)
|
||||
|
||||
cdc.RegisterInterface((*crypto.PrivKey)(nil), nil)
|
||||
cdc.RegisterConcrete(PrivKeySecp256k1{},
|
||||
PrivKeyAminoRoute, nil)
|
||||
PrivKeyAminoName, nil)
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
@@ -8,7 +8,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305" // forked to github.com/tendermint/crypto
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
// Implements crypto.AEAD
|
||||
|
@@ -4,7 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/nacl/secretbox" // forked to github.com/tendermint/crypto
|
||||
"golang.org/x/crypto/nacl/secretbox"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
|
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"golang.org/x/crypto/bcrypt" // forked to github.com/tendermint/crypto
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
@@ -30,9 +30,7 @@ func TestSimpleWithKDF(t *testing.T) {
|
||||
|
||||
plaintext := []byte("sometext")
|
||||
secretPass := []byte("somesecret")
|
||||
salt := []byte("somesaltsomesalt") // len 16
|
||||
// NOTE: we use a fork of x/crypto so we can inject our own randomness for salt
|
||||
secret, err := bcrypt.GenerateFromPassword(salt, secretPass, 12)
|
||||
secret, err := bcrypt.GenerateFromPassword(secretPass, 12)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
@@ -8,8 +8,21 @@ module.exports = {
|
||||
lineNumbers: true
|
||||
},
|
||||
themeConfig: {
|
||||
lastUpdated: "Last Updated",
|
||||
nav: [{ text: "Back to Tendermint", link: "https://tendermint.com" }],
|
||||
repo: "tendermint/tendermint",
|
||||
editLinks: true,
|
||||
docsDir: "docs",
|
||||
docsBranch: "develop",
|
||||
editLinkText: 'Edit this page on Github',
|
||||
lastUpdated: true,
|
||||
algolia: {
|
||||
apiKey: '59f0e2deb984aa9cdf2b3a5fd24ac501',
|
||||
indexName: 'tendermint',
|
||||
debug: false
|
||||
},
|
||||
nav: [
|
||||
{ text: "Back to Tendermint", link: "https://tendermint.com" },
|
||||
{ text: "RPC", link: "../rpc/" }
|
||||
],
|
||||
sidebar: [
|
||||
{
|
||||
title: "Introduction",
|
||||
@@ -21,6 +34,20 @@ module.exports = {
|
||||
"/introduction/what-is-tendermint"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Apps",
|
||||
collapsable: false,
|
||||
children: [
|
||||
"/app-dev/getting-started",
|
||||
"/app-dev/abci-cli",
|
||||
"/app-dev/app-architecture",
|
||||
"/app-dev/app-development",
|
||||
"/app-dev/subscribing-to-events-via-websocket",
|
||||
"/app-dev/indexing-transactions",
|
||||
"/app-dev/abci-spec",
|
||||
"/app-dev/ecosystem"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Tendermint Core",
|
||||
collapsable: false,
|
||||
@@ -39,15 +66,6 @@ module.exports = {
|
||||
"/tendermint-core/validators"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Tools",
|
||||
collapsable: false,
|
||||
children: [
|
||||
"/tools/",
|
||||
"/tools/benchmarking",
|
||||
"/tools/monitoring"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Networks",
|
||||
collapsable: false,
|
||||
@@ -58,18 +76,13 @@ module.exports = {
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Apps",
|
||||
title: "Tools",
|
||||
collapsable: false,
|
||||
children: [
|
||||
"/app-dev/getting-started",
|
||||
"/app-dev/abci-cli",
|
||||
"/app-dev/app-architecture",
|
||||
"/app-dev/app-development",
|
||||
"/app-dev/subscribing-to-events-via-websocket",
|
||||
"/app-dev/indexing-transactions",
|
||||
"/app-dev/abci-spec",
|
||||
"/app-dev/ecosystem"
|
||||
]
|
||||
children: [
|
||||
"/tools/",
|
||||
"/tools/benchmarking",
|
||||
"/tools/monitoring"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Tendermint Spec",
|
||||
|
@@ -12,10 +12,10 @@ respectively.
|
||||
|
||||
## How It Works
|
||||
|
||||
There is a Jenkins job listening for changes in the `/docs` directory, on both
|
||||
There is a CircleCI job listening for changes in the `/docs` directory, on both
|
||||
the `master` and `develop` branches. Any updates to files in this directory
|
||||
on those branches will automatically trigger a website deployment. Under the hood,
|
||||
a private website repository has make targets consumed by a standard Jenkins task.
|
||||
the private website repository has a `make build-docs` target consumed by a CircleCI job in that repo.
|
||||
|
||||
## README
|
||||
|
||||
@@ -93,6 +93,10 @@ python -m SimpleHTTPServer 8080
|
||||
|
||||
then navigate to localhost:8080 in your browser.
|
||||
|
||||
## Search
|
||||
|
||||
We are using [Algolia](https://www.algolia.com) to power full-text search. This uses a public API search-only key in the `config.js` as well as a [tendermint.json](https://github.com/algolia/docsearch-configs/blob/master/configs/tendermint.json) configuration file that we can update with PRs.
|
||||
|
||||
## Consistency
|
||||
|
||||
Because the build processes are identical (as is the information contained herein), this file should be kept in sync as
|
||||
|
@@ -5,7 +5,7 @@ Tendermint blockchain application.
|
||||
|
||||
The following diagram provides a superb example:
|
||||
|
||||
<https://drive.google.com/open?id=1yR2XpRi9YCY9H9uMfcw8-RMJpvDyvjz9>
|
||||

|
||||
|
||||
The end-user application here is the Cosmos Voyager, at the bottom left.
|
||||
Voyager communicates with a REST API exposed by a local Light-Client
|
||||
|
@@ -181,5 +181,13 @@
|
||||
"language": "Javascript",
|
||||
"author": "Dennis McKinnon"
|
||||
}
|
||||
],
|
||||
"aminoLibraries": [
|
||||
{
|
||||
"name": "JS-Amino",
|
||||
"url": "https://github.com/TanNgocDo/Js-Amino",
|
||||
"language": "Javascript",
|
||||
"author": "TanNgocDo"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -1,11 +1,9 @@
|
||||
# Ecosystem
|
||||
|
||||
The growing list of applications built using various pieces of the
|
||||
Tendermint stack can be found at:
|
||||
Tendermint stack can be found at the [ecosystem page](https://tendermint.com/ecosystem).
|
||||
|
||||
- https://tendermint.com/ecosystem
|
||||
|
||||
We thank the community for their contributions thus far and welcome the
|
||||
We thank the community for their contributions and welcome the
|
||||
addition of new projects. A pull request can be submitted to [this
|
||||
file](https://github.com/tendermint/tendermint/blob/master/docs/app-dev/ecosystem.json)
|
||||
to include your project.
|
||||
|
@@ -126,6 +126,312 @@ func TestConsensusXXX(t *testing.T) {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Consensus Executor
|
||||
|
||||
## Consensus Core
|
||||
|
||||
```go
|
||||
type Event interface{}
|
||||
|
||||
type EventNewHeight struct {
|
||||
Height int64
|
||||
ValidatorId int
|
||||
}
|
||||
|
||||
type EventNewRound HeightAndRound
|
||||
|
||||
type EventProposal struct {
|
||||
Height int64
|
||||
Round int
|
||||
Timestamp Time
|
||||
BlockID BlockID
|
||||
POLRound int
|
||||
Sender int
|
||||
}
|
||||
|
||||
type Majority23PrevotesBlock struct {
|
||||
Height int64
|
||||
Round int
|
||||
BlockID BlockID
|
||||
}
|
||||
|
||||
type Majority23PrecommitBlock struct {
|
||||
Height int64
|
||||
Round int
|
||||
BlockID BlockID
|
||||
}
|
||||
|
||||
type HeightAndRound struct {
|
||||
Height int64
|
||||
Round int
|
||||
}
|
||||
|
||||
type Majority23PrevotesAny HeightAndRound
|
||||
type Majority23PrecommitAny HeightAndRound
|
||||
type TimeoutPropose HeightAndRound
|
||||
type TimeoutPrevotes HeightAndRound
|
||||
type TimeoutPrecommit HeightAndRound
|
||||
|
||||
|
||||
type Message interface{}
|
||||
|
||||
type MessageProposal struct {
|
||||
Height int64
|
||||
Round int
|
||||
BlockID BlockID
|
||||
POLRound int
|
||||
}
|
||||
|
||||
type VoteType int
|
||||
|
||||
const (
|
||||
VoteTypeUnknown VoteType = iota
|
||||
Prevote
|
||||
Precommit
|
||||
)
|
||||
|
||||
|
||||
type MessageVote struct {
|
||||
Height int64
|
||||
Round int
|
||||
BlockID BlockID
|
||||
Type VoteType
|
||||
}
|
||||
|
||||
|
||||
type MessageDecision struct {
|
||||
Height int64
|
||||
Round int
|
||||
BlockID BlockID
|
||||
}
|
||||
|
||||
type TriggerTimeout struct {
|
||||
Height int64
|
||||
Round int
|
||||
Duration Duration
|
||||
}
|
||||
|
||||
|
||||
type RoundStep int
|
||||
|
||||
const (
|
||||
RoundStepUnknown RoundStep = iota
|
||||
RoundStepPropose
|
||||
RoundStepPrevote
|
||||
RoundStepPrecommit
|
||||
RoundStepCommit
|
||||
)
|
||||
|
||||
type State struct {
|
||||
Height int64
|
||||
Round int
|
||||
Step RoundStep
|
||||
LockedValue BlockID
|
||||
LockedRound int
|
||||
ValidValue BlockID
|
||||
ValidRound int
|
||||
ValidatorId int
|
||||
ValidatorSetSize int
|
||||
}
|
||||
|
||||
func proposer(height int64, round int) int {}
|
||||
func getValue() BlockID {}
|
||||
|
||||
func Consensus(event Event, state State) (State, Message, TriggerTimeout) {
|
||||
msg = nil
|
||||
timeout = nil
|
||||
switch event := event.(type) {
|
||||
case EventNewHeight:
|
||||
if event.Height > state.Height {
|
||||
state.Height = event.Height
|
||||
state.Round = -1
|
||||
state.Step = RoundStepPropose
|
||||
state.LockedValue = nil
|
||||
state.LockedRound = -1
|
||||
state.ValidValue = nil
|
||||
state.ValidRound = -1
|
||||
state.ValidatorId = event.ValidatorId
|
||||
}
|
||||
return state, msg, timeout
|
||||
|
||||
case EventNewRound:
|
||||
if event.Height == state.Height and event.Round > state.Round {
|
||||
state.Round = eventRound
|
||||
state.Step = RoundStepPropose
|
||||
if proposer(state.Height, state.Round) == state.ValidatorId {
|
||||
proposal = state.ValidValue
|
||||
if proposal == nil {
|
||||
proposal = getValue()
|
||||
}
|
||||
msg = MessageProposal { state.Height, state.Round, proposal, state.ValidRound }
|
||||
}
|
||||
timeout = TriggerTimeout { state.Height, state.Round, timeoutPropose(state.Round) }
|
||||
}
|
||||
return state, msg, timeout
|
||||
|
||||
case EventProposal:
|
||||
if event.Height == state.Height and event.Round == state.Round and
|
||||
event.Sender == proposal(state.Height, state.Round) and state.Step == RoundStepPropose {
|
||||
if event.POLRound >= state.LockedRound or event.BlockID == state.BlockID or state.LockedRound == -1 {
|
||||
msg = MessageVote { state.Height, state.Round, event.BlockID, Prevote }
|
||||
}
|
||||
state.Step = RoundStepPrevote
|
||||
}
|
||||
return state, msg, timeout
|
||||
|
||||
case TimeoutPropose:
|
||||
if event.Height == state.Height and event.Round == state.Round and state.Step == RoundStepPropose {
|
||||
msg = MessageVote { state.Height, state.Round, nil, Prevote }
|
||||
state.Step = RoundStepPrevote
|
||||
}
|
||||
return state, msg, timeout
|
||||
|
||||
case Majority23PrevotesBlock:
|
||||
if event.Height == state.Height and event.Round == state.Round and state.Step >= RoundStepPrevote and event.Round > state.ValidRound {
|
||||
state.ValidRound = event.Round
|
||||
state.ValidValue = event.BlockID
|
||||
if state.Step == RoundStepPrevote {
|
||||
state.LockedRound = event.Round
|
||||
state.LockedValue = event.BlockID
|
||||
msg = MessageVote { state.Height, state.Round, event.BlockID, Precommit }
|
||||
state.Step = RoundStepPrecommit
|
||||
}
|
||||
}
|
||||
return state, msg, timeout
|
||||
|
||||
case Majority23PrevotesAny:
|
||||
if event.Height == state.Height and event.Round == state.Round and state.Step == RoundStepPrevote {
|
||||
timeout = TriggerTimeout { state.Height, state.Round, timeoutPrevote(state.Round) }
|
||||
}
|
||||
return state, msg, timeout
|
||||
|
||||
case TimeoutPrevote:
|
||||
if event.Height == state.Height and event.Round == state.Round and state.Step == RoundStepPrevote {
|
||||
msg = MessageVote { state.Height, state.Round, nil, Precommit }
|
||||
state.Step = RoundStepPrecommit
|
||||
}
|
||||
return state, msg, timeout
|
||||
|
||||
case Majority23PrecommitBlock:
|
||||
if event.Height == state.Height {
|
||||
state.Step = RoundStepCommit
|
||||
state.LockedValue = event.BlockID
|
||||
}
|
||||
return state, msg, timeout
|
||||
|
||||
case Majority23PrecommitAny:
|
||||
if event.Height == state.Height and event.Round == state.Round {
|
||||
timeout = TriggerTimeout { state.Height, state.Round, timeoutPrecommit(state.Round) }
|
||||
}
|
||||
return state, msg, timeout
|
||||
|
||||
case TimeoutPrecommit:
|
||||
if event.Height == state.Height and event.Round == state.Round {
|
||||
state.Round = state.Round + 1
|
||||
}
|
||||
return state, msg, timeout
|
||||
}
|
||||
}
|
||||
|
||||
func ConsensusExecutor() {
|
||||
proposal = nil
|
||||
votes = HeightVoteSet { Height: 1 }
|
||||
state = State {
|
||||
Height: 1
|
||||
Round: 0
|
||||
Step: RoundStepPropose
|
||||
LockedValue: nil
|
||||
LockedRound: -1
|
||||
ValidValue: nil
|
||||
ValidRound: -1
|
||||
}
|
||||
|
||||
event = EventNewHeight {1, id}
|
||||
state, msg, timeout = Consensus(event, state)
|
||||
|
||||
event = EventNewRound {state.Height, 0}
|
||||
state, msg, timeout = Consensus(event, state)
|
||||
|
||||
if msg != nil {
|
||||
send msg
|
||||
}
|
||||
|
||||
if timeout != nil {
|
||||
trigger timeout
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case message := <- msgCh:
|
||||
switch msg := message.(type) {
|
||||
case MessageProposal:
|
||||
|
||||
case MessageVote:
|
||||
if msg.Height == state.Height {
|
||||
newVote = votes.AddVote(msg)
|
||||
if newVote {
|
||||
switch msg.Type {
|
||||
case Prevote:
|
||||
prevotes = votes.Prevotes(msg.Round)
|
||||
if prevotes.WeakCertificate() and msg.Round > state.Round {
|
||||
event = EventNewRound { msg.Height, msg.Round }
|
||||
state, msg, timeout = Consensus(event, state)
|
||||
state = handleStateChange(state, msg, timeout)
|
||||
}
|
||||
|
||||
if blockID, ok = prevotes.TwoThirdsMajority(); ok and blockID != nil {
|
||||
if msg.Round == state.Round and hasBlock(blockID) {
|
||||
event = Majority23PrevotesBlock { msg.Height, msg.Round, blockID }
|
||||
state, msg, timeout = Consensus(event, state)
|
||||
state = handleStateChange(state, msg, timeout)
|
||||
}
|
||||
if proposal != nil and proposal.POLRound == msg.Round and hasBlock(blockID) {
|
||||
event = EventProposal {
|
||||
Height: state.Height
|
||||
Round: state.Round
|
||||
BlockID: blockID
|
||||
POLRound: proposal.POLRound
|
||||
Sender: message.Sender
|
||||
}
|
||||
state, msg, timeout = Consensus(event, state)
|
||||
state = handleStateChange(state, msg, timeout)
|
||||
}
|
||||
}
|
||||
|
||||
if prevotes.HasTwoThirdsAny() and msg.Round == state.Round {
|
||||
event = Majority23PrevotesAny { msg.Height, msg.Round, blockID }
|
||||
state, msg, timeout = Consensus(event, state)
|
||||
state = handleStateChange(state, msg, timeout)
|
||||
}
|
||||
|
||||
case Precommit:
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
case timeout := <- timeoutCh:
|
||||
|
||||
case block := <- blockCh:
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleStateChange(state, msg, timeout) State {
|
||||
if state.Step == Commit {
|
||||
state = ExecuteBlock(state.LockedValue)
|
||||
}
|
||||
if msg != nil {
|
||||
send msg
|
||||
}
|
||||
if timeout != nil {
|
||||
trigger timeout
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Implementation roadmap
|
||||
|
||||
* implement proposed implementation
|
||||
|
BIN
docs/imgs/cosmos-tendermint-stack-4k.jpg
Normal file
BIN
docs/imgs/cosmos-tendermint-stack-4k.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 625 KiB |
@@ -70,10 +70,6 @@ Tendermint is in essence similar software, but with two key differences:
|
||||
the application logic that's right for them, from key-value store to
|
||||
cryptocurrency to e-voting platform and beyond.
|
||||
|
||||
The layout of this Tendermint website content is also ripped directly
|
||||
and without shame from [consul.io](https://www.consul.io/) and the other
|
||||
[Hashicorp sites](https://www.hashicorp.com/#tools).
|
||||
|
||||
### Bitcoin, Ethereum, etc.
|
||||
|
||||
Tendermint emerged in the tradition of cryptocurrencies like Bitcoin,
|
||||
|
@@ -1,20 +1,17 @@
|
||||
# Docker Compose
|
||||
|
||||
With Docker Compose, we can spin up local testnets in a single command:
|
||||
|
||||
```
|
||||
make localnet-start
|
||||
```
|
||||
With Docker Compose, you can spin up local testnets with a single command.
|
||||
|
||||
## Requirements
|
||||
|
||||
- [Install tendermint](/docs/install.md)
|
||||
- [Install docker](https://docs.docker.com/engine/installation/)
|
||||
- [Install docker-compose](https://docs.docker.com/compose/install/)
|
||||
1. [Install tendermint](/docs/install.md)
|
||||
2. [Install docker](https://docs.docker.com/engine/installation/)
|
||||
3. [Install docker-compose](https://docs.docker.com/compose/install/)
|
||||
|
||||
## Build
|
||||
|
||||
Build the `tendermint` binary and the `tendermint/localnode` docker image.
|
||||
Build the `tendermint` binary and, optionally, the `tendermint/localnode`
|
||||
docker image.
|
||||
|
||||
Note the binary will be mounted into the container so it can be updated without
|
||||
rebuilding the image.
|
||||
@@ -25,11 +22,10 @@ cd $GOPATH/src/github.com/tendermint/tendermint
|
||||
# Build the linux binary in ./build
|
||||
make build-linux
|
||||
|
||||
# Build tendermint/localnode image
|
||||
# (optionally) Build tendermint/localnode image
|
||||
make build-docker-localnode
|
||||
```
|
||||
|
||||
|
||||
## Run a testnet
|
||||
|
||||
To start a 4 node testnet run:
|
||||
@@ -38,9 +34,13 @@ To start a 4 node testnet run:
|
||||
make localnet-start
|
||||
```
|
||||
|
||||
The nodes bind their RPC servers to ports 26657, 26660, 26662, and 26664 on the host.
|
||||
The nodes bind their RPC servers to ports 26657, 26660, 26662, and 26664 on the
|
||||
host.
|
||||
|
||||
This file creates a 4-node network using the localnode image.
|
||||
The nodes of the network expose their P2P and RPC endpoints to the host machine on ports 26656-26657, 26659-26660, 26661-26662, and 26663-26664 respectively.
|
||||
|
||||
The nodes of the network expose their P2P and RPC endpoints to the host machine
|
||||
on ports 26656-26657, 26659-26660, 26661-26662, and 26663-26664 respectively.
|
||||
|
||||
To update the binary, just rebuild it and restart the nodes:
|
||||
|
||||
@@ -52,34 +52,40 @@ make localnet-start
|
||||
|
||||
## Configuration
|
||||
|
||||
The `make localnet-start` creates files for a 4-node testnet in `./build` by calling the `tendermint testnet` command.
|
||||
The `make localnet-start` creates files for a 4-node testnet in `./build` by
|
||||
calling the `tendermint testnet` command.
|
||||
|
||||
The `./build` directory is mounted to the `/tendermint` mount point to attach the binary and config files to the container.
|
||||
The `./build` directory is mounted to the `/tendermint` mount point to attach
|
||||
the binary and config files to the container.
|
||||
|
||||
For instance, to create a single node testnet:
|
||||
To change the number of validators / non-validators change the `localnet-start` Makefile target:
|
||||
|
||||
```
|
||||
localnet-start: localnet-stop
|
||||
@if ! [ -f build/node0/config/genesis.json ]; then docker run --rm -v $(CURDIR)/build:/tendermint:Z tendermint/localnode testnet --v 5 --n 3 --o . --populate-persistent-peers --starting-ip-address 192.167.10.2 ; fi
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
The command now will generate config files for 5 validators and 3
|
||||
non-validators network.
|
||||
|
||||
Before running it, don't forget to cleanup the old files:
|
||||
|
||||
```
|
||||
cd $GOPATH/src/github.com/tendermint/tendermint
|
||||
|
||||
# Clear the build folder
|
||||
rm -rf ./build
|
||||
|
||||
# Build binary
|
||||
make build-linux
|
||||
|
||||
# Create configuration
|
||||
docker run -e LOG="stdout" -v `pwd`/build:/tendermint tendermint/localnode testnet --o . --v 1
|
||||
|
||||
#Run the node
|
||||
docker run -v `pwd`/build:/tendermint tendermint/localnode
|
||||
|
||||
rm -rf ./build/node*
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
Log is saved under the attached volume, in the `tendermint.log` file. If the `LOG` environment variable is set to `stdout` at start, the log is not saved, but printed on the screen.
|
||||
Log is saved under the attached volume, in the `tendermint.log` file. If the
|
||||
`LOG` environment variable is set to `stdout` at start, the log is not saved,
|
||||
but printed on the screen.
|
||||
|
||||
## Special binaries
|
||||
|
||||
If you have multiple binaries with different names, you can specify which one to run with the BINARY environment variable. The path of the binary is relative to the attached volume.
|
||||
|
||||
If you have multiple binaries with different names, you can specify which one
|
||||
to run with the `BINARY` environment variable. The path of the binary is relative
|
||||
to the attached volume.
|
||||
|
4670
docs/package-lock.json
generated
4670
docs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"prettier": "^1.13.7",
|
||||
"remark-cli": "^5.0.0",
|
||||
"remark-lint-no-dead-urls": "^0.3.0",
|
||||
"remark-lint-write-good": "^1.0.3",
|
||||
"textlint": "^10.2.1",
|
||||
"textlint-rule-stop-words": "^1.0.3"
|
||||
},
|
||||
"name": "tendermint",
|
||||
"description": "Tendermint Core Documentation",
|
||||
"version": "0.0.1",
|
||||
"main": "README.md",
|
||||
"devDependencies": {},
|
||||
"scripts": {
|
||||
"lint:json": "prettier \"**/*.json\" --write",
|
||||
"lint:md": "prettier \"**/*.md\" --write && remark . && textlint \"md/**\"",
|
||||
"lint": "yarn lint:json && yarn lint:md"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tendermint/tendermint.git"
|
||||
},
|
||||
"keywords": [
|
||||
"tendermint",
|
||||
"blockchain"
|
||||
],
|
||||
"author": "Tendermint",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/tendermint/tendermint/issues"
|
||||
},
|
||||
"homepage": "https://tendermint.com/docs/",
|
||||
"remarkConfig": {
|
||||
"plugins": [
|
||||
"remark-lint-no-dead-urls",
|
||||
"remark-lint-write-good"
|
||||
]
|
||||
}
|
||||
}
|
@@ -14,31 +14,31 @@ please submit them to our [bug bounty](https://tendermint.com/security)!
|
||||
|
||||
### Data Structures
|
||||
|
||||
- [Encoding and Digests](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/encoding.md)
|
||||
- [Blockchain](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/blockchain.md)
|
||||
- [State](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/state.md)
|
||||
- [Encoding and Digests](./blockchain/encoding.md)
|
||||
- [Blockchain](./blockchain/blockchain.md)
|
||||
- [State](./blockchain/state.md)
|
||||
|
||||
### Consensus Protocol
|
||||
|
||||
- [Consensus Algorithm](/docs/spec/consensus/consensus.md)
|
||||
- [Creating a proposal](/docs/spec/consensus/creating-proposal.md)
|
||||
- [Time](/docs/spec/consensus/bft-time.md)
|
||||
- [Light-Client](/docs/spec/consensus/light-client.md)
|
||||
- [Consensus Algorithm](./consensus/consensus.md)
|
||||
- [Creating a proposal](./consensus/creating-proposal.md)
|
||||
- [Time](./consensus/bft-time.md)
|
||||
- [Light-Client](./consensus/light-client.md)
|
||||
|
||||
### P2P and Network Protocols
|
||||
|
||||
- [The Base P2P Layer](https://github.com/tendermint/tendermint/tree/master/docs/spec/p2p): multiplex the protocols ("reactors") on authenticated and encrypted TCP connections
|
||||
- [Peer Exchange (PEX)](https://github.com/tendermint/tendermint/tree/master/docs/spec/reactors/pex): gossip known peer addresses so peers can find each other
|
||||
- [Block Sync](https://github.com/tendermint/tendermint/tree/master/docs/spec/reactors/block_sync): gossip blocks so peers can catch up quickly
|
||||
- [Consensus](https://github.com/tendermint/tendermint/tree/master/docs/spec/reactors/consensus): gossip votes and block parts so new blocks can be committed
|
||||
- [Mempool](https://github.com/tendermint/tendermint/tree/master/docs/spec/reactors/mempool): gossip transactions so they get included in blocks
|
||||
- Evidence: Forthcoming, see [this issue](https://github.com/tendermint/tendermint/issues/2329).
|
||||
- [The Base P2P Layer](./p2p/): multiplex the protocols ("reactors") on authenticated and encrypted TCP connections
|
||||
- [Peer Exchange (PEX)](./reactors/pex/): gossip known peer addresses so peers can find each other
|
||||
- [Block Sync](./reactors/block_sync/): gossip blocks so peers can catch up quickly
|
||||
- [Consensus](./reactors/consensus/): gossip votes and block parts so new blocks can be committed
|
||||
- [Mempool](./reactors/mempool/): gossip transactions so they get included in blocks
|
||||
- [Evidence](./reactors/evidence/): sending invalid evidence will stop the peer
|
||||
|
||||
### Software
|
||||
|
||||
- [ABCI](/docs/spec/software/abci.md): Details about interactions between the
|
||||
- [ABCI](./software/abci.md): Details about interactions between the
|
||||
application and consensus engine over ABCI
|
||||
- [Write-Ahead Log](/docs/spec/software/wal.md): Details about how the consensus
|
||||
- [Write-Ahead Log](./software/wal.md): Details about how the consensus
|
||||
engine preserves data and recovers from crash failures
|
||||
|
||||
## Overview
|
||||
|
@@ -407,21 +407,22 @@ If `storeBlockHeight == stateBlockHeight && appBlockHeight < storeBlockHeight`,
|
||||
replay all blocks in full from `appBlockHeight` to `storeBlockHeight`.
|
||||
This happens if we completed processing the block, but the app forgot its height.
|
||||
|
||||
If `storeBlockHeight == stateBlockHeight && appBlockHeight == storeBlockHeight`, we're done
|
||||
If `storeBlockHeight == stateBlockHeight && appBlockHeight == storeBlockHeight`, we're done.
|
||||
This happens if we crashed at an opportune spot.
|
||||
|
||||
If `storeBlockHeight == stateBlockHeight+1`
|
||||
This happens if we started processing the block but didn't finish.
|
||||
|
||||
If `appBlockHeight < stateBlockHeight`
|
||||
replay all blocks in full from `appBlockHeight` to `storeBlockHeight-1`,
|
||||
and replay the block at `storeBlockHeight` using the WAL.
|
||||
This happens if the app forgot the last block it committed.
|
||||
If `appBlockHeight < stateBlockHeight`
|
||||
replay all blocks in full from `appBlockHeight` to `storeBlockHeight-1`,
|
||||
and replay the block at `storeBlockHeight` using the WAL.
|
||||
This happens if the app forgot the last block it committed.
|
||||
|
||||
If `appBlockHeight == stateBlockHeight`,
|
||||
replay the last block (storeBlockHeight) in full.
|
||||
This happens if we crashed before the app finished Commit
|
||||
If `appBlockHeight == stateBlockHeight`,
|
||||
replay the last block (storeBlockHeight) in full.
|
||||
This happens if we crashed before the app finished Commit
|
||||
|
||||
If `appBlockHeight == storeBlockHeight`
|
||||
update the state using the saved ABCI responses but dont run the block against the real app.
|
||||
This happens if we crashed after the app finished Commit but before Tendermint saved the state.
|
||||
|
||||
If appBlockHeight == storeBlockHeight {
|
||||
update the state using the saved ABCI responses but dont run the block against the real app.
|
||||
This happens if we crashed after the app finished Commit but before Tendermint saved the state.
|
||||
|
@@ -230,7 +230,7 @@ The block version must match the state version.
|
||||
len(block.ChainID) < 50
|
||||
```
|
||||
|
||||
ChainID must be maximum 50 UTF-8 symbols.
|
||||
ChainID must be less than 50 bytes.
|
||||
|
||||
### Height
|
||||
|
||||
|
205
docs/spec/consensus/signing.md
Normal file
205
docs/spec/consensus/signing.md
Normal file
@@ -0,0 +1,205 @@
|
||||
# Validator Signing
|
||||
|
||||
Here we specify the rules for validating a proposal and vote before signing.
|
||||
First we include some general notes on validating data structures common to both types.
|
||||
We then provide specific validation rules for each. Finally, we include validation rules to prevent double-sigining.
|
||||
|
||||
## SignedMsgType
|
||||
|
||||
The `SignedMsgType` is a single byte that refers to the type of the message
|
||||
being signed. It is defined in Go as follows:
|
||||
|
||||
```
|
||||
// SignedMsgType is a type of signed message in the consensus.
|
||||
type SignedMsgType byte
|
||||
|
||||
const (
|
||||
// Votes
|
||||
PrevoteType SignedMsgType = 0x01
|
||||
PrecommitType SignedMsgType = 0x02
|
||||
|
||||
// Proposals
|
||||
ProposalType SignedMsgType = 0x20
|
||||
)
|
||||
```
|
||||
|
||||
All signed messages must correspond to one of these types.
|
||||
|
||||
## Timestamp
|
||||
|
||||
Timestamp validation is subtle and there are currently no bounds placed on the
|
||||
timestamp included in a proposal or vote. It is expected that validators will honestly
|
||||
report their local clock time. The median of all timestamps
|
||||
included in a commit is used as the timestamp for the next block height.
|
||||
|
||||
Timestamps are expected to be strictly monotonic for a given validator, though
|
||||
this is not currently enforced.
|
||||
|
||||
## ChainID
|
||||
|
||||
ChainID is an unstructured string with a max length of 50-bytes.
|
||||
In the future, the ChainID may become structured, and may take on longer lengths.
|
||||
For now, it is recommended that signers be configured for a particular ChainID,
|
||||
and to only sign votes and proposals corresponding to that ChainID.
|
||||
|
||||
## BlockID
|
||||
|
||||
BlockID is the structure used to represent the block:
|
||||
|
||||
```
|
||||
type BlockID struct {
|
||||
Hash []byte
|
||||
PartsHeader PartSetHeader
|
||||
}
|
||||
|
||||
type PartSetHeader struct {
|
||||
Hash []byte
|
||||
Total int
|
||||
}
|
||||
```
|
||||
|
||||
To be included in a valid vote or proposal, BlockID must either represent a `nil` block, or a complete one.
|
||||
We introduce two methods, `BlockID.IsNil()` and `BlockID.IsComplete()` for these cases, respectively.
|
||||
|
||||
`BlockID.IsNil()` returns true for BlockID `b` if each of the following
|
||||
are true:
|
||||
|
||||
```
|
||||
b.Hash == nil
|
||||
b.PartsHeader.Total == 0
|
||||
b.PartsHeader.Hash == nil
|
||||
```
|
||||
|
||||
`BlockID.IsComplete()` returns true for BlockID `b` if each of the following
|
||||
are true:
|
||||
|
||||
```
|
||||
len(b.Hash) == 32
|
||||
b.PartsHeader.Total > 0
|
||||
len(b.PartsHeader.Hash) == 32
|
||||
```
|
||||
|
||||
## Proposals
|
||||
|
||||
The structure of a propsal for signing looks like:
|
||||
|
||||
```
|
||||
type CanonicalProposal struct {
|
||||
Type SignedMsgType // type alias for byte
|
||||
Height int64 `binary:"fixed64"`
|
||||
Round int64 `binary:"fixed64"`
|
||||
POLRound int64 `binary:"fixed64"`
|
||||
BlockID BlockID
|
||||
Timestamp time.Time
|
||||
ChainID string
|
||||
}
|
||||
```
|
||||
|
||||
A proposal is valid if each of the following lines evaluates to true for proposal `p`:
|
||||
|
||||
```
|
||||
p.Type == 0x20
|
||||
p.Height > 0
|
||||
p.Round >= 0
|
||||
p.POLRound >= -1
|
||||
p.BlockID.IsComplete()
|
||||
```
|
||||
|
||||
In other words, a proposal is valid for signing if it contains the type of a Proposal
|
||||
(0x20), has a positive, non-zero height, a
|
||||
non-negative round, a POLRound not less than -1, and a complete BlockID.
|
||||
|
||||
## Votes
|
||||
|
||||
The structure of a vote for signing looks like:
|
||||
|
||||
```
|
||||
type CanonicalVote struct {
|
||||
Type SignedMsgType // type alias for byte
|
||||
Height int64 `binary:"fixed64"`
|
||||
Round int64 `binary:"fixed64"`
|
||||
Timestamp time.Time
|
||||
BlockID BlockID
|
||||
ChainID string
|
||||
}
|
||||
```
|
||||
|
||||
A vote is valid if each of the following lines evaluates to true for vote `v`:
|
||||
|
||||
```
|
||||
v.Type == 0x1 || v.Type == 0x2
|
||||
v.Height > 0
|
||||
v.Round >= 0
|
||||
v.BlockID.IsNil() || v.BlockID.IsValid()
|
||||
```
|
||||
|
||||
In other words, a vote is valid for signing if it contains the type of a Prevote
|
||||
or Precommit (0x1 or 0x2, respectively), has a positive, non-zero height, a
|
||||
non-negative round, and an empty or valid BlockID.
|
||||
|
||||
## Invalid Votes and Proposals
|
||||
|
||||
Votes and proposals which do not satisfy the above rules are considered invalid.
|
||||
Peers gossipping invalid votes and proposals may be disconnected from other peers on the network.
|
||||
Note, however, that there is not currently any explicit mechanism to punish validators signing votes or proposals that fail
|
||||
these basic validation rules.
|
||||
|
||||
## Double Signing
|
||||
|
||||
Signers must be careful not to sign conflicting messages, also known as "double signing" or "equivocating".
|
||||
Tendermint has mechanisms to publish evidence of validators that signed conflicting votes, so they can be punished
|
||||
by the application. Note Tendermint does not currently handle evidence of conflciting proposals, though it may in the future.
|
||||
|
||||
### State
|
||||
|
||||
To prevent such double signing, signers must track the height, round, and type of the last message signed.
|
||||
Assume the signer keeps the following state, `s`:
|
||||
|
||||
```
|
||||
type LastSigned struct {
|
||||
Height int64
|
||||
Round int64
|
||||
Type SignedMsgType // byte
|
||||
}
|
||||
```
|
||||
|
||||
After signing a vote or proposal `m`, the signer sets:
|
||||
|
||||
```
|
||||
s.Height = m.Height
|
||||
s.Round = m.Round
|
||||
s.Type = m.Type
|
||||
```
|
||||
|
||||
### Proposals
|
||||
|
||||
A signer should only sign a proposal `p` if any of the following lines are true:
|
||||
|
||||
```
|
||||
p.Height > s.Height
|
||||
p.Height == s.Height && p.Round > s.Round
|
||||
```
|
||||
|
||||
In other words, a proposal should only be signed if it's at a higher height, or a higher round for the same height.
|
||||
Once a proposal or vote has been signed for a given height and round, a proposal should never be signed for the same height and round.
|
||||
|
||||
### Votes
|
||||
|
||||
A signer should only sign a vote `v` if any of the following lines are true:
|
||||
|
||||
```
|
||||
v.Height > s.Height
|
||||
v.Height == s.Height && v.Round > s.Round
|
||||
v.Height == s.Height && v.Round == s.Round && v.Step == 0x1 && s.Step == 0x20
|
||||
v.Height == s.Height && v.Round == s.Round && v.Step == 0x2 && s.Step != 0x2
|
||||
```
|
||||
|
||||
In other words, a vote should only be signed if it's:
|
||||
|
||||
- at a higher height
|
||||
- at a higher round for the same height
|
||||
- a prevote for the same height and round where we haven't signed a prevote or precommit (but have signed a proposal)
|
||||
- a precommit for the same height and round where we haven't signed a precommit (but have signed a proposal and/or a prevote)
|
||||
|
||||
This means that once a validator signs a prevote for a given height and round, the only other message it can sign for that height and round is a precommit.
|
||||
And once a validator signs a precommit for a given height and round, it must not sign any other message for that same height and round.
|
@@ -36,22 +36,26 @@ db_backend = "leveldb"
|
||||
# Database directory
|
||||
db_dir = "data"
|
||||
|
||||
# Output level for logging
|
||||
log_level = "state:info,*:error"
|
||||
# Output level for logging, including package level options
|
||||
log_level = "main:info,state:info,*:error"
|
||||
|
||||
# Output format: 'plain' (colored text) or 'json'
|
||||
log_format = "plain"
|
||||
|
||||
##### additional base config options #####
|
||||
|
||||
# The ID of the chain to join (should be signed with every transaction and vote)
|
||||
chain_id = ""
|
||||
|
||||
# Path to the JSON file containing the initial validator set and other meta data
|
||||
genesis_file = "genesis.json"
|
||||
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 = "priv_validator.json"
|
||||
priv_validator_file = "config/priv_validator.json"
|
||||
|
||||
# TCP or UNIX socket address for Tendermint to listen on for
|
||||
# connections from an external PrivValidator process
|
||||
priv_validator_laddr = ""
|
||||
|
||||
# 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"
|
||||
@@ -74,13 +78,13 @@ laddr = "tcp://0.0.0.0:26657"
|
||||
# A list of origins a cross-domain request can be executed from
|
||||
# Default value '[]' disables cors support
|
||||
# Use '["*"]' to allow any origin
|
||||
cors_allowed_origins = "[]"
|
||||
cors_allowed_origins = []
|
||||
|
||||
# A list of methods the client is allowed to use with cross-domain requests
|
||||
cors_allowed_methods = "[HEAD GET POST]"
|
||||
cors_allowed_methods = ["HEAD", "GET", "POST"]
|
||||
|
||||
# A list of non simple headers the client is allowed to use with cross-domain requests
|
||||
cors_allowed_headers = "[Origin Accept Content-Type X-Requested-With X-Server-Time]"
|
||||
cors_allowed_headers = ["Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time"]
|
||||
|
||||
# TCP or UNIX socket address for the gRPC server to listen on
|
||||
# NOTE: This server only supports /broadcast_tx_commit
|
||||
@@ -88,7 +92,7 @@ grpc_laddr = ""
|
||||
|
||||
# Maximum number of simultaneous connections.
|
||||
# Does not include RPC (HTTP&WebSocket) connections. See max_open_connections
|
||||
# If you want to accept more significant number than the default, make sure
|
||||
# If you want to accept a larger number than the default, make sure
|
||||
# you increase your OS limits.
|
||||
# 0 - unlimited.
|
||||
# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files}
|
||||
@@ -100,7 +104,7 @@ unsafe = false
|
||||
|
||||
# Maximum number of simultaneous connections (including WebSocket).
|
||||
# Does not include gRPC connections. See grpc_max_open_connections
|
||||
# If you want to accept more significant number than the default, make sure
|
||||
# If you want to accept a larger number than the default, make sure
|
||||
# you increase your OS limits.
|
||||
# 0 - unlimited.
|
||||
# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files}
|
||||
@@ -113,6 +117,12 @@ max_open_connections = 900
|
||||
# Address to listen for incoming connections
|
||||
laddr = "tcp://0.0.0.0:26656"
|
||||
|
||||
# Address to advertise to peers for them to dial
|
||||
# If empty, will use the same port as the laddr,
|
||||
# and will introspect on the listener or use UPnP
|
||||
# to figure out the address.
|
||||
external_address = ""
|
||||
|
||||
# Comma separated list of seed nodes to connect to
|
||||
seeds = ""
|
||||
|
||||
@@ -123,7 +133,7 @@ persistent_peers = ""
|
||||
upnp = false
|
||||
|
||||
# Path to address book
|
||||
addr_book_file = "addrbook.json"
|
||||
addr_book_file = "config/addrbook.json"
|
||||
|
||||
# Set true for strict address routability rules
|
||||
# Set false for private or local networks
|
||||
@@ -160,7 +170,7 @@ seed_mode = false
|
||||
private_peer_ids = ""
|
||||
|
||||
# Toggle to disable guard against peers connecting from the same ip.
|
||||
allow_duplicate_ip = true
|
||||
allow_duplicate_ip = false
|
||||
|
||||
# Peer connection configuration.
|
||||
handshake_timeout = "20s"
|
||||
@@ -171,26 +181,26 @@ dial_timeout = "3s"
|
||||
|
||||
recheck = true
|
||||
broadcast = true
|
||||
wal_dir = "data/mempool.wal"
|
||||
wal_dir = ""
|
||||
|
||||
# size of the mempool
|
||||
size = 100000
|
||||
size = 5000
|
||||
|
||||
# size of the cache (used to filter transactions we saw earlier)
|
||||
cache_size = 100000
|
||||
cache_size = 10000
|
||||
|
||||
##### consensus configuration options #####
|
||||
[consensus]
|
||||
|
||||
wal_file = "data/cs.wal/wal"
|
||||
|
||||
timeout_propose = "3000ms"
|
||||
timeout_propose = "3s"
|
||||
timeout_propose_delta = "500ms"
|
||||
timeout_prevote = "1000ms"
|
||||
timeout_prevote = "1s"
|
||||
timeout_prevote_delta = "500ms"
|
||||
timeout_precommit = "1000ms"
|
||||
timeout_precommit = "1s"
|
||||
timeout_precommit_delta = "500ms"
|
||||
timeout_commit = "1000ms"
|
||||
timeout_commit = "1s"
|
||||
|
||||
# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0)
|
||||
skip_timeout_commit = false
|
||||
@@ -201,10 +211,10 @@ create_empty_blocks_interval = "0s"
|
||||
|
||||
# Reactor sleep duration parameters
|
||||
peer_gossip_sleep_duration = "100ms"
|
||||
peer_query_maj23_sleep_duration = "2000ms"
|
||||
peer_query_maj23_sleep_duration = "2s"
|
||||
|
||||
# Block time parameters. Corresponds to the minimum time increment between consecutive blocks.
|
||||
blocktime_iota = "1000ms"
|
||||
blocktime_iota = "1s"
|
||||
|
||||
##### transactions indexer configuration options #####
|
||||
[tx_index]
|
||||
@@ -245,7 +255,7 @@ prometheus = false
|
||||
prometheus_listen_addr = ":26660"
|
||||
|
||||
# Maximum number of simultaneous connections.
|
||||
# If you want to accept a more significant number than the default, make sure
|
||||
# If you want to accept a larger number than the default, make sure
|
||||
# you increase your OS limits.
|
||||
# 0 - unlimited.
|
||||
max_open_connections = 3
|
||||
|
@@ -113,7 +113,7 @@ blocks are produced regularly, even if there are no transactions. See
|
||||
_No Empty Blocks_, below, to modify this setting.
|
||||
|
||||
Tendermint supports in-process versions of the `counter`, `kvstore` and
|
||||
`nil` apps that ship as examples with `abci-cli`. It's easy to compile
|
||||
`noop` apps that ship as examples with `abci-cli`. It's easy to compile
|
||||
your own app in-process with Tendermint if it's written in Go. If your
|
||||
app is not written in Go, simply run it in another process, and use the
|
||||
`--proxy_app` flag to specify the address of the socket it is listening
|
||||
|
2611
docs/yarn.lock
2611
docs/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
@@ -19,12 +20,22 @@ var (
|
||||
// inside a test (not in the init func) because
|
||||
// verbose flag only set at the time of testing.
|
||||
func TestingLogger() Logger {
|
||||
return TestingLoggerWithOutput(os.Stdout)
|
||||
}
|
||||
|
||||
// TestingLoggerWOutput returns a TMLogger which writes to (w io.Writer) if testing being run
|
||||
// with the verbose (-v) flag, NopLogger otherwise.
|
||||
//
|
||||
// Note that the call to TestingLoggerWithOutput(w io.Writer) must be made
|
||||
// inside a test (not in the init func) because
|
||||
// verbose flag only set at the time of testing.
|
||||
func TestingLoggerWithOutput(w io.Writer) Logger {
|
||||
if _testingLogger != nil {
|
||||
return _testingLogger
|
||||
}
|
||||
|
||||
if testing.Verbose() {
|
||||
_testingLogger = NewTMLogger(NewSyncWriter(os.Stdout))
|
||||
_testingLogger = NewTMLogger(NewSyncWriter(w))
|
||||
} else {
|
||||
_testingLogger = NewNopLogger()
|
||||
}
|
||||
|
@@ -108,6 +108,10 @@ func PostCheckMaxGas(maxGas int64) PostCheckFunc {
|
||||
if maxGas == -1 {
|
||||
return nil
|
||||
}
|
||||
if res.GasWanted < 0 {
|
||||
return fmt.Errorf("gas wanted %d is negative",
|
||||
res.GasWanted)
|
||||
}
|
||||
if res.GasWanted > maxGas {
|
||||
return fmt.Errorf("gas wanted %d is greater than max gas %d",
|
||||
res.GasWanted, maxGas)
|
||||
@@ -486,11 +490,15 @@ func (mem *Mempool) ReapMaxBytesMaxGas(maxBytes, maxGas int64) types.Txs {
|
||||
return txs
|
||||
}
|
||||
totalBytes += int64(len(memTx.tx)) + aminoOverhead
|
||||
// Check total gas requirement
|
||||
if maxGas > -1 && totalGas+memTx.gasWanted > maxGas {
|
||||
// Check total gas requirement.
|
||||
// If maxGas is negative, skip this check.
|
||||
// Since newTotalGas < masGas, which
|
||||
// must be non-negative, it follows that this won't overflow.
|
||||
newTotalGas := totalGas + memTx.gasWanted
|
||||
if maxGas > -1 && newTotalGas > maxGas {
|
||||
return txs
|
||||
}
|
||||
totalGas += memTx.gasWanted
|
||||
totalGas = newTotalGas
|
||||
txs = append(txs, memTx.tx)
|
||||
}
|
||||
return txs
|
||||
@@ -548,13 +556,18 @@ func (mem *Mempool) Update(
|
||||
// Remove committed transactions.
|
||||
txsLeft := mem.removeTxs(txs)
|
||||
|
||||
// Recheck mempool txs if any txs were committed in the block
|
||||
if mem.config.Recheck && len(txsLeft) > 0 {
|
||||
mem.logger.Info("Recheck txs", "numtxs", len(txsLeft), "height", height)
|
||||
mem.recheckTxs(txsLeft)
|
||||
// At this point, mem.txs are being rechecked.
|
||||
// mem.recheckCursor re-scans mem.txs and possibly removes some txs.
|
||||
// Before mem.Reap(), we should wait for mem.recheckCursor to be nil.
|
||||
// Either recheck non-committed txs to see if they became invalid
|
||||
// or just notify there're some txs left.
|
||||
if len(txsLeft) > 0 {
|
||||
if mem.config.Recheck {
|
||||
mem.logger.Info("Recheck txs", "numtxs", len(txsLeft), "height", height)
|
||||
mem.recheckTxs(txsLeft)
|
||||
// At this point, mem.txs are being rechecked.
|
||||
// mem.recheckCursor re-scans mem.txs and possibly removes some txs.
|
||||
// Before mem.Reap(), we should wait for mem.recheckCursor to be nil.
|
||||
} else {
|
||||
mem.notifyTxsAvailable()
|
||||
}
|
||||
}
|
||||
|
||||
// Update metrics
|
||||
@@ -663,7 +676,7 @@ func (cache *mapTxCache) Push(tx types.Tx) bool {
|
||||
// Use the tx hash in the cache
|
||||
txHash := sha256.Sum256(tx)
|
||||
if moved, exists := cache.map_[txHash]; exists {
|
||||
cache.list.MoveToFront(moved)
|
||||
cache.list.MoveToBack(moved)
|
||||
return false
|
||||
}
|
||||
|
||||
|
103
node/node.go
103
node/node.go
@@ -7,6 +7,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -86,8 +87,26 @@ func DefaultNewNode(config *cfg.Config, logger log.Logger) (*Node, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert old PrivValidator if it exists.
|
||||
oldPrivVal := config.OldPrivValidatorFile()
|
||||
newPrivValKey := config.PrivValidatorKeyFile()
|
||||
newPrivValState := config.PrivValidatorStateFile()
|
||||
if _, err := os.Stat(oldPrivVal); !os.IsNotExist(err) {
|
||||
oldPV, err := privval.LoadOldFilePV(oldPrivVal)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error reading OldPrivValidator from %v: %v\n", oldPrivVal, err)
|
||||
}
|
||||
logger.Info("Upgrading PrivValidator file",
|
||||
"old", oldPrivVal,
|
||||
"newKey", newPrivValKey,
|
||||
"newState", newPrivValState,
|
||||
)
|
||||
oldPV.Upgrade(newPrivValKey, newPrivValState)
|
||||
}
|
||||
|
||||
return NewNode(config,
|
||||
privval.LoadOrGenFilePV(config.PrivValidatorFile()),
|
||||
privval.LoadOrGenFilePV(newPrivValKey, newPrivValState),
|
||||
nodeKey,
|
||||
proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()),
|
||||
DefaultGenesisDocProviderFunc(config),
|
||||
@@ -210,13 +229,18 @@ func NewNode(config *cfg.Config,
|
||||
// what happened during block replay).
|
||||
state = sm.LoadState(stateDB)
|
||||
|
||||
// Ensure the state's block version matches that of the software.
|
||||
// Log the version info.
|
||||
logger.Info("Version info",
|
||||
"software", version.TMCoreSemVer,
|
||||
"block", version.BlockProtocol,
|
||||
"p2p", version.P2PProtocol,
|
||||
)
|
||||
|
||||
// If the state and software differ in block version, at least log it.
|
||||
if state.Version.Consensus.Block != version.BlockProtocol {
|
||||
return nil, fmt.Errorf(
|
||||
"Block version of the software does not match that of the state.\n"+
|
||||
"Got version.BlockProtocol=%v, state.Version.Consensus.Block=%v",
|
||||
version.BlockProtocol,
|
||||
state.Version.Consensus.Block,
|
||||
logger.Info("Software and state have different block protocols",
|
||||
"software", version.BlockProtocol,
|
||||
"state", state.Version.Consensus.Block,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -235,16 +259,19 @@ func NewNode(config *cfg.Config,
|
||||
fastSync := config.FastSync
|
||||
if state.Validators.Size() == 1 {
|
||||
addr, _ := state.Validators.GetByIndex(0)
|
||||
if bytes.Equal(privValidator.GetAddress(), addr) {
|
||||
privValAddr := privValidator.GetPubKey().Address()
|
||||
if bytes.Equal(privValAddr, addr) {
|
||||
fastSync = false
|
||||
}
|
||||
}
|
||||
|
||||
pubKey := privValidator.GetPubKey()
|
||||
addr := pubKey.Address()
|
||||
// Log whether this node is a validator or an observer
|
||||
if state.Validators.HasAddress(privValidator.GetAddress()) {
|
||||
consensusLogger.Info("This node is a validator", "addr", privValidator.GetAddress(), "pubKey", privValidator.GetPubKey())
|
||||
if state.Validators.HasAddress(addr) {
|
||||
consensusLogger.Info("This node is a validator", "addr", addr, "pubKey", pubKey)
|
||||
} else {
|
||||
consensusLogger.Info("This node is not a validator", "addr", privValidator.GetAddress(), "pubKey", privValidator.GetPubKey())
|
||||
consensusLogger.Info("This node is not a validator", "addr", addr, "pubKey", pubKey)
|
||||
}
|
||||
|
||||
csMetrics, p2pMetrics, memplMetrics, smMetrics := metricsProvider()
|
||||
@@ -343,20 +370,21 @@ func NewNode(config *cfg.Config,
|
||||
indexerService := txindex.NewIndexerService(txIndexer, eventBus)
|
||||
indexerService.SetLogger(logger.With("module", "txindex"))
|
||||
|
||||
var (
|
||||
p2pLogger = logger.With("module", "p2p")
|
||||
nodeInfo = makeNodeInfo(
|
||||
config,
|
||||
nodeKey.ID(),
|
||||
txIndexer,
|
||||
genDoc.ChainID,
|
||||
p2p.NewProtocolVersion(
|
||||
version.P2PProtocol, // global
|
||||
state.Version.Consensus.Block,
|
||||
state.Version.Consensus.App,
|
||||
),
|
||||
)
|
||||
p2pLogger := logger.With("module", "p2p")
|
||||
nodeInfo, err := makeNodeInfo(
|
||||
config,
|
||||
nodeKey.ID(),
|
||||
txIndexer,
|
||||
genDoc.ChainID,
|
||||
p2p.NewProtocolVersion(
|
||||
version.P2PProtocol, // global
|
||||
state.Version.Consensus.Block,
|
||||
state.Version.Consensus.App,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Setup Transport.
|
||||
var (
|
||||
@@ -454,7 +482,7 @@ func NewNode(config *cfg.Config,
|
||||
Seeds: splitAndTrimEmpty(config.P2P.Seeds, ",", " "),
|
||||
SeedMode: config.P2P.SeedMode,
|
||||
})
|
||||
pexReactor.SetLogger(p2pLogger)
|
||||
pexReactor.SetLogger(logger.With("module", "pex"))
|
||||
sw.AddReactor("PEX", pexReactor)
|
||||
}
|
||||
|
||||
@@ -611,7 +639,8 @@ func (n *Node) ConfigureRPC() {
|
||||
rpccore.SetEvidencePool(n.evidencePool)
|
||||
rpccore.SetP2PPeers(n.sw)
|
||||
rpccore.SetP2PTransport(n)
|
||||
rpccore.SetPubKey(n.privValidator.GetPubKey())
|
||||
pubKey := n.privValidator.GetPubKey()
|
||||
rpccore.SetPubKey(pubKey)
|
||||
rpccore.SetGenesisDoc(n.genesisDoc)
|
||||
rpccore.SetAddrBook(n.addrBook)
|
||||
rpccore.SetProxyAppQuery(n.proxyApp.Query())
|
||||
@@ -777,7 +806,7 @@ func makeNodeInfo(
|
||||
txIndexer txindex.TxIndexer,
|
||||
chainID string,
|
||||
protocolVersion p2p.ProtocolVersion,
|
||||
) p2p.NodeInfo {
|
||||
) (p2p.NodeInfo, error) {
|
||||
txIndexerStatus := "on"
|
||||
if _, ok := txIndexer.(*null.TxIndex); ok {
|
||||
txIndexerStatus = "off"
|
||||
@@ -812,7 +841,8 @@ func makeNodeInfo(
|
||||
|
||||
nodeInfo.ListenAddr = lAddr
|
||||
|
||||
return nodeInfo
|
||||
err := nodeInfo.Validate()
|
||||
return nodeInfo, err
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -848,16 +878,20 @@ func createAndStartPrivValidatorSocketClient(
|
||||
listenAddr string,
|
||||
logger log.Logger,
|
||||
) (types.PrivValidator, error) {
|
||||
var pvsc types.PrivValidator
|
||||
var listener net.Listener
|
||||
|
||||
protocol, address := cmn.ProtocolAndAddress(listenAddr)
|
||||
ln, err := net.Listen(protocol, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch protocol {
|
||||
case "unix":
|
||||
pvsc = privval.NewIPCVal(logger.With("module", "privval"), address)
|
||||
listener = privval.NewUnixListener(ln)
|
||||
case "tcp":
|
||||
// TODO: persist this key so external signer
|
||||
// can actually authenticate us
|
||||
pvsc = privval.NewTCPVal(logger.With("module", "privval"), listenAddr, ed25519.GenPrivKey())
|
||||
listener = privval.NewTCPListener(ln, ed25519.GenPrivKey())
|
||||
default:
|
||||
return nil, fmt.Errorf(
|
||||
"Wrong listen address: expected either 'tcp' or 'unix' protocols, got %s",
|
||||
@@ -865,10 +899,9 @@ func createAndStartPrivValidatorSocketClient(
|
||||
)
|
||||
}
|
||||
|
||||
if pvsc, ok := pvsc.(cmn.Service); ok {
|
||||
if err := pvsc.Start(); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to start")
|
||||
}
|
||||
pvsc := privval.NewSocketVal(logger.With("module", "privval"), listener)
|
||||
if err := pvsc.Start(); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to start")
|
||||
}
|
||||
|
||||
return pvsc, nil
|
||||
|
@@ -122,25 +122,25 @@ func TestNodeSetPrivValTCP(t *testing.T) {
|
||||
config := cfg.ResetTestRoot("node_priv_val_tcp_test")
|
||||
config.BaseConfig.PrivValidatorListenAddr = addr
|
||||
|
||||
rs := privval.NewRemoteSigner(
|
||||
dialer := privval.DialTCPFn(addr, 100*time.Millisecond, ed25519.GenPrivKey())
|
||||
pvsc := privval.NewRemoteSigner(
|
||||
log.TestingLogger(),
|
||||
config.ChainID(),
|
||||
addr,
|
||||
types.NewMockPV(),
|
||||
ed25519.GenPrivKey(),
|
||||
dialer,
|
||||
)
|
||||
privval.RemoteSignerConnDeadline(5 * time.Millisecond)(rs)
|
||||
|
||||
go func() {
|
||||
err := rs.Start()
|
||||
err := pvsc.Start()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
defer rs.Stop()
|
||||
defer pvsc.Stop()
|
||||
|
||||
n, err := DefaultNewNode(config, log.TestingLogger())
|
||||
require.NoError(t, err)
|
||||
assert.IsType(t, &privval.TCPVal{}, n.PrivValidator())
|
||||
assert.IsType(t, &privval.SocketVal{}, n.PrivValidator())
|
||||
}
|
||||
|
||||
// address without a protocol must result in error
|
||||
@@ -161,25 +161,25 @@ func TestNodeSetPrivValIPC(t *testing.T) {
|
||||
config := cfg.ResetTestRoot("node_priv_val_tcp_test")
|
||||
config.BaseConfig.PrivValidatorListenAddr = "unix://" + tmpfile
|
||||
|
||||
rs := privval.NewIPCRemoteSigner(
|
||||
dialer := privval.DialUnixFn(tmpfile)
|
||||
pvsc := privval.NewRemoteSigner(
|
||||
log.TestingLogger(),
|
||||
config.ChainID(),
|
||||
tmpfile,
|
||||
types.NewMockPV(),
|
||||
dialer,
|
||||
)
|
||||
privval.IPCRemoteSignerConnDeadline(3 * time.Second)(rs)
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
n, err := DefaultNewNode(config, log.TestingLogger())
|
||||
require.NoError(t, err)
|
||||
assert.IsType(t, &privval.IPCVal{}, n.PrivValidator())
|
||||
assert.IsType(t, &privval.SocketVal{}, n.PrivValidator())
|
||||
}()
|
||||
|
||||
err := rs.Start()
|
||||
err := pvsc.Start()
|
||||
require.NoError(t, err)
|
||||
defer rs.Stop()
|
||||
defer pvsc.Stop()
|
||||
|
||||
<-done
|
||||
}
|
||||
|
@@ -4,8 +4,8 @@ The p2p package provides an abstraction around peer-to-peer communication.
|
||||
|
||||
Docs:
|
||||
|
||||
- [Connection](https://github.com/tendermint/tendermint/blob/master/docs/spec/docs/spec/p2p/connection.md) for details on how connections and multiplexing work
|
||||
- [Peer](https://github.com/tendermint/tendermint/blob/master/docs/spec/docs/spec/p2p/peer.md) for details on peer ID, handshakes, and peer exchange
|
||||
- [Node](https://github.com/tendermint/tendermint/blob/master/docs/spec/docs/spec/p2p/node.md) for details about different types of nodes and how they should work
|
||||
- [Pex](https://github.com/tendermint/tendermint/blob/master/docs/spec/docs/spec/reactors/pex/pex.md) for details on peer discovery and exchange
|
||||
- [Config](https://github.com/tendermint/tendermint/blob/master/docs/spec/docs/spec/p2p/config.md) for details on some config option
|
||||
- [Connection](https://github.com/tendermint/tendermint/blob/master/docs/spec/p2p/connection.md) for details on how connections and multiplexing work
|
||||
- [Peer](https://github.com/tendermint/tendermint/blob/master/docs/spec/p2p/peer.md) for details on peer ID, handshakes, and peer exchange
|
||||
- [Node](https://github.com/tendermint/tendermint/blob/master/docs/spec/p2p/node.md) for details about different types of nodes and how they should work
|
||||
- [Pex](https://github.com/tendermint/tendermint/blob/master/docs/spec/reactors/pex/pex.md) for details on peer discovery and exchange
|
||||
- [Config](https://github.com/tendermint/tendermint/blob/master/docs/spec/p2p/config.md) for details on some config option
|
||||
|
@@ -160,6 +160,7 @@ func NewMConnectionWithConfig(conn net.Conn, chDescs []*ChannelDescriptor, onRec
|
||||
onReceive: onReceive,
|
||||
onError: onError,
|
||||
config: config,
|
||||
created: time.Now(),
|
||||
}
|
||||
|
||||
// Create channels
|
||||
|
@@ -8,9 +8,9 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
// forked to github.com/tendermint/crypto
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
@@ -28,20 +28,36 @@ const aeadSizeOverhead = 16 // overhead of poly 1305 authentication tag
|
||||
const aeadKeySize = chacha20poly1305.KeySize
|
||||
const aeadNonceSize = chacha20poly1305.NonceSize
|
||||
|
||||
// SecretConnection implements net.conn.
|
||||
// SecretConnection implements net.Conn.
|
||||
// It is an implementation of the STS protocol.
|
||||
// Note we do not (yet) assume that a remote peer's pubkey
|
||||
// is known ahead of time, and thus we are technically
|
||||
// still vulnerable to MITM. (TODO!)
|
||||
// See docs/sts-final.pdf for more info
|
||||
// See https://github.com/tendermint/tendermint/blob/0.1/docs/sts-final.pdf for
|
||||
// details on the protocol.
|
||||
//
|
||||
// Consumers of the SecretConnection are responsible for authenticating
|
||||
// the remote peer's pubkey against known information, like a nodeID.
|
||||
// Otherwise they are vulnerable to MITM.
|
||||
// (TODO(ismail): see also https://github.com/tendermint/tendermint/issues/3010)
|
||||
type SecretConnection struct {
|
||||
conn io.ReadWriteCloser
|
||||
recvBuffer []byte
|
||||
recvNonce *[aeadNonceSize]byte
|
||||
sendNonce *[aeadNonceSize]byte
|
||||
|
||||
// immutable
|
||||
recvSecret *[aeadKeySize]byte
|
||||
sendSecret *[aeadKeySize]byte
|
||||
remPubKey crypto.PubKey
|
||||
conn io.ReadWriteCloser
|
||||
|
||||
// net.Conn must be thread safe:
|
||||
// https://golang.org/pkg/net/#Conn.
|
||||
// Since we have internal mutable state,
|
||||
// we need mtxs. But recv and send states
|
||||
// are independent, so we can use two mtxs.
|
||||
// All .Read are covered by recvMtx,
|
||||
// all .Write are covered by sendMtx.
|
||||
recvMtx sync.Mutex
|
||||
recvBuffer []byte
|
||||
recvNonce *[aeadNonceSize]byte
|
||||
|
||||
sendMtx sync.Mutex
|
||||
sendNonce *[aeadNonceSize]byte
|
||||
}
|
||||
|
||||
// MakeSecretConnection performs handshake and returns a new authenticated
|
||||
@@ -110,9 +126,12 @@ func (sc *SecretConnection) RemotePubKey() crypto.PubKey {
|
||||
return sc.remPubKey
|
||||
}
|
||||
|
||||
// Writes encrypted frames of `sealedFrameSize`
|
||||
// CONTRACT: data smaller than dataMaxSize is read atomically.
|
||||
// Writes encrypted frames of `totalFrameSize + aeadSizeOverhead`.
|
||||
// CONTRACT: data smaller than dataMaxSize is written atomically.
|
||||
func (sc *SecretConnection) Write(data []byte) (n int, err error) {
|
||||
sc.sendMtx.Lock()
|
||||
defer sc.sendMtx.Unlock()
|
||||
|
||||
for 0 < len(data) {
|
||||
var frame = make([]byte, totalFrameSize)
|
||||
var chunk []byte
|
||||
@@ -131,6 +150,7 @@ func (sc *SecretConnection) Write(data []byte) (n int, err error) {
|
||||
if err != nil {
|
||||
return n, errors.New("Invalid SecretConnection Key")
|
||||
}
|
||||
|
||||
// encrypt the frame
|
||||
var sealedFrame = make([]byte, aeadSizeOverhead+totalFrameSize)
|
||||
aead.Seal(sealedFrame[:0], sc.sendNonce[:], frame, nil)
|
||||
@@ -148,23 +168,30 @@ func (sc *SecretConnection) Write(data []byte) (n int, err error) {
|
||||
|
||||
// CONTRACT: data smaller than dataMaxSize is read atomically.
|
||||
func (sc *SecretConnection) Read(data []byte) (n int, err error) {
|
||||
sc.recvMtx.Lock()
|
||||
defer sc.recvMtx.Unlock()
|
||||
|
||||
// read off and update the recvBuffer, if non-empty
|
||||
if 0 < len(sc.recvBuffer) {
|
||||
n = copy(data, sc.recvBuffer)
|
||||
sc.recvBuffer = sc.recvBuffer[n:]
|
||||
return
|
||||
}
|
||||
|
||||
// read off the conn
|
||||
sealedFrame := make([]byte, totalFrameSize+aeadSizeOverhead)
|
||||
_, err = io.ReadFull(sc.conn, sealedFrame)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
aead, err := chacha20poly1305.New(sc.recvSecret[:])
|
||||
if err != nil {
|
||||
return n, errors.New("Invalid SecretConnection Key")
|
||||
}
|
||||
sealedFrame := make([]byte, totalFrameSize+aeadSizeOverhead)
|
||||
_, err = io.ReadFull(sc.conn, sealedFrame)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// decrypt the frame
|
||||
// decrypt the frame.
|
||||
// reads and updates the sc.recvNonce
|
||||
var frame = make([]byte, totalFrameSize)
|
||||
_, err = aead.Open(frame[:0], sc.recvNonce[:], sealedFrame, nil)
|
||||
if err != nil {
|
||||
@@ -173,12 +200,13 @@ func (sc *SecretConnection) Read(data []byte) (n int, err error) {
|
||||
incrNonce(sc.recvNonce)
|
||||
// end decryption
|
||||
|
||||
// copy checkLength worth into data,
|
||||
// set recvBuffer to the rest.
|
||||
var chunkLength = binary.LittleEndian.Uint32(frame) // read the first four bytes
|
||||
if chunkLength > dataMaxSize {
|
||||
return 0, errors.New("chunkLength is greater than dataMaxSize")
|
||||
}
|
||||
var chunk = frame[dataLenSize : dataLenSize+chunkLength]
|
||||
|
||||
n = copy(data, chunk)
|
||||
sc.recvBuffer = chunk[n:]
|
||||
return
|
||||
|
@@ -7,10 +7,12 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -98,6 +100,69 @@ func TestSecretConnectionHandshake(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcurrentWrite(t *testing.T) {
|
||||
fooSecConn, barSecConn := makeSecretConnPair(t)
|
||||
fooWriteText := cmn.RandStr(dataMaxSize)
|
||||
|
||||
// write from two routines.
|
||||
// should be safe from race according to net.Conn:
|
||||
// https://golang.org/pkg/net/#Conn
|
||||
n := 100
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(3)
|
||||
go writeLots(t, wg, fooSecConn, fooWriteText, n)
|
||||
go writeLots(t, wg, fooSecConn, fooWriteText, n)
|
||||
|
||||
// Consume reads from bar's reader
|
||||
readLots(t, wg, barSecConn, n*2)
|
||||
wg.Wait()
|
||||
|
||||
if err := fooSecConn.Close(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcurrentRead(t *testing.T) {
|
||||
fooSecConn, barSecConn := makeSecretConnPair(t)
|
||||
fooWriteText := cmn.RandStr(dataMaxSize)
|
||||
n := 100
|
||||
|
||||
// read from two routines.
|
||||
// should be safe from race according to net.Conn:
|
||||
// https://golang.org/pkg/net/#Conn
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(3)
|
||||
go readLots(t, wg, fooSecConn, n/2)
|
||||
go readLots(t, wg, fooSecConn, n/2)
|
||||
|
||||
// write to bar
|
||||
writeLots(t, wg, barSecConn, fooWriteText, n)
|
||||
wg.Wait()
|
||||
|
||||
if err := fooSecConn.Close(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func writeLots(t *testing.T, wg *sync.WaitGroup, conn net.Conn, txt string, n int) {
|
||||
defer wg.Done()
|
||||
for i := 0; i < n; i++ {
|
||||
_, err := conn.Write([]byte(txt))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to write to fooSecConn: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readLots(t *testing.T, wg *sync.WaitGroup, conn net.Conn, n int) {
|
||||
readBuffer := make([]byte, dataMaxSize)
|
||||
for i := 0; i < n; i++ {
|
||||
_, err := conn.Read(readBuffer)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func TestSecretConnectionReadWrite(t *testing.T) {
|
||||
fooConn, barConn := makeKVStoreConnPair()
|
||||
fooWrites, barWrites := []string{}, []string{}
|
||||
|
@@ -175,6 +175,9 @@ func (na *NetAddress) Same(other interface{}) bool {
|
||||
|
||||
// String representation: <ID>@<IP>:<PORT>
|
||||
func (na *NetAddress) String() string {
|
||||
if na == nil {
|
||||
return "<nil-NetAddress>"
|
||||
}
|
||||
if na.str == "" {
|
||||
addrStr := na.DialString()
|
||||
if na.ID != "" {
|
||||
@@ -186,6 +189,9 @@ func (na *NetAddress) String() string {
|
||||
}
|
||||
|
||||
func (na *NetAddress) DialString() string {
|
||||
if na == nil {
|
||||
return "<nil-NetAddress>"
|
||||
}
|
||||
return net.JoinHostPort(
|
||||
na.IP.String(),
|
||||
strconv.FormatUint(uint64(na.Port), 10),
|
||||
|
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
maxNodeInfoSize = 10240 // 10Kb
|
||||
maxNodeInfoSize = 10240 // 10KB
|
||||
maxNumChannels = 16 // plenty of room for upgrades, for now
|
||||
)
|
||||
|
||||
@@ -36,7 +36,7 @@ type nodeInfoAddress interface {
|
||||
// nodeInfoTransport validates a nodeInfo and checks
|
||||
// our compatibility with it. It's for use in the handshake.
|
||||
type nodeInfoTransport interface {
|
||||
ValidateBasic() error
|
||||
Validate() error
|
||||
CompatibleWith(other NodeInfo) error
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ func (info DefaultNodeInfo) ID() ID {
|
||||
return info.ID_
|
||||
}
|
||||
|
||||
// ValidateBasic checks the self-reported DefaultNodeInfo is safe.
|
||||
// Validate checks the self-reported DefaultNodeInfo is safe.
|
||||
// It returns an error if there
|
||||
// are too many Channels, if there are any duplicate Channels,
|
||||
// if the ListenAddr is malformed, or if the ListenAddr is a host name
|
||||
@@ -116,7 +116,7 @@ func (info DefaultNodeInfo) ID() ID {
|
||||
// International clients could then use punycode (or we could use
|
||||
// url-encoding), and we just need to be careful with how we handle that in our
|
||||
// clients. (e.g. off by default).
|
||||
func (info DefaultNodeInfo) ValidateBasic() error {
|
||||
func (info DefaultNodeInfo) Validate() error {
|
||||
|
||||
// ID is already validated.
|
||||
|
||||
|
@@ -12,7 +12,7 @@ func TestNodeInfoValidate(t *testing.T) {
|
||||
|
||||
// empty fails
|
||||
ni := DefaultNodeInfo{}
|
||||
assert.Error(t, ni.ValidateBasic())
|
||||
assert.Error(t, ni.Validate())
|
||||
|
||||
channels := make([]byte, maxNumChannels)
|
||||
for i := 0; i < maxNumChannels; i++ {
|
||||
@@ -68,13 +68,13 @@ func TestNodeInfoValidate(t *testing.T) {
|
||||
// test case passes
|
||||
ni = testNodeInfo(nodeKey.ID(), name).(DefaultNodeInfo)
|
||||
ni.Channels = channels
|
||||
assert.NoError(t, ni.ValidateBasic())
|
||||
assert.NoError(t, ni.Validate())
|
||||
|
||||
for _, tc := range testCases {
|
||||
ni := testNodeInfo(nodeKey.ID(), name).(DefaultNodeInfo)
|
||||
ni.Channels = channels
|
||||
tc.malleateNodeInfo(&ni)
|
||||
err := ni.ValidateBasic()
|
||||
err := ni.Validate()
|
||||
if tc.expectErr {
|
||||
assert.Error(t, err, tc.testName)
|
||||
} else {
|
||||
|
@@ -98,13 +98,15 @@ func (ps *PeerSet) Get(peerKey ID) Peer {
|
||||
}
|
||||
|
||||
// Remove discards peer by its Key, if the peer was previously memoized.
|
||||
func (ps *PeerSet) Remove(peer Peer) {
|
||||
// Returns true if the peer was removed, and false if it was not found.
|
||||
// in the set.
|
||||
func (ps *PeerSet) Remove(peer Peer) bool {
|
||||
ps.mtx.Lock()
|
||||
defer ps.mtx.Unlock()
|
||||
|
||||
item := ps.lookup[peer.ID()]
|
||||
if item == nil {
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
index := item.index
|
||||
@@ -116,7 +118,7 @@ func (ps *PeerSet) Remove(peer Peer) {
|
||||
if index == len(ps.list)-1 {
|
||||
ps.list = newList
|
||||
delete(ps.lookup, peer.ID())
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
// Replace the popped item with the last item in the old list.
|
||||
@@ -127,6 +129,7 @@ func (ps *PeerSet) Remove(peer Peer) {
|
||||
lastPeerItem.index = index
|
||||
ps.list = newList
|
||||
delete(ps.lookup, peer.ID())
|
||||
return true
|
||||
}
|
||||
|
||||
// Size returns the number of unique items in the peerSet.
|
||||
|
@@ -60,13 +60,15 @@ func TestPeerSetAddRemoveOne(t *testing.T) {
|
||||
n := len(peerList)
|
||||
// 1. Test removing from the front
|
||||
for i, peerAtFront := range peerList {
|
||||
peerSet.Remove(peerAtFront)
|
||||
removed := peerSet.Remove(peerAtFront)
|
||||
assert.True(t, removed)
|
||||
wantSize := n - i - 1
|
||||
for j := 0; j < 2; j++ {
|
||||
assert.Equal(t, false, peerSet.Has(peerAtFront.ID()), "#%d Run #%d: failed to remove peer", i, j)
|
||||
assert.Equal(t, wantSize, peerSet.Size(), "#%d Run #%d: failed to remove peer and decrement size", i, j)
|
||||
// Test the route of removing the now non-existent element
|
||||
peerSet.Remove(peerAtFront)
|
||||
removed := peerSet.Remove(peerAtFront)
|
||||
assert.False(t, removed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +83,8 @@ func TestPeerSetAddRemoveOne(t *testing.T) {
|
||||
// b) In reverse, remove each element
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
peerAtEnd := peerList[i]
|
||||
peerSet.Remove(peerAtEnd)
|
||||
removed := peerSet.Remove(peerAtEnd)
|
||||
assert.True(t, removed)
|
||||
assert.Equal(t, false, peerSet.Has(peerAtEnd.ID()), "#%d: failed to remove item at end", i)
|
||||
assert.Equal(t, i, peerSet.Size(), "#%d: differing sizes after peerSet.Remove(atEndPeer)", i)
|
||||
}
|
||||
@@ -105,7 +108,8 @@ func TestPeerSetAddRemoveMany(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, peer := range peers {
|
||||
peerSet.Remove(peer)
|
||||
removed := peerSet.Remove(peer)
|
||||
assert.True(t, removed)
|
||||
if peerSet.Has(peer.ID()) {
|
||||
t.Errorf("Failed to remove peer")
|
||||
}
|
||||
|
@@ -162,26 +162,29 @@ func (a *addrBook) FilePath() string {
|
||||
|
||||
// AddOurAddress one of our addresses.
|
||||
func (a *addrBook) AddOurAddress(addr *p2p.NetAddress) {
|
||||
a.Logger.Info("Add our address to book", "addr", addr)
|
||||
a.mtx.Lock()
|
||||
defer a.mtx.Unlock()
|
||||
|
||||
a.Logger.Info("Add our address to book", "addr", addr)
|
||||
a.ourAddrs[addr.String()] = struct{}{}
|
||||
a.mtx.Unlock()
|
||||
}
|
||||
|
||||
// OurAddress returns true if it is our address.
|
||||
func (a *addrBook) OurAddress(addr *p2p.NetAddress) bool {
|
||||
a.mtx.Lock()
|
||||
defer a.mtx.Unlock()
|
||||
|
||||
_, ok := a.ourAddrs[addr.String()]
|
||||
a.mtx.Unlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
func (a *addrBook) AddPrivateIDs(IDs []string) {
|
||||
a.mtx.Lock()
|
||||
defer a.mtx.Unlock()
|
||||
|
||||
for _, id := range IDs {
|
||||
a.privateIDs[p2p.ID(id)] = struct{}{}
|
||||
}
|
||||
a.mtx.Unlock()
|
||||
}
|
||||
|
||||
// AddAddress implements AddrBook
|
||||
@@ -191,6 +194,7 @@ func (a *addrBook) AddPrivateIDs(IDs []string) {
|
||||
func (a *addrBook) AddAddress(addr *p2p.NetAddress, src *p2p.NetAddress) error {
|
||||
a.mtx.Lock()
|
||||
defer a.mtx.Unlock()
|
||||
|
||||
return a.addAddress(addr, src)
|
||||
}
|
||||
|
||||
@@ -198,6 +202,7 @@ func (a *addrBook) AddAddress(addr *p2p.NetAddress, src *p2p.NetAddress) error {
|
||||
func (a *addrBook) RemoveAddress(addr *p2p.NetAddress) {
|
||||
a.mtx.Lock()
|
||||
defer a.mtx.Unlock()
|
||||
|
||||
ka := a.addrLookup[addr.ID]
|
||||
if ka == nil {
|
||||
return
|
||||
@@ -211,14 +216,16 @@ func (a *addrBook) RemoveAddress(addr *p2p.NetAddress) {
|
||||
func (a *addrBook) IsGood(addr *p2p.NetAddress) bool {
|
||||
a.mtx.Lock()
|
||||
defer a.mtx.Unlock()
|
||||
|
||||
return a.addrLookup[addr.ID].isOld()
|
||||
}
|
||||
|
||||
// HasAddress returns true if the address is in the book.
|
||||
func (a *addrBook) HasAddress(addr *p2p.NetAddress) bool {
|
||||
a.mtx.Lock()
|
||||
defer a.mtx.Unlock()
|
||||
|
||||
ka := a.addrLookup[addr.ID]
|
||||
a.mtx.Unlock()
|
||||
return ka != nil
|
||||
}
|
||||
|
||||
@@ -292,6 +299,7 @@ func (a *addrBook) PickAddress(biasTowardsNewAddrs int) *p2p.NetAddress {
|
||||
func (a *addrBook) MarkGood(addr *p2p.NetAddress) {
|
||||
a.mtx.Lock()
|
||||
defer a.mtx.Unlock()
|
||||
|
||||
ka := a.addrLookup[addr.ID]
|
||||
if ka == nil {
|
||||
return
|
||||
@@ -306,6 +314,7 @@ func (a *addrBook) MarkGood(addr *p2p.NetAddress) {
|
||||
func (a *addrBook) MarkAttempt(addr *p2p.NetAddress) {
|
||||
a.mtx.Lock()
|
||||
defer a.mtx.Unlock()
|
||||
|
||||
ka := a.addrLookup[addr.ID]
|
||||
if ka == nil {
|
||||
return
|
||||
@@ -461,12 +470,13 @@ ADDRS_LOOP:
|
||||
|
||||
// ListOfKnownAddresses returns the new and old addresses.
|
||||
func (a *addrBook) ListOfKnownAddresses() []*knownAddress {
|
||||
addrs := []*knownAddress{}
|
||||
a.mtx.Lock()
|
||||
defer a.mtx.Unlock()
|
||||
|
||||
addrs := []*knownAddress{}
|
||||
for _, addr := range a.addrLookup {
|
||||
addrs = append(addrs, addr.copy())
|
||||
}
|
||||
a.mtx.Unlock()
|
||||
return addrs
|
||||
}
|
||||
|
||||
@@ -476,6 +486,7 @@ func (a *addrBook) ListOfKnownAddresses() []*knownAddress {
|
||||
func (a *addrBook) Size() int {
|
||||
a.mtx.Lock()
|
||||
defer a.mtx.Unlock()
|
||||
|
||||
return a.size()
|
||||
}
|
||||
|
||||
|
@@ -211,7 +211,9 @@ func (sw *Switch) OnStop() {
|
||||
// Stop peers
|
||||
for _, p := range sw.peers.List() {
|
||||
p.Stop()
|
||||
sw.peers.Remove(p)
|
||||
if sw.peers.Remove(p) {
|
||||
sw.metrics.Peers.Add(float64(-1))
|
||||
}
|
||||
}
|
||||
|
||||
// Stop reactors
|
||||
@@ -299,8 +301,9 @@ func (sw *Switch) StopPeerGracefully(peer Peer) {
|
||||
}
|
||||
|
||||
func (sw *Switch) stopAndRemovePeer(peer Peer, reason interface{}) {
|
||||
sw.peers.Remove(peer)
|
||||
sw.metrics.Peers.Add(float64(-1))
|
||||
if sw.peers.Remove(peer) {
|
||||
sw.metrics.Peers.Add(float64(-1))
|
||||
}
|
||||
peer.Stop()
|
||||
for _, reactor := range sw.reactors {
|
||||
reactor.RemovePeer(peer, reason)
|
||||
@@ -505,6 +508,12 @@ func (sw *Switch) acceptRoutine() {
|
||||
"err", err,
|
||||
"numPeers", sw.peers.Size(),
|
||||
)
|
||||
// We could instead have a retry loop around the acceptRoutine,
|
||||
// but that would need to stop and let the node shutdown eventually.
|
||||
// So might as well panic and let process managers restart the node.
|
||||
// There's no point in letting the node run without the acceptRoutine,
|
||||
// since it won't be able to accept new connections.
|
||||
panic(fmt.Errorf("accept routine exited: %v", err))
|
||||
}
|
||||
|
||||
break
|
||||
|
@@ -3,10 +3,17 @@ package p2p
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -335,6 +342,54 @@ func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) {
|
||||
assert.False(p.IsRunning())
|
||||
}
|
||||
|
||||
func TestSwitchStopPeerForError(t *testing.T) {
|
||||
s := httptest.NewServer(stdprometheus.UninstrumentedHandler())
|
||||
defer s.Close()
|
||||
|
||||
scrapeMetrics := func() string {
|
||||
resp, _ := http.Get(s.URL)
|
||||
buf, _ := ioutil.ReadAll(resp.Body)
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
namespace, subsystem, name := config.TestInstrumentationConfig().Namespace, MetricsSubsystem, "peers"
|
||||
re := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + ` ([0-9\.]+)`)
|
||||
peersMetricValue := func() float64 {
|
||||
matches := re.FindStringSubmatch(scrapeMetrics())
|
||||
f, _ := strconv.ParseFloat(matches[1], 64)
|
||||
return f
|
||||
}
|
||||
|
||||
p2pMetrics := PrometheusMetrics(namespace)
|
||||
|
||||
// make two connected switches
|
||||
sw1, sw2 := MakeSwitchPair(t, func(i int, sw *Switch) *Switch {
|
||||
// set metrics on sw1
|
||||
if i == 0 {
|
||||
opt := WithMetrics(p2pMetrics)
|
||||
opt(sw)
|
||||
}
|
||||
return initSwitchFunc(i, sw)
|
||||
})
|
||||
|
||||
assert.Equal(t, len(sw1.Peers().List()), 1)
|
||||
assert.EqualValues(t, 1, peersMetricValue())
|
||||
|
||||
// send messages to the peer from sw1
|
||||
p := sw1.Peers().List()[0]
|
||||
p.Send(0x1, []byte("here's a message to send"))
|
||||
|
||||
// stop sw2. this should cause the p to fail,
|
||||
// which results in calling StopPeerForError internally
|
||||
sw2.Stop()
|
||||
|
||||
// now call StopPeerForError explicitly, eg. from a reactor
|
||||
sw1.StopPeerForError(p, fmt.Errorf("some err"))
|
||||
|
||||
assert.Equal(t, len(sw1.Peers().List()), 0)
|
||||
assert.EqualValues(t, 0, peersMetricValue())
|
||||
}
|
||||
|
||||
func TestSwitchReconnectsToPersistentPeer(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
|
@@ -24,7 +24,7 @@ type mockNodeInfo struct {
|
||||
|
||||
func (ni mockNodeInfo) ID() ID { return ni.addr.ID }
|
||||
func (ni mockNodeInfo) NetAddress() *NetAddress { return ni.addr }
|
||||
func (ni mockNodeInfo) ValidateBasic() error { return nil }
|
||||
func (ni mockNodeInfo) Validate() error { return nil }
|
||||
func (ni mockNodeInfo) CompatibleWith(other NodeInfo) error { return nil }
|
||||
|
||||
func AddPeerToSwitch(sw *Switch, peer Peer) {
|
||||
@@ -184,7 +184,7 @@ func MakeSwitch(
|
||||
|
||||
// TODO: let the config be passed in?
|
||||
sw := initSwitch(i, NewSwitch(cfg, t, opts...))
|
||||
sw.SetLogger(log.TestingLogger())
|
||||
sw.SetLogger(log.TestingLogger().With("switch", i))
|
||||
sw.SetNodeKey(&nodeKey)
|
||||
|
||||
ni := nodeInfo.(DefaultNodeInfo)
|
||||
|
@@ -350,7 +350,7 @@ func (mt *MultiplexTransport) upgrade(
|
||||
}
|
||||
}
|
||||
|
||||
if err := nodeInfo.ValidateBasic(); err != nil {
|
||||
if err := nodeInfo.Validate(); err != nil {
|
||||
return nil, nil, ErrRejected{
|
||||
conn: c,
|
||||
err: err,
|
||||
|
263
privval/client.go
Normal file
263
privval/client.go
Normal file
@@ -0,0 +1,263 @@
|
||||
package privval
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultConnHeartBeatSeconds = 2
|
||||
defaultDialRetries = 10
|
||||
)
|
||||
|
||||
// Socket errors.
|
||||
var (
|
||||
ErrUnexpectedResponse = errors.New("received unexpected response")
|
||||
)
|
||||
|
||||
var (
|
||||
connHeartbeat = time.Second * defaultConnHeartBeatSeconds
|
||||
)
|
||||
|
||||
// SocketValOption sets an optional parameter on the SocketVal.
|
||||
type SocketValOption func(*SocketVal)
|
||||
|
||||
// SocketValHeartbeat sets the period on which to check the liveness of the
|
||||
// connected Signer connections.
|
||||
func SocketValHeartbeat(period time.Duration) SocketValOption {
|
||||
return func(sc *SocketVal) { sc.connHeartbeat = period }
|
||||
}
|
||||
|
||||
// SocketVal implements PrivValidator.
|
||||
// It listens for an external process to dial in and uses
|
||||
// the socket to request signatures.
|
||||
type SocketVal struct {
|
||||
cmn.BaseService
|
||||
|
||||
listener net.Listener
|
||||
|
||||
// ping
|
||||
cancelPing chan struct{}
|
||||
pingTicker *time.Ticker
|
||||
connHeartbeat time.Duration
|
||||
|
||||
// signer is mutable since it can be
|
||||
// reset if the connection fails.
|
||||
// failures are detected by a background
|
||||
// ping routine.
|
||||
// Methods on the underlying net.Conn itself
|
||||
// are already gorountine safe.
|
||||
mtx sync.RWMutex
|
||||
signer *RemoteSignerClient
|
||||
}
|
||||
|
||||
// Check that SocketVal implements PrivValidator.
|
||||
var _ types.PrivValidator = (*SocketVal)(nil)
|
||||
|
||||
// NewSocketVal returns an instance of SocketVal.
|
||||
func NewSocketVal(
|
||||
logger log.Logger,
|
||||
listener net.Listener,
|
||||
) *SocketVal {
|
||||
sc := &SocketVal{
|
||||
listener: listener,
|
||||
connHeartbeat: connHeartbeat,
|
||||
}
|
||||
|
||||
sc.BaseService = *cmn.NewBaseService(logger, "SocketVal", sc)
|
||||
|
||||
return sc
|
||||
}
|
||||
|
||||
//--------------------------------------------------------
|
||||
// Implement PrivValidator
|
||||
|
||||
// GetPubKey implements PrivValidator.
|
||||
func (sc *SocketVal) GetPubKey() crypto.PubKey {
|
||||
sc.mtx.RLock()
|
||||
defer sc.mtx.RUnlock()
|
||||
return sc.signer.GetPubKey()
|
||||
}
|
||||
|
||||
// SignVote implements PrivValidator.
|
||||
func (sc *SocketVal) SignVote(chainID string, vote *types.Vote) error {
|
||||
sc.mtx.RLock()
|
||||
defer sc.mtx.RUnlock()
|
||||
return sc.signer.SignVote(chainID, vote)
|
||||
}
|
||||
|
||||
// SignProposal implements PrivValidator.
|
||||
func (sc *SocketVal) SignProposal(chainID string, proposal *types.Proposal) error {
|
||||
sc.mtx.RLock()
|
||||
defer sc.mtx.RUnlock()
|
||||
return sc.signer.SignProposal(chainID, proposal)
|
||||
}
|
||||
|
||||
//--------------------------------------------------------
|
||||
// More thread safe methods proxied to the signer
|
||||
|
||||
// Ping is used to check connection health.
|
||||
func (sc *SocketVal) Ping() error {
|
||||
sc.mtx.RLock()
|
||||
defer sc.mtx.RUnlock()
|
||||
return sc.signer.Ping()
|
||||
}
|
||||
|
||||
// Close closes the underlying net.Conn.
|
||||
func (sc *SocketVal) Close() {
|
||||
sc.mtx.RLock()
|
||||
defer sc.mtx.RUnlock()
|
||||
if sc.signer != nil {
|
||||
if err := sc.signer.Close(); err != nil {
|
||||
sc.Logger.Error("OnStop", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
if sc.listener != nil {
|
||||
if err := sc.listener.Close(); err != nil {
|
||||
sc.Logger.Error("OnStop", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------
|
||||
// Service start and stop
|
||||
|
||||
// OnStart implements cmn.Service.
|
||||
func (sc *SocketVal) OnStart() error {
|
||||
if closed, err := sc.reset(); err != nil {
|
||||
sc.Logger.Error("OnStart", "err", err)
|
||||
return err
|
||||
} else if closed {
|
||||
return fmt.Errorf("listener is closed")
|
||||
}
|
||||
|
||||
// Start a routine to keep the connection alive
|
||||
sc.cancelPing = make(chan struct{}, 1)
|
||||
sc.pingTicker = time.NewTicker(sc.connHeartbeat)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-sc.pingTicker.C:
|
||||
err := sc.Ping()
|
||||
if err != nil {
|
||||
sc.Logger.Error("Ping", "err", err)
|
||||
if err == ErrUnexpectedResponse {
|
||||
return
|
||||
}
|
||||
|
||||
closed, err := sc.reset()
|
||||
if err != nil {
|
||||
sc.Logger.Error("Reconnecting to remote signer failed", "err", err)
|
||||
continue
|
||||
}
|
||||
if closed {
|
||||
sc.Logger.Info("listener is closing")
|
||||
return
|
||||
}
|
||||
|
||||
sc.Logger.Info("Re-created connection to remote signer", "impl", sc)
|
||||
}
|
||||
case <-sc.cancelPing:
|
||||
sc.pingTicker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnStop implements cmn.Service.
|
||||
func (sc *SocketVal) OnStop() {
|
||||
if sc.cancelPing != nil {
|
||||
close(sc.cancelPing)
|
||||
}
|
||||
sc.Close()
|
||||
}
|
||||
|
||||
//--------------------------------------------------------
|
||||
// Connection and signer management
|
||||
|
||||
// waits to accept and sets a new connection.
|
||||
// connection is closed in OnStop.
|
||||
// returns true if the listener is closed
|
||||
// (ie. it returns a nil conn).
|
||||
func (sc *SocketVal) reset() (bool, error) {
|
||||
sc.mtx.Lock()
|
||||
defer sc.mtx.Unlock()
|
||||
|
||||
// first check if the conn already exists and close it.
|
||||
if sc.signer != nil {
|
||||
if err := sc.signer.Close(); err != nil {
|
||||
sc.Logger.Error("error closing connection", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// wait for a new conn
|
||||
conn, err := sc.waitConnection()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// listener is closed
|
||||
if conn == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
sc.signer, err = NewRemoteSignerClient(conn)
|
||||
if err != nil {
|
||||
// failed to fetch the pubkey. close out the connection.
|
||||
if err := conn.Close(); err != nil {
|
||||
sc.Logger.Error("error closing connection", "err", err)
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (sc *SocketVal) acceptConnection() (net.Conn, error) {
|
||||
conn, err := sc.listener.Accept()
|
||||
if err != nil {
|
||||
if !sc.IsRunning() {
|
||||
return nil, nil // Ignore error from listener closing.
|
||||
}
|
||||
return nil, err
|
||||
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// waitConnection uses the configured wait timeout to error if no external
|
||||
// process connects in the time period.
|
||||
func (sc *SocketVal) waitConnection() (net.Conn, error) {
|
||||
var (
|
||||
connc = make(chan net.Conn, 1)
|
||||
errc = make(chan error, 1)
|
||||
)
|
||||
|
||||
go func(connc chan<- net.Conn, errc chan<- error) {
|
||||
conn, err := sc.acceptConnection()
|
||||
if err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
|
||||
connc <- conn
|
||||
}(connc, errc)
|
||||
|
||||
select {
|
||||
case conn := <-connc:
|
||||
return conn, nil
|
||||
case err := <-errc:
|
||||
return nil, err
|
||||
}
|
||||
}
|
@@ -17,6 +17,16 @@ import (
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
var (
|
||||
testAcceptDeadline = defaultAcceptDeadlineSeconds * time.Second
|
||||
|
||||
testConnDeadline = 100 * time.Millisecond
|
||||
testConnDeadline2o3 = 66 * time.Millisecond // 2/3 of the other one
|
||||
|
||||
testHeartbeatTimeout = 10 * time.Millisecond
|
||||
testHeartbeatTimeout3o2 = 6 * time.Millisecond // 3/2 of the other one
|
||||
)
|
||||
|
||||
func TestSocketPVAddress(t *testing.T) {
|
||||
var (
|
||||
chainID = cmn.RandStr(12)
|
||||
@@ -25,15 +35,10 @@ func TestSocketPVAddress(t *testing.T) {
|
||||
defer sc.Stop()
|
||||
defer rs.Stop()
|
||||
|
||||
serverAddr := rs.privVal.GetAddress()
|
||||
|
||||
clientAddr := sc.GetAddress()
|
||||
serverAddr := rs.privVal.GetPubKey().Address()
|
||||
clientAddr := sc.GetPubKey().Address()
|
||||
|
||||
assert.Equal(t, serverAddr, clientAddr)
|
||||
|
||||
// TODO(xla): Remove when PrivValidator2 replaced PrivValidator.
|
||||
assert.Equal(t, serverAddr, sc.GetAddress())
|
||||
|
||||
}
|
||||
|
||||
func TestSocketPVPubKey(t *testing.T) {
|
||||
@@ -44,15 +49,11 @@ func TestSocketPVPubKey(t *testing.T) {
|
||||
defer sc.Stop()
|
||||
defer rs.Stop()
|
||||
|
||||
clientKey, err := sc.getPubKey()
|
||||
require.NoError(t, err)
|
||||
clientKey := sc.GetPubKey()
|
||||
|
||||
privKey := rs.privVal.GetPubKey()
|
||||
privvalPubKey := rs.privVal.GetPubKey()
|
||||
|
||||
assert.Equal(t, privKey, clientKey)
|
||||
|
||||
// TODO(xla): Remove when PrivValidator2 replaced PrivValidator.
|
||||
assert.Equal(t, privKey, sc.GetPubKey())
|
||||
assert.Equal(t, privvalPubKey, clientKey)
|
||||
}
|
||||
|
||||
func TestSocketPVProposal(t *testing.T) {
|
||||
@@ -103,14 +104,14 @@ func TestSocketPVVoteResetDeadline(t *testing.T) {
|
||||
defer sc.Stop()
|
||||
defer rs.Stop()
|
||||
|
||||
time.Sleep(3 * time.Millisecond)
|
||||
time.Sleep(testConnDeadline2o3)
|
||||
|
||||
require.NoError(t, rs.privVal.SignVote(chainID, want))
|
||||
require.NoError(t, sc.SignVote(chainID, have))
|
||||
assert.Equal(t, want.Signature, have.Signature)
|
||||
|
||||
// This would exceed the deadline if it was not extended by the previous message
|
||||
time.Sleep(3 * time.Millisecond)
|
||||
time.Sleep(testConnDeadline2o3)
|
||||
|
||||
require.NoError(t, rs.privVal.SignVote(chainID, want))
|
||||
require.NoError(t, sc.SignVote(chainID, have))
|
||||
@@ -130,7 +131,7 @@ func TestSocketPVVoteKeepalive(t *testing.T) {
|
||||
defer sc.Stop()
|
||||
defer rs.Stop()
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
time.Sleep(testConnDeadline * 2)
|
||||
|
||||
require.NoError(t, rs.privVal.SignVote(chainID, want))
|
||||
require.NoError(t, sc.SignVote(chainID, have))
|
||||
@@ -139,23 +140,18 @@ func TestSocketPVVoteKeepalive(t *testing.T) {
|
||||
|
||||
func TestSocketPVDeadline(t *testing.T) {
|
||||
var (
|
||||
addr = testFreeAddr(t)
|
||||
listenc = make(chan struct{})
|
||||
sc = NewTCPVal(
|
||||
log.TestingLogger(),
|
||||
addr,
|
||||
ed25519.GenPrivKey(),
|
||||
)
|
||||
addr = testFreeAddr(t)
|
||||
listenc = make(chan struct{})
|
||||
thisConnTimeout = 100 * time.Millisecond
|
||||
sc = newSocketVal(log.TestingLogger(), addr, thisConnTimeout)
|
||||
)
|
||||
|
||||
TCPValConnTimeout(100 * time.Millisecond)(sc)
|
||||
|
||||
go func(sc *TCPVal) {
|
||||
go func(sc *SocketVal) {
|
||||
defer close(listenc)
|
||||
|
||||
require.NoError(t, sc.Start())
|
||||
assert.Equal(t, sc.Start().(cmn.Error).Data(), ErrConnTimeout)
|
||||
|
||||
assert.True(t, sc.IsRunning())
|
||||
assert.False(t, sc.IsRunning())
|
||||
}(sc)
|
||||
|
||||
for {
|
||||
@@ -174,9 +170,6 @@ func TestSocketPVDeadline(t *testing.T) {
|
||||
}
|
||||
|
||||
<-listenc
|
||||
|
||||
_, err := sc.getPubKey()
|
||||
assert.Equal(t, err.(cmn.Error).Data(), ErrConnTimeout)
|
||||
}
|
||||
|
||||
func TestRemoteSignerRetry(t *testing.T) {
|
||||
@@ -210,9 +203,8 @@ func TestRemoteSignerRetry(t *testing.T) {
|
||||
rs := NewRemoteSigner(
|
||||
log.TestingLogger(),
|
||||
cmn.RandStr(12),
|
||||
ln.Addr().String(),
|
||||
types.NewMockPV(),
|
||||
ed25519.GenPrivKey(),
|
||||
DialTCPFn(ln.Addr().String(), testConnDeadline, ed25519.GenPrivKey()),
|
||||
)
|
||||
defer rs.Stop()
|
||||
|
||||
@@ -241,15 +233,8 @@ func TestRemoteSignVoteErrors(t *testing.T) {
|
||||
defer sc.Stop()
|
||||
defer rs.Stop()
|
||||
|
||||
err := writeMsg(sc.conn, &SignVoteRequest{Vote: vote})
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := readMsg(sc.conn)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp := *res.(*SignedVoteResponse)
|
||||
require.NotNil(t, resp.Error)
|
||||
require.Equal(t, resp.Error.Description, types.ErroringMockPVErr.Error())
|
||||
err := sc.SignVote("", vote)
|
||||
require.Equal(t, err.(*RemoteSignerError).Description, types.ErroringMockPVErr.Error())
|
||||
|
||||
err = rs.privVal.SignVote(chainID, vote)
|
||||
require.Error(t, err)
|
||||
@@ -268,15 +253,8 @@ func TestRemoteSignProposalErrors(t *testing.T) {
|
||||
defer sc.Stop()
|
||||
defer rs.Stop()
|
||||
|
||||
err := writeMsg(sc.conn, &SignProposalRequest{Proposal: proposal})
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := readMsg(sc.conn)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp := *res.(*SignedProposalResponse)
|
||||
require.NotNil(t, resp.Error)
|
||||
require.Equal(t, resp.Error.Description, types.ErroringMockPVErr.Error())
|
||||
err := sc.SignProposal("", proposal)
|
||||
require.Equal(t, err.(*RemoteSignerError).Description, types.ErroringMockPVErr.Error())
|
||||
|
||||
err = rs.privVal.SignProposal(chainID, proposal)
|
||||
require.Error(t, err)
|
||||
@@ -296,28 +274,24 @@ func TestErrUnexpectedResponse(t *testing.T) {
|
||||
rs = NewRemoteSigner(
|
||||
logger,
|
||||
chainID,
|
||||
addr,
|
||||
types.NewMockPV(),
|
||||
ed25519.GenPrivKey(),
|
||||
)
|
||||
sc = NewTCPVal(
|
||||
logger,
|
||||
addr,
|
||||
ed25519.GenPrivKey(),
|
||||
DialTCPFn(addr, testConnDeadline, ed25519.GenPrivKey()),
|
||||
)
|
||||
sc = newSocketVal(logger, addr, testConnDeadline)
|
||||
)
|
||||
|
||||
testStartSocketPV(t, readyc, sc)
|
||||
defer sc.Stop()
|
||||
RemoteSignerConnDeadline(time.Millisecond)(rs)
|
||||
RemoteSignerConnRetries(1e6)(rs)
|
||||
|
||||
RemoteSignerConnRetries(100)(rs)
|
||||
// we do not want to Start() the remote signer here and instead use the connection to
|
||||
// reply with intentionally wrong replies below:
|
||||
rsConn, err := rs.connect()
|
||||
defer rsConn.Close()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, rsConn)
|
||||
// send over public key to get the remote signer running:
|
||||
go testReadWriteResponse(t, &PubKeyResponse{}, rsConn)
|
||||
<-readyc
|
||||
|
||||
// Proposal:
|
||||
@@ -341,11 +315,73 @@ func TestErrUnexpectedResponse(t *testing.T) {
|
||||
require.Equal(t, err, ErrUnexpectedResponse)
|
||||
}
|
||||
|
||||
func TestRetryTCPConnToRemoteSigner(t *testing.T) {
|
||||
var (
|
||||
addr = testFreeAddr(t)
|
||||
logger = log.TestingLogger()
|
||||
chainID = cmn.RandStr(12)
|
||||
readyc = make(chan struct{})
|
||||
|
||||
rs = NewRemoteSigner(
|
||||
logger,
|
||||
chainID,
|
||||
types.NewMockPV(),
|
||||
DialTCPFn(addr, testConnDeadline, ed25519.GenPrivKey()),
|
||||
)
|
||||
thisConnTimeout = testConnDeadline
|
||||
sc = newSocketVal(logger, addr, thisConnTimeout)
|
||||
)
|
||||
// Ping every:
|
||||
SocketValHeartbeat(testHeartbeatTimeout)(sc)
|
||||
|
||||
RemoteSignerConnDeadline(testConnDeadline)(rs)
|
||||
RemoteSignerConnRetries(10)(rs)
|
||||
|
||||
testStartSocketPV(t, readyc, sc)
|
||||
defer sc.Stop()
|
||||
require.NoError(t, rs.Start())
|
||||
assert.True(t, rs.IsRunning())
|
||||
|
||||
<-readyc
|
||||
time.Sleep(testHeartbeatTimeout * 2)
|
||||
|
||||
rs.Stop()
|
||||
rs2 := NewRemoteSigner(
|
||||
logger,
|
||||
chainID,
|
||||
types.NewMockPV(),
|
||||
DialTCPFn(addr, testConnDeadline, ed25519.GenPrivKey()),
|
||||
)
|
||||
// let some pings pass
|
||||
time.Sleep(testHeartbeatTimeout3o2)
|
||||
require.NoError(t, rs2.Start())
|
||||
assert.True(t, rs2.IsRunning())
|
||||
defer rs2.Stop()
|
||||
|
||||
// give the client some time to re-establish the conn to the remote signer
|
||||
// should see sth like this in the logs:
|
||||
//
|
||||
// E[10016-01-10|17:12:46.128] Ping err="remote signer timed out"
|
||||
// I[10016-01-10|17:16:42.447] Re-created connection to remote signer impl=SocketVal
|
||||
time.Sleep(testConnDeadline * 2)
|
||||
}
|
||||
|
||||
func newSocketVal(logger log.Logger, addr string, connDeadline time.Duration) *SocketVal {
|
||||
ln, err := net.Listen(cmn.ProtocolAndAddress(addr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tcpLn := NewTCPListener(ln, ed25519.GenPrivKey())
|
||||
TCPListenerAcceptDeadline(testAcceptDeadline)(tcpLn)
|
||||
TCPListenerConnDeadline(testConnDeadline)(tcpLn)
|
||||
return NewSocketVal(logger, tcpLn)
|
||||
}
|
||||
|
||||
func testSetupSocketPair(
|
||||
t *testing.T,
|
||||
chainID string,
|
||||
privValidator types.PrivValidator,
|
||||
) (*TCPVal, *RemoteSigner) {
|
||||
) (*SocketVal, *RemoteSigner) {
|
||||
var (
|
||||
addr = testFreeAddr(t)
|
||||
logger = log.TestingLogger()
|
||||
@@ -354,20 +390,16 @@ func testSetupSocketPair(
|
||||
rs = NewRemoteSigner(
|
||||
logger,
|
||||
chainID,
|
||||
addr,
|
||||
privVal,
|
||||
ed25519.GenPrivKey(),
|
||||
)
|
||||
sc = NewTCPVal(
|
||||
logger,
|
||||
addr,
|
||||
ed25519.GenPrivKey(),
|
||||
DialTCPFn(addr, testConnDeadline, ed25519.GenPrivKey()),
|
||||
)
|
||||
|
||||
thisConnTimeout = testConnDeadline
|
||||
sc = newSocketVal(logger, addr, thisConnTimeout)
|
||||
)
|
||||
|
||||
TCPValConnTimeout(5 * time.Millisecond)(sc)
|
||||
TCPValHeartbeat(2 * time.Millisecond)(sc)
|
||||
RemoteSignerConnDeadline(5 * time.Millisecond)(rs)
|
||||
SocketValHeartbeat(testHeartbeatTimeout)(sc)
|
||||
RemoteSignerConnDeadline(testConnDeadline)(rs)
|
||||
RemoteSignerConnRetries(1e6)(rs)
|
||||
|
||||
testStartSocketPV(t, readyc, sc)
|
||||
@@ -388,8 +420,8 @@ func testReadWriteResponse(t *testing.T, resp RemoteSignerMsg, rsConn net.Conn)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func testStartSocketPV(t *testing.T, readyc chan struct{}, sc *TCPVal) {
|
||||
go func(sc *TCPVal) {
|
||||
func testStartSocketPV(t *testing.T, readyc chan struct{}, sc *SocketVal) {
|
||||
go func(sc *SocketVal) {
|
||||
require.NoError(t, sc.Start())
|
||||
assert.True(t, sc.IsRunning())
|
||||
|
21
privval/doc.go
Normal file
21
privval/doc.go
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
|
||||
Package privval provides different implementations of the types.PrivValidator.
|
||||
|
||||
FilePV
|
||||
|
||||
FilePV is the simplest implementation and developer default. It uses one file for the private key and another to store state.
|
||||
|
||||
SocketVal
|
||||
|
||||
SocketVal establishes a connection to an external process, like a Key Management Server (KMS), using a socket.
|
||||
SocketVal listens for the external KMS process to dial in.
|
||||
SocketVal takes a listener, which determines the type of connection
|
||||
(ie. encrypted over tcp, or unencrypted over unix).
|
||||
|
||||
RemoteSigner
|
||||
|
||||
RemoteSigner is a simple wrapper around a net.Conn. It's used by both IPCVal and TCPVal.
|
||||
|
||||
*/
|
||||
package privval
|
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
@@ -23,6 +22,7 @@ const (
|
||||
stepPrecommit int8 = 3
|
||||
)
|
||||
|
||||
// A vote is either stepPrevote or stepPrecommit.
|
||||
func voteToStep(vote *types.Vote) int8 {
|
||||
switch vote.Type {
|
||||
case types.PrevoteType:
|
||||
@@ -30,105 +30,95 @@ func voteToStep(vote *types.Vote) int8 {
|
||||
case types.PrecommitType:
|
||||
return stepPrecommit
|
||||
default:
|
||||
cmn.PanicSanity("Unknown vote type")
|
||||
panic("Unknown vote type")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// FilePV implements PrivValidator using data persisted to disk
|
||||
// to prevent double signing.
|
||||
// NOTE: the directory containing the pv.filePath must already exist.
|
||||
// It includes the LastSignature and LastSignBytes so we don't lose the signature
|
||||
// if the process crashes after signing but before the resulting consensus message is processed.
|
||||
type FilePV struct {
|
||||
Address types.Address `json:"address"`
|
||||
PubKey crypto.PubKey `json:"pub_key"`
|
||||
LastHeight int64 `json:"last_height"`
|
||||
LastRound int `json:"last_round"`
|
||||
LastStep int8 `json:"last_step"`
|
||||
LastSignature []byte `json:"last_signature,omitempty"`
|
||||
LastSignBytes cmn.HexBytes `json:"last_signbytes,omitempty"`
|
||||
PrivKey crypto.PrivKey `json:"priv_key"`
|
||||
//-------------------------------------------------------------------------------
|
||||
|
||||
// FilePVKey stores the immutable part of PrivValidator.
|
||||
type FilePVKey struct {
|
||||
Address types.Address `json:"address"`
|
||||
PubKey crypto.PubKey `json:"pub_key"`
|
||||
PrivKey crypto.PrivKey `json:"priv_key"`
|
||||
|
||||
// For persistence.
|
||||
// Overloaded for testing.
|
||||
filePath string
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
// GetAddress returns the address of the validator.
|
||||
// Implements PrivValidator.
|
||||
func (pv *FilePV) GetAddress() types.Address {
|
||||
return pv.Address
|
||||
}
|
||||
|
||||
// GetPubKey returns the public key of the validator.
|
||||
// Implements PrivValidator.
|
||||
func (pv *FilePV) GetPubKey() crypto.PubKey {
|
||||
return pv.PubKey
|
||||
}
|
||||
|
||||
// GenFilePV generates a new validator with randomly generated private key
|
||||
// and sets the filePath, but does not call Save().
|
||||
func GenFilePV(filePath string) *FilePV {
|
||||
privKey := ed25519.GenPrivKey()
|
||||
return &FilePV{
|
||||
Address: privKey.PubKey().Address(),
|
||||
PubKey: privKey.PubKey(),
|
||||
PrivKey: privKey,
|
||||
LastStep: stepNone,
|
||||
filePath: filePath,
|
||||
}
|
||||
}
|
||||
|
||||
// LoadFilePV loads a FilePV from the filePath. The FilePV handles double
|
||||
// signing prevention by persisting data to the filePath. If the filePath does
|
||||
// not exist, the FilePV must be created manually and saved.
|
||||
func LoadFilePV(filePath string) *FilePV {
|
||||
pvJSONBytes, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
pv := &FilePV{}
|
||||
err = cdc.UnmarshalJSON(pvJSONBytes, &pv)
|
||||
if err != nil {
|
||||
cmn.Exit(fmt.Sprintf("Error reading PrivValidator from %v: %v\n", filePath, err))
|
||||
}
|
||||
|
||||
// overwrite pubkey and address for convenience
|
||||
pv.PubKey = pv.PrivKey.PubKey()
|
||||
pv.Address = pv.PubKey.Address()
|
||||
|
||||
pv.filePath = filePath
|
||||
return pv
|
||||
}
|
||||
|
||||
// LoadOrGenFilePV loads a FilePV from the given filePath
|
||||
// or else generates a new one and saves it to the filePath.
|
||||
func LoadOrGenFilePV(filePath string) *FilePV {
|
||||
var pv *FilePV
|
||||
if cmn.FileExists(filePath) {
|
||||
pv = LoadFilePV(filePath)
|
||||
} else {
|
||||
pv = GenFilePV(filePath)
|
||||
pv.Save()
|
||||
}
|
||||
return pv
|
||||
}
|
||||
|
||||
// Save persists the FilePV to disk.
|
||||
func (pv *FilePV) Save() {
|
||||
pv.mtx.Lock()
|
||||
defer pv.mtx.Unlock()
|
||||
pv.save()
|
||||
}
|
||||
|
||||
func (pv *FilePV) save() {
|
||||
outFile := pv.filePath
|
||||
// Save persists the FilePVKey to its filePath.
|
||||
func (pvKey FilePVKey) Save() {
|
||||
outFile := pvKey.filePath
|
||||
if outFile == "" {
|
||||
panic("Cannot save PrivValidator: filePath not set")
|
||||
panic("Cannot save PrivValidator key: filePath not set")
|
||||
}
|
||||
jsonBytes, err := cdc.MarshalJSONIndent(pv, "", " ")
|
||||
|
||||
jsonBytes, err := cdc.MarshalJSONIndent(pvKey, "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
|
||||
// FilePVLastSignState stores the mutable part of PrivValidator.
|
||||
type FilePVLastSignState struct {
|
||||
Height int64 `json:"height"`
|
||||
Round int `json:"round"`
|
||||
Step int8 `json:"step"`
|
||||
Signature []byte `json:"signature,omitempty"`
|
||||
SignBytes cmn.HexBytes `json:"signbytes,omitempty"`
|
||||
|
||||
filePath string
|
||||
}
|
||||
|
||||
// CheckHRS checks the given height, round, step (HRS) against that of the
|
||||
// FilePVLastSignState. It returns an error if the arguments constitute a regression,
|
||||
// or if they match but the SignBytes are empty.
|
||||
// The returned boolean indicates whether the last Signature should be reused -
|
||||
// it returns true if the HRS matches the arguments and the SignBytes are not empty (indicating
|
||||
// we have already signed for this HRS, and can reuse the existing signature).
|
||||
// It panics if the HRS matches the arguments, there's a SignBytes, but no Signature.
|
||||
func (lss *FilePVLastSignState) CheckHRS(height int64, round int, step int8) (bool, error) {
|
||||
|
||||
if lss.Height > height {
|
||||
return false, errors.New("Height regression")
|
||||
}
|
||||
|
||||
if lss.Height == height {
|
||||
if lss.Round > round {
|
||||
return false, errors.New("Round regression")
|
||||
}
|
||||
|
||||
if lss.Round == round {
|
||||
if lss.Step > step {
|
||||
return false, errors.New("Step regression")
|
||||
} else if lss.Step == step {
|
||||
if lss.SignBytes != nil {
|
||||
if lss.Signature == nil {
|
||||
panic("pv: Signature is nil but SignBytes is not!")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
return false, errors.New("No SignBytes found")
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Save persists the FilePvLastSignState to its filePath.
|
||||
func (lss *FilePVLastSignState) Save() {
|
||||
outFile := lss.filePath
|
||||
if outFile == "" {
|
||||
panic("Cannot save FilePVLastSignState: filePath not set")
|
||||
}
|
||||
jsonBytes, err := cdc.MarshalJSONIndent(lss, "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -138,23 +128,115 @@ func (pv *FilePV) save() {
|
||||
}
|
||||
}
|
||||
|
||||
// Reset resets all fields in the FilePV.
|
||||
// NOTE: Unsafe!
|
||||
func (pv *FilePV) Reset() {
|
||||
var sig []byte
|
||||
pv.LastHeight = 0
|
||||
pv.LastRound = 0
|
||||
pv.LastStep = 0
|
||||
pv.LastSignature = sig
|
||||
pv.LastSignBytes = nil
|
||||
pv.Save()
|
||||
//-------------------------------------------------------------------------------
|
||||
|
||||
// FilePV implements PrivValidator using data persisted to disk
|
||||
// to prevent double signing.
|
||||
// NOTE: the directories containing pv.Key.filePath and pv.LastSignState.filePath must already exist.
|
||||
// It includes the LastSignature and LastSignBytes so we don't lose the signature
|
||||
// if the process crashes after signing but before the resulting consensus message is processed.
|
||||
type FilePV struct {
|
||||
Key FilePVKey
|
||||
LastSignState FilePVLastSignState
|
||||
}
|
||||
|
||||
// GenFilePV generates a new validator with randomly generated private key
|
||||
// and sets the filePaths, but does not call Save().
|
||||
func GenFilePV(keyFilePath, stateFilePath string) *FilePV {
|
||||
privKey := ed25519.GenPrivKey()
|
||||
|
||||
return &FilePV{
|
||||
Key: FilePVKey{
|
||||
Address: privKey.PubKey().Address(),
|
||||
PubKey: privKey.PubKey(),
|
||||
PrivKey: privKey,
|
||||
filePath: keyFilePath,
|
||||
},
|
||||
LastSignState: FilePVLastSignState{
|
||||
Step: stepNone,
|
||||
filePath: stateFilePath,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// LoadFilePV loads a FilePV from the filePaths. The FilePV handles double
|
||||
// signing prevention by persisting data to the stateFilePath. If either file path
|
||||
// does not exist, the program will exit.
|
||||
func LoadFilePV(keyFilePath, stateFilePath string) *FilePV {
|
||||
return loadFilePV(keyFilePath, stateFilePath, true)
|
||||
}
|
||||
|
||||
// LoadFilePVEmptyState loads a FilePV from the given keyFilePath, with an empty LastSignState.
|
||||
// If the keyFilePath does not exist, the program will exit.
|
||||
func LoadFilePVEmptyState(keyFilePath, stateFilePath string) *FilePV {
|
||||
return loadFilePV(keyFilePath, stateFilePath, false)
|
||||
}
|
||||
|
||||
// If loadState is true, we load from the stateFilePath. Otherwise, we use an empty LastSignState.
|
||||
func loadFilePV(keyFilePath, stateFilePath string, loadState bool) *FilePV {
|
||||
keyJSONBytes, err := ioutil.ReadFile(keyFilePath)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
pvKey := FilePVKey{}
|
||||
err = cdc.UnmarshalJSON(keyJSONBytes, &pvKey)
|
||||
if err != nil {
|
||||
cmn.Exit(fmt.Sprintf("Error reading PrivValidator key from %v: %v\n", keyFilePath, err))
|
||||
}
|
||||
|
||||
// overwrite pubkey and address for convenience
|
||||
pvKey.PubKey = pvKey.PrivKey.PubKey()
|
||||
pvKey.Address = pvKey.PubKey.Address()
|
||||
pvKey.filePath = keyFilePath
|
||||
|
||||
pvState := FilePVLastSignState{}
|
||||
if loadState {
|
||||
stateJSONBytes, err := ioutil.ReadFile(stateFilePath)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
err = cdc.UnmarshalJSON(stateJSONBytes, &pvState)
|
||||
if err != nil {
|
||||
cmn.Exit(fmt.Sprintf("Error reading PrivValidator state from %v: %v\n", stateFilePath, err))
|
||||
}
|
||||
}
|
||||
|
||||
pvState.filePath = stateFilePath
|
||||
|
||||
return &FilePV{
|
||||
Key: pvKey,
|
||||
LastSignState: pvState,
|
||||
}
|
||||
}
|
||||
|
||||
// LoadOrGenFilePV loads a FilePV from the given filePaths
|
||||
// or else generates a new one and saves it to the filePaths.
|
||||
func LoadOrGenFilePV(keyFilePath, stateFilePath string) *FilePV {
|
||||
var pv *FilePV
|
||||
if cmn.FileExists(keyFilePath) {
|
||||
pv = LoadFilePV(keyFilePath, stateFilePath)
|
||||
} else {
|
||||
pv = GenFilePV(keyFilePath, stateFilePath)
|
||||
pv.Save()
|
||||
}
|
||||
return pv
|
||||
}
|
||||
|
||||
// GetAddress returns the address of the validator.
|
||||
// Implements PrivValidator.
|
||||
func (pv *FilePV) GetAddress() types.Address {
|
||||
return pv.Key.Address
|
||||
}
|
||||
|
||||
// GetPubKey returns the public key of the validator.
|
||||
// Implements PrivValidator.
|
||||
func (pv *FilePV) GetPubKey() crypto.PubKey {
|
||||
return pv.Key.PubKey
|
||||
}
|
||||
|
||||
// SignVote signs a canonical representation of the vote, along with the
|
||||
// chainID. Implements PrivValidator.
|
||||
func (pv *FilePV) SignVote(chainID string, vote *types.Vote) error {
|
||||
pv.mtx.Lock()
|
||||
defer pv.mtx.Unlock()
|
||||
if err := pv.signVote(chainID, vote); err != nil {
|
||||
return fmt.Errorf("Error signing vote: %v", err)
|
||||
}
|
||||
@@ -164,65 +246,63 @@ func (pv *FilePV) SignVote(chainID string, vote *types.Vote) error {
|
||||
// SignProposal signs a canonical representation of the proposal, along with
|
||||
// the chainID. Implements PrivValidator.
|
||||
func (pv *FilePV) SignProposal(chainID string, proposal *types.Proposal) error {
|
||||
pv.mtx.Lock()
|
||||
defer pv.mtx.Unlock()
|
||||
if err := pv.signProposal(chainID, proposal); err != nil {
|
||||
return fmt.Errorf("Error signing proposal: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// returns error if HRS regression or no LastSignBytes. returns true if HRS is unchanged
|
||||
func (pv *FilePV) checkHRS(height int64, round int, step int8) (bool, error) {
|
||||
if pv.LastHeight > height {
|
||||
return false, errors.New("Height regression")
|
||||
}
|
||||
|
||||
if pv.LastHeight == height {
|
||||
if pv.LastRound > round {
|
||||
return false, errors.New("Round regression")
|
||||
}
|
||||
|
||||
if pv.LastRound == round {
|
||||
if pv.LastStep > step {
|
||||
return false, errors.New("Step regression")
|
||||
} else if pv.LastStep == step {
|
||||
if pv.LastSignBytes != nil {
|
||||
if pv.LastSignature == nil {
|
||||
panic("pv: LastSignature is nil but LastSignBytes is not!")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
return false, errors.New("No LastSignature found")
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
// Save persists the FilePV to disk.
|
||||
func (pv *FilePV) Save() {
|
||||
pv.Key.Save()
|
||||
pv.LastSignState.Save()
|
||||
}
|
||||
|
||||
// Reset resets all fields in the FilePV.
|
||||
// NOTE: Unsafe!
|
||||
func (pv *FilePV) Reset() {
|
||||
var sig []byte
|
||||
pv.LastSignState.Height = 0
|
||||
pv.LastSignState.Round = 0
|
||||
pv.LastSignState.Step = 0
|
||||
pv.LastSignState.Signature = sig
|
||||
pv.LastSignState.SignBytes = nil
|
||||
pv.Save()
|
||||
}
|
||||
|
||||
// String returns a string representation of the FilePV.
|
||||
func (pv *FilePV) String() string {
|
||||
return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", pv.GetAddress(), pv.LastSignState.Height, pv.LastSignState.Round, pv.LastSignState.Step)
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
|
||||
// signVote checks if the vote is good to sign and sets the vote signature.
|
||||
// It may need to set the timestamp as well if the vote is otherwise the same as
|
||||
// a previously signed vote (ie. we crashed after signing but before the vote hit the WAL).
|
||||
func (pv *FilePV) signVote(chainID string, vote *types.Vote) error {
|
||||
height, round, step := vote.Height, vote.Round, voteToStep(vote)
|
||||
signBytes := vote.SignBytes(chainID)
|
||||
|
||||
sameHRS, err := pv.checkHRS(height, round, step)
|
||||
lss := pv.LastSignState
|
||||
|
||||
sameHRS, err := lss.CheckHRS(height, round, step)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signBytes := vote.SignBytes(chainID)
|
||||
|
||||
// We might crash before writing to the wal,
|
||||
// causing us to try to re-sign for the same HRS.
|
||||
// If signbytes are the same, use the last signature.
|
||||
// If they only differ by timestamp, use last timestamp and signature
|
||||
// Otherwise, return error
|
||||
if sameHRS {
|
||||
if bytes.Equal(signBytes, pv.LastSignBytes) {
|
||||
vote.Signature = pv.LastSignature
|
||||
} else if timestamp, ok := checkVotesOnlyDifferByTimestamp(pv.LastSignBytes, signBytes); ok {
|
||||
if bytes.Equal(signBytes, lss.SignBytes) {
|
||||
vote.Signature = lss.Signature
|
||||
} else if timestamp, ok := checkVotesOnlyDifferByTimestamp(lss.SignBytes, signBytes); ok {
|
||||
vote.Timestamp = timestamp
|
||||
vote.Signature = pv.LastSignature
|
||||
vote.Signature = lss.Signature
|
||||
} else {
|
||||
err = fmt.Errorf("Conflicting data")
|
||||
}
|
||||
@@ -230,7 +310,7 @@ func (pv *FilePV) signVote(chainID string, vote *types.Vote) error {
|
||||
}
|
||||
|
||||
// It passed the checks. Sign the vote
|
||||
sig, err := pv.PrivKey.Sign(signBytes)
|
||||
sig, err := pv.Key.PrivKey.Sign(signBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -244,24 +324,27 @@ func (pv *FilePV) signVote(chainID string, vote *types.Vote) error {
|
||||
// a previously signed proposal ie. we crashed after signing but before the proposal hit the WAL).
|
||||
func (pv *FilePV) signProposal(chainID string, proposal *types.Proposal) error {
|
||||
height, round, step := proposal.Height, proposal.Round, stepPropose
|
||||
signBytes := proposal.SignBytes(chainID)
|
||||
|
||||
sameHRS, err := pv.checkHRS(height, round, step)
|
||||
lss := pv.LastSignState
|
||||
|
||||
sameHRS, err := lss.CheckHRS(height, round, step)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signBytes := proposal.SignBytes(chainID)
|
||||
|
||||
// We might crash before writing to the wal,
|
||||
// causing us to try to re-sign for the same HRS.
|
||||
// If signbytes are the same, use the last signature.
|
||||
// If they only differ by timestamp, use last timestamp and signature
|
||||
// Otherwise, return error
|
||||
if sameHRS {
|
||||
if bytes.Equal(signBytes, pv.LastSignBytes) {
|
||||
proposal.Signature = pv.LastSignature
|
||||
} else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(pv.LastSignBytes, signBytes); ok {
|
||||
if bytes.Equal(signBytes, lss.SignBytes) {
|
||||
proposal.Signature = lss.Signature
|
||||
} else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(lss.SignBytes, signBytes); ok {
|
||||
proposal.Timestamp = timestamp
|
||||
proposal.Signature = pv.LastSignature
|
||||
proposal.Signature = lss.Signature
|
||||
} else {
|
||||
err = fmt.Errorf("Conflicting data")
|
||||
}
|
||||
@@ -269,7 +352,7 @@ func (pv *FilePV) signProposal(chainID string, proposal *types.Proposal) error {
|
||||
}
|
||||
|
||||
// It passed the checks. Sign the proposal
|
||||
sig, err := pv.PrivKey.Sign(signBytes)
|
||||
sig, err := pv.Key.PrivKey.Sign(signBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -282,20 +365,15 @@ func (pv *FilePV) signProposal(chainID string, proposal *types.Proposal) error {
|
||||
func (pv *FilePV) saveSigned(height int64, round int, step int8,
|
||||
signBytes []byte, sig []byte) {
|
||||
|
||||
pv.LastHeight = height
|
||||
pv.LastRound = round
|
||||
pv.LastStep = step
|
||||
pv.LastSignature = sig
|
||||
pv.LastSignBytes = signBytes
|
||||
pv.save()
|
||||
pv.LastSignState.Height = height
|
||||
pv.LastSignState.Round = round
|
||||
pv.LastSignState.Step = step
|
||||
pv.LastSignState.Signature = sig
|
||||
pv.LastSignState.SignBytes = signBytes
|
||||
pv.LastSignState.Save()
|
||||
}
|
||||
|
||||
// String returns a string representation of the FilePV.
|
||||
func (pv *FilePV) String() string {
|
||||
return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", pv.GetAddress(), pv.LastHeight, pv.LastRound, pv.LastStep)
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
//-----------------------------------------------------------------------------------------
|
||||
|
||||
// returns the timestamp from the lastSignBytes.
|
||||
// returns true if the only difference in the votes is their timestamp.
|
@@ -18,36 +18,100 @@ import (
|
||||
func TestGenLoadValidator(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tempFile, err := ioutil.TempFile("", "priv_validator_")
|
||||
tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_")
|
||||
require.Nil(t, err)
|
||||
privVal := GenFilePV(tempFile.Name())
|
||||
tempStateFile, err := ioutil.TempFile("", "priv_validator_state_")
|
||||
require.Nil(t, err)
|
||||
|
||||
privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name())
|
||||
|
||||
height := int64(100)
|
||||
privVal.LastHeight = height
|
||||
privVal.LastSignState.Height = height
|
||||
privVal.Save()
|
||||
addr := privVal.GetAddress()
|
||||
|
||||
privVal = LoadFilePV(tempFile.Name())
|
||||
privVal = LoadFilePV(tempKeyFile.Name(), tempStateFile.Name())
|
||||
assert.Equal(addr, privVal.GetAddress(), "expected privval addr to be the same")
|
||||
assert.Equal(height, privVal.LastHeight, "expected privval.LastHeight to have been saved")
|
||||
assert.Equal(height, privVal.LastSignState.Height, "expected privval.LastHeight to have been saved")
|
||||
}
|
||||
|
||||
func TestResetValidator(t *testing.T) {
|
||||
tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_")
|
||||
require.Nil(t, err)
|
||||
tempStateFile, err := ioutil.TempFile("", "priv_validator_state_")
|
||||
require.Nil(t, err)
|
||||
|
||||
privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name())
|
||||
emptyState := FilePVLastSignState{filePath: tempStateFile.Name()}
|
||||
|
||||
// new priv val has empty state
|
||||
assert.Equal(t, privVal.LastSignState, emptyState)
|
||||
|
||||
// test vote
|
||||
height, round := int64(10), 1
|
||||
voteType := byte(types.PrevoteType)
|
||||
blockID := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}}
|
||||
vote := newVote(privVal.Key.Address, 0, height, round, voteType, blockID)
|
||||
err = privVal.SignVote("mychainid", vote)
|
||||
assert.NoError(t, err, "expected no error signing vote")
|
||||
|
||||
// priv val after signing is not same as empty
|
||||
assert.NotEqual(t, privVal.LastSignState, emptyState)
|
||||
|
||||
// priv val after reset is same as empty
|
||||
privVal.Reset()
|
||||
assert.Equal(t, privVal.LastSignState, emptyState)
|
||||
}
|
||||
|
||||
func TestLoadOrGenValidator(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tempFile, err := ioutil.TempFile("", "priv_validator_")
|
||||
tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_")
|
||||
require.Nil(t, err)
|
||||
tempFilePath := tempFile.Name()
|
||||
if err := os.Remove(tempFilePath); err != nil {
|
||||
tempStateFile, err := ioutil.TempFile("", "priv_validator_state_")
|
||||
require.Nil(t, err)
|
||||
|
||||
tempKeyFilePath := tempKeyFile.Name()
|
||||
if err := os.Remove(tempKeyFilePath); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
privVal := LoadOrGenFilePV(tempFilePath)
|
||||
tempStateFilePath := tempStateFile.Name()
|
||||
if err := os.Remove(tempStateFilePath); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
privVal := LoadOrGenFilePV(tempKeyFilePath, tempStateFilePath)
|
||||
addr := privVal.GetAddress()
|
||||
privVal = LoadOrGenFilePV(tempFilePath)
|
||||
privVal = LoadOrGenFilePV(tempKeyFilePath, tempStateFilePath)
|
||||
assert.Equal(addr, privVal.GetAddress(), "expected privval addr to be the same")
|
||||
}
|
||||
|
||||
func TestUnmarshalValidator(t *testing.T) {
|
||||
func TestUnmarshalValidatorState(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
// create some fixed values
|
||||
serialized := `{
|
||||
"height": "1",
|
||||
"round": "1",
|
||||
"step": 1
|
||||
}`
|
||||
|
||||
val := FilePVLastSignState{}
|
||||
err := cdc.UnmarshalJSON([]byte(serialized), &val)
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
// make sure the values match
|
||||
assert.EqualValues(val.Height, 1)
|
||||
assert.EqualValues(val.Round, 1)
|
||||
assert.EqualValues(val.Step, 1)
|
||||
|
||||
// export it and make sure it is the same
|
||||
out, err := cdc.MarshalJSON(val)
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.JSONEq(serialized, string(out))
|
||||
}
|
||||
|
||||
func TestUnmarshalValidatorKey(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
// create some fixed values
|
||||
@@ -67,22 +131,19 @@ func TestUnmarshalValidator(t *testing.T) {
|
||||
"type": "tendermint/PubKeyEd25519",
|
||||
"value": "%s"
|
||||
},
|
||||
"last_height": "0",
|
||||
"last_round": "0",
|
||||
"last_step": 0,
|
||||
"priv_key": {
|
||||
"type": "tendermint/PrivKeyEd25519",
|
||||
"value": "%s"
|
||||
}
|
||||
}`, addr, pubB64, privB64)
|
||||
|
||||
val := FilePV{}
|
||||
val := FilePVKey{}
|
||||
err := cdc.UnmarshalJSON([]byte(serialized), &val)
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
// make sure the values match
|
||||
assert.EqualValues(addr, val.GetAddress())
|
||||
assert.EqualValues(pubKey, val.GetPubKey())
|
||||
assert.EqualValues(addr, val.Address)
|
||||
assert.EqualValues(pubKey, val.PubKey)
|
||||
assert.EqualValues(privKey, val.PrivKey)
|
||||
|
||||
// export it and make sure it is the same
|
||||
@@ -94,9 +155,12 @@ func TestUnmarshalValidator(t *testing.T) {
|
||||
func TestSignVote(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tempFile, err := ioutil.TempFile("", "priv_validator_")
|
||||
tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_")
|
||||
require.Nil(t, err)
|
||||
privVal := GenFilePV(tempFile.Name())
|
||||
tempStateFile, err := ioutil.TempFile("", "priv_validator_state_")
|
||||
require.Nil(t, err)
|
||||
|
||||
privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name())
|
||||
|
||||
block1 := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}}
|
||||
block2 := types.BlockID{[]byte{3, 2, 1}, types.PartSetHeader{}}
|
||||
@@ -104,7 +168,7 @@ func TestSignVote(t *testing.T) {
|
||||
voteType := byte(types.PrevoteType)
|
||||
|
||||
// sign a vote for first time
|
||||
vote := newVote(privVal.Address, 0, height, round, voteType, block1)
|
||||
vote := newVote(privVal.Key.Address, 0, height, round, voteType, block1)
|
||||
err = privVal.SignVote("mychainid", vote)
|
||||
assert.NoError(err, "expected no error signing vote")
|
||||
|
||||
@@ -114,10 +178,10 @@ func TestSignVote(t *testing.T) {
|
||||
|
||||
// now try some bad votes
|
||||
cases := []*types.Vote{
|
||||
newVote(privVal.Address, 0, height, round-1, voteType, block1), // round regression
|
||||
newVote(privVal.Address, 0, height-1, round, voteType, block1), // height regression
|
||||
newVote(privVal.Address, 0, height-2, round+4, voteType, block1), // height regression and different round
|
||||
newVote(privVal.Address, 0, height, round, voteType, block2), // different block
|
||||
newVote(privVal.Key.Address, 0, height, round-1, voteType, block1), // round regression
|
||||
newVote(privVal.Key.Address, 0, height-1, round, voteType, block1), // height regression
|
||||
newVote(privVal.Key.Address, 0, height-2, round+4, voteType, block1), // height regression and different round
|
||||
newVote(privVal.Key.Address, 0, height, round, voteType, block2), // different block
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
@@ -136,9 +200,12 @@ func TestSignVote(t *testing.T) {
|
||||
func TestSignProposal(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tempFile, err := ioutil.TempFile("", "priv_validator_")
|
||||
tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_")
|
||||
require.Nil(t, err)
|
||||
privVal := GenFilePV(tempFile.Name())
|
||||
tempStateFile, err := ioutil.TempFile("", "priv_validator_state_")
|
||||
require.Nil(t, err)
|
||||
|
||||
privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name())
|
||||
|
||||
block1 := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{5, []byte{1, 2, 3}}}
|
||||
block2 := types.BlockID{[]byte{3, 2, 1}, types.PartSetHeader{10, []byte{3, 2, 1}}}
|
||||
@@ -175,9 +242,12 @@ func TestSignProposal(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDifferByTimestamp(t *testing.T) {
|
||||
tempFile, err := ioutil.TempFile("", "priv_validator_")
|
||||
tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_")
|
||||
require.Nil(t, err)
|
||||
privVal := GenFilePV(tempFile.Name())
|
||||
tempStateFile, err := ioutil.TempFile("", "priv_validator_state_")
|
||||
require.Nil(t, err)
|
||||
|
||||
privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name())
|
||||
|
||||
block1 := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{5, []byte{1, 2, 3}}}
|
||||
height, round := int64(10), 1
|
||||
@@ -208,7 +278,7 @@ func TestDifferByTimestamp(t *testing.T) {
|
||||
{
|
||||
voteType := byte(types.PrevoteType)
|
||||
blockID := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}}
|
||||
vote := newVote(privVal.Address, 0, height, round, voteType, blockID)
|
||||
vote := newVote(privVal.Key.Address, 0, height, round, voteType, blockID)
|
||||
err := privVal.SignVote("mychainid", vote)
|
||||
assert.NoError(t, err, "expected no error signing vote")
|
||||
|
120
privval/ipc.go
120
privval/ipc.go
@@ -1,120 +0,0 @@
|
||||
package privval
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// IPCValOption sets an optional parameter on the SocketPV.
|
||||
type IPCValOption func(*IPCVal)
|
||||
|
||||
// IPCValConnTimeout sets the read and write timeout for connections
|
||||
// from external signing processes.
|
||||
func IPCValConnTimeout(timeout time.Duration) IPCValOption {
|
||||
return func(sc *IPCVal) { sc.connTimeout = timeout }
|
||||
}
|
||||
|
||||
// IPCValHeartbeat sets the period on which to check the liveness of the
|
||||
// connected Signer connections.
|
||||
func IPCValHeartbeat(period time.Duration) IPCValOption {
|
||||
return func(sc *IPCVal) { sc.connHeartbeat = period }
|
||||
}
|
||||
|
||||
// IPCVal implements PrivValidator, it uses a unix socket to request signatures
|
||||
// from an external process.
|
||||
type IPCVal struct {
|
||||
cmn.BaseService
|
||||
*RemoteSignerClient
|
||||
|
||||
addr string
|
||||
|
||||
connTimeout time.Duration
|
||||
connHeartbeat time.Duration
|
||||
|
||||
conn net.Conn
|
||||
cancelPing chan struct{}
|
||||
pingTicker *time.Ticker
|
||||
}
|
||||
|
||||
// Check that IPCVal implements PrivValidator.
|
||||
var _ types.PrivValidator = (*IPCVal)(nil)
|
||||
|
||||
// NewIPCVal returns an instance of IPCVal.
|
||||
func NewIPCVal(
|
||||
logger log.Logger,
|
||||
socketAddr string,
|
||||
) *IPCVal {
|
||||
sc := &IPCVal{
|
||||
addr: socketAddr,
|
||||
connTimeout: connTimeout,
|
||||
connHeartbeat: connHeartbeat,
|
||||
}
|
||||
|
||||
sc.BaseService = *cmn.NewBaseService(logger, "IPCVal", sc)
|
||||
|
||||
return sc
|
||||
}
|
||||
|
||||
// OnStart implements cmn.Service.
|
||||
func (sc *IPCVal) OnStart() error {
|
||||
err := sc.connect()
|
||||
if err != nil {
|
||||
sc.Logger.Error("OnStart", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
sc.RemoteSignerClient = NewRemoteSignerClient(sc.conn)
|
||||
|
||||
// Start a routine to keep the connection alive
|
||||
sc.cancelPing = make(chan struct{}, 1)
|
||||
sc.pingTicker = time.NewTicker(sc.connHeartbeat)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-sc.pingTicker.C:
|
||||
err := sc.Ping()
|
||||
if err != nil {
|
||||
sc.Logger.Error("Ping", "err", err)
|
||||
}
|
||||
case <-sc.cancelPing:
|
||||
sc.pingTicker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnStop implements cmn.Service.
|
||||
func (sc *IPCVal) OnStop() {
|
||||
if sc.cancelPing != nil {
|
||||
close(sc.cancelPing)
|
||||
}
|
||||
|
||||
if sc.conn != nil {
|
||||
if err := sc.conn.Close(); err != nil {
|
||||
sc.Logger.Error("OnStop", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *IPCVal) connect() error {
|
||||
la, err := net.ResolveUnixAddr("unix", sc.addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn, err := net.DialUnix("unix", nil, la)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sc.conn = newTimeoutConn(conn, sc.connTimeout)
|
||||
|
||||
return nil
|
||||
}
|
@@ -1,132 +0,0 @@
|
||||
package privval
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// IPCRemoteSignerOption sets an optional parameter on the IPCRemoteSigner.
|
||||
type IPCRemoteSignerOption func(*IPCRemoteSigner)
|
||||
|
||||
// IPCRemoteSignerConnDeadline sets the read and write deadline for connections
|
||||
// from external signing processes.
|
||||
func IPCRemoteSignerConnDeadline(deadline time.Duration) IPCRemoteSignerOption {
|
||||
return func(ss *IPCRemoteSigner) { ss.connDeadline = deadline }
|
||||
}
|
||||
|
||||
// IPCRemoteSignerConnRetries sets the amount of attempted retries to connect.
|
||||
func IPCRemoteSignerConnRetries(retries int) IPCRemoteSignerOption {
|
||||
return func(ss *IPCRemoteSigner) { ss.connRetries = retries }
|
||||
}
|
||||
|
||||
// IPCRemoteSigner is a RPC implementation of PrivValidator that listens on a unix socket.
|
||||
type IPCRemoteSigner struct {
|
||||
cmn.BaseService
|
||||
|
||||
addr string
|
||||
chainID string
|
||||
connDeadline time.Duration
|
||||
connRetries int
|
||||
privVal types.PrivValidator
|
||||
|
||||
listener *net.UnixListener
|
||||
}
|
||||
|
||||
// NewIPCRemoteSigner returns an instance of IPCRemoteSigner.
|
||||
func NewIPCRemoteSigner(
|
||||
logger log.Logger,
|
||||
chainID, socketAddr string,
|
||||
privVal types.PrivValidator,
|
||||
) *IPCRemoteSigner {
|
||||
rs := &IPCRemoteSigner{
|
||||
addr: socketAddr,
|
||||
chainID: chainID,
|
||||
connDeadline: time.Second * defaultConnDeadlineSeconds,
|
||||
connRetries: defaultDialRetries,
|
||||
privVal: privVal,
|
||||
}
|
||||
|
||||
rs.BaseService = *cmn.NewBaseService(logger, "IPCRemoteSigner", rs)
|
||||
|
||||
return rs
|
||||
}
|
||||
|
||||
// OnStart implements cmn.Service.
|
||||
func (rs *IPCRemoteSigner) OnStart() error {
|
||||
err := rs.listen()
|
||||
if err != nil {
|
||||
err = cmn.ErrorWrap(err, "listen")
|
||||
rs.Logger.Error("OnStart", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
conn, err := rs.listener.AcceptUnix()
|
||||
if err != nil {
|
||||
rs.Logger.Error("AcceptUnix", "err", err)
|
||||
return
|
||||
}
|
||||
go rs.handleConnection(conn)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnStop implements cmn.Service.
|
||||
func (rs *IPCRemoteSigner) OnStop() {
|
||||
if rs.listener != nil {
|
||||
if err := rs.listener.Close(); err != nil {
|
||||
rs.Logger.Error("OnStop", "err", cmn.ErrorWrap(err, "closing listener failed"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (rs *IPCRemoteSigner) listen() error {
|
||||
la, err := net.ResolveUnixAddr("unix", rs.addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rs.listener, err = net.ListenUnix("unix", la)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (rs *IPCRemoteSigner) handleConnection(conn net.Conn) {
|
||||
for {
|
||||
if !rs.IsRunning() {
|
||||
return // Ignore error from listener closing.
|
||||
}
|
||||
|
||||
// Reset the connection deadline
|
||||
conn.SetDeadline(time.Now().Add(rs.connDeadline))
|
||||
|
||||
req, err := readMsg(conn)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
rs.Logger.Error("handleConnection", "err", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
res, err := handleRequest(req, rs.chainID, rs.privVal)
|
||||
|
||||
if err != nil {
|
||||
// only log the error; we'll reply with an error in res
|
||||
rs.Logger.Error("handleConnection", "err", err)
|
||||
}
|
||||
|
||||
err = writeMsg(conn, res)
|
||||
if err != nil {
|
||||
rs.Logger.Error("handleConnection", "err", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,147 +0,0 @@
|
||||
package privval
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
func TestIPCPVVote(t *testing.T) {
|
||||
var (
|
||||
chainID = cmn.RandStr(12)
|
||||
sc, rs = testSetupIPCSocketPair(t, chainID, types.NewMockPV())
|
||||
|
||||
ts = time.Now()
|
||||
vType = types.PrecommitType
|
||||
want = &types.Vote{Timestamp: ts, Type: vType}
|
||||
have = &types.Vote{Timestamp: ts, Type: vType}
|
||||
)
|
||||
defer sc.Stop()
|
||||
defer rs.Stop()
|
||||
|
||||
require.NoError(t, rs.privVal.SignVote(chainID, want))
|
||||
require.NoError(t, sc.SignVote(chainID, have))
|
||||
assert.Equal(t, want.Signature, have.Signature)
|
||||
}
|
||||
|
||||
func TestIPCPVVoteResetDeadline(t *testing.T) {
|
||||
var (
|
||||
chainID = cmn.RandStr(12)
|
||||
sc, rs = testSetupIPCSocketPair(t, chainID, types.NewMockPV())
|
||||
|
||||
ts = time.Now()
|
||||
vType = types.PrecommitType
|
||||
want = &types.Vote{Timestamp: ts, Type: vType}
|
||||
have = &types.Vote{Timestamp: ts, Type: vType}
|
||||
)
|
||||
defer sc.Stop()
|
||||
defer rs.Stop()
|
||||
|
||||
time.Sleep(3 * time.Millisecond)
|
||||
|
||||
require.NoError(t, rs.privVal.SignVote(chainID, want))
|
||||
require.NoError(t, sc.SignVote(chainID, have))
|
||||
assert.Equal(t, want.Signature, have.Signature)
|
||||
|
||||
// This would exceed the deadline if it was not extended by the previous message
|
||||
time.Sleep(3 * time.Millisecond)
|
||||
|
||||
require.NoError(t, rs.privVal.SignVote(chainID, want))
|
||||
require.NoError(t, sc.SignVote(chainID, have))
|
||||
assert.Equal(t, want.Signature, have.Signature)
|
||||
}
|
||||
|
||||
func TestIPCPVVoteKeepalive(t *testing.T) {
|
||||
var (
|
||||
chainID = cmn.RandStr(12)
|
||||
sc, rs = testSetupIPCSocketPair(t, chainID, types.NewMockPV())
|
||||
|
||||
ts = time.Now()
|
||||
vType = types.PrecommitType
|
||||
want = &types.Vote{Timestamp: ts, Type: vType}
|
||||
have = &types.Vote{Timestamp: ts, Type: vType}
|
||||
)
|
||||
defer sc.Stop()
|
||||
defer rs.Stop()
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
require.NoError(t, rs.privVal.SignVote(chainID, want))
|
||||
require.NoError(t, sc.SignVote(chainID, have))
|
||||
assert.Equal(t, want.Signature, have.Signature)
|
||||
}
|
||||
|
||||
func testSetupIPCSocketPair(
|
||||
t *testing.T,
|
||||
chainID string,
|
||||
privValidator types.PrivValidator,
|
||||
) (*IPCVal, *IPCRemoteSigner) {
|
||||
addr, err := testUnixAddr()
|
||||
require.NoError(t, err)
|
||||
|
||||
var (
|
||||
logger = log.TestingLogger()
|
||||
privVal = privValidator
|
||||
readyc = make(chan struct{})
|
||||
rs = NewIPCRemoteSigner(
|
||||
logger,
|
||||
chainID,
|
||||
addr,
|
||||
privVal,
|
||||
)
|
||||
sc = NewIPCVal(
|
||||
logger,
|
||||
addr,
|
||||
)
|
||||
)
|
||||
|
||||
IPCValConnTimeout(5 * time.Millisecond)(sc)
|
||||
IPCValHeartbeat(time.Millisecond)(sc)
|
||||
|
||||
IPCRemoteSignerConnDeadline(time.Millisecond * 5)(rs)
|
||||
|
||||
testStartIPCRemoteSigner(t, readyc, rs)
|
||||
|
||||
<-readyc
|
||||
|
||||
require.NoError(t, sc.Start())
|
||||
assert.True(t, sc.IsRunning())
|
||||
|
||||
return sc, rs
|
||||
}
|
||||
|
||||
func testStartIPCRemoteSigner(t *testing.T, readyc chan struct{}, rs *IPCRemoteSigner) {
|
||||
go func(rs *IPCRemoteSigner) {
|
||||
require.NoError(t, rs.Start())
|
||||
assert.True(t, rs.IsRunning())
|
||||
|
||||
readyc <- struct{}{}
|
||||
}(rs)
|
||||
}
|
||||
|
||||
func testUnixAddr() (string, error) {
|
||||
f, err := ioutil.TempFile("/tmp", "nettest")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
addr := f.Name()
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = os.Remove(addr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return addr, nil
|
||||
}
|
80
privval/old_file.go
Normal file
80
privval/old_file.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package privval
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// OldFilePV is the old version of the FilePV, pre v0.28.0.
|
||||
type OldFilePV struct {
|
||||
Address types.Address `json:"address"`
|
||||
PubKey crypto.PubKey `json:"pub_key"`
|
||||
LastHeight int64 `json:"last_height"`
|
||||
LastRound int `json:"last_round"`
|
||||
LastStep int8 `json:"last_step"`
|
||||
LastSignature []byte `json:"last_signature,omitempty"`
|
||||
LastSignBytes cmn.HexBytes `json:"last_signbytes,omitempty"`
|
||||
PrivKey crypto.PrivKey `json:"priv_key"`
|
||||
|
||||
filePath string
|
||||
}
|
||||
|
||||
// LoadOldFilePV loads an OldFilePV from the filePath.
|
||||
func LoadOldFilePV(filePath string) (*OldFilePV, error) {
|
||||
pvJSONBytes, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pv := &OldFilePV{}
|
||||
err = cdc.UnmarshalJSON(pvJSONBytes, &pv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// overwrite pubkey and address for convenience
|
||||
pv.PubKey = pv.PrivKey.PubKey()
|
||||
pv.Address = pv.PubKey.Address()
|
||||
|
||||
pv.filePath = filePath
|
||||
return pv, nil
|
||||
}
|
||||
|
||||
// Upgrade convets the OldFilePV to the new FilePV, separating the immutable and mutable components,
|
||||
// and persisting them to the keyFilePath and stateFilePath, respectively.
|
||||
// It renames the original file by adding ".bak".
|
||||
func (oldFilePV *OldFilePV) Upgrade(keyFilePath, stateFilePath string) *FilePV {
|
||||
privKey := oldFilePV.PrivKey
|
||||
pvKey := FilePVKey{
|
||||
PrivKey: privKey,
|
||||
PubKey: privKey.PubKey(),
|
||||
Address: privKey.PubKey().Address(),
|
||||
filePath: keyFilePath,
|
||||
}
|
||||
|
||||
pvState := FilePVLastSignState{
|
||||
Height: oldFilePV.LastHeight,
|
||||
Round: oldFilePV.LastRound,
|
||||
Step: oldFilePV.LastStep,
|
||||
Signature: oldFilePV.LastSignature,
|
||||
SignBytes: oldFilePV.LastSignBytes,
|
||||
filePath: stateFilePath,
|
||||
}
|
||||
|
||||
// Save the new PV files
|
||||
pv := &FilePV{
|
||||
Key: pvKey,
|
||||
LastSignState: pvState,
|
||||
}
|
||||
pv.Save()
|
||||
|
||||
// Rename the old PV file
|
||||
err := os.Rename(oldFilePV.filePath, oldFilePV.filePath+".bak")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return pv
|
||||
}
|
77
privval/old_file_test.go
Normal file
77
privval/old_file_test.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package privval_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/privval"
|
||||
)
|
||||
|
||||
const oldPrivvalContent = `{
|
||||
"address": "1D8089FAFDFAE4A637F3D616E17B92905FA2D91D",
|
||||
"pub_key": {
|
||||
"type": "tendermint/PubKeyEd25519",
|
||||
"value": "r3Yg2AhDZ745CNTpavsGU+mRZ8WpRXqoJuyqjN8mJq0="
|
||||
},
|
||||
"last_height": "5",
|
||||
"last_round": "0",
|
||||
"last_step": 3,
|
||||
"last_signature": "CTr7b9ZQlrJJf+12rPl5t/YSCUc/KqV7jQogCfFJA24e7hof69X6OMT7eFLVQHyodPjD/QTA298XHV5ejxInDQ==",
|
||||
"last_signbytes": "750802110500000000000000220B08B398F3E00510F48DA6402A480A20FC258973076512999C3E6839A22E9FBDB1B77CF993E8A9955412A41A59D4CAD312240A20C971B286ACB8AAA6FCA0365EB0A660B189EDC08B46B5AF2995DEFA51A28D215B10013211746573742D636861696E2D533245415533",
|
||||
"priv_key": {
|
||||
"type": "tendermint/PrivKeyEd25519",
|
||||
"value": "7MwvTGEWWjsYwjn2IpRb+GYsWi9nnFsw8jPLLY1UtP6vdiDYCENnvjkI1Olq+wZT6ZFnxalFeqgm7KqM3yYmrQ=="
|
||||
}
|
||||
}`
|
||||
|
||||
func TestLoadAndUpgrade(t *testing.T) {
|
||||
|
||||
oldFilePath := initTmpOldFile(t)
|
||||
defer os.Remove(oldFilePath)
|
||||
newStateFile, err := ioutil.TempFile("", "priv_validator_state*.json")
|
||||
defer os.Remove(newStateFile.Name())
|
||||
require.NoError(t, err)
|
||||
newKeyFile, err := ioutil.TempFile("", "priv_validator_key*.json")
|
||||
defer os.Remove(newKeyFile.Name())
|
||||
require.NoError(t, err)
|
||||
|
||||
oldPV, err := privval.LoadOldFilePV(oldFilePath)
|
||||
assert.NoError(t, err)
|
||||
newPV := oldPV.Upgrade(newKeyFile.Name(), newStateFile.Name())
|
||||
|
||||
assertEqualPV(t, oldPV, newPV)
|
||||
assert.NoError(t, err)
|
||||
upgradedPV := privval.LoadFilePV(newKeyFile.Name(), newStateFile.Name())
|
||||
assertEqualPV(t, oldPV, upgradedPV)
|
||||
oldPV, err = privval.LoadOldFilePV(oldFilePath + ".bak")
|
||||
require.NoError(t, err)
|
||||
assertEqualPV(t, oldPV, upgradedPV)
|
||||
}
|
||||
|
||||
func assertEqualPV(t *testing.T, oldPV *privval.OldFilePV, newPV *privval.FilePV) {
|
||||
assert.Equal(t, oldPV.Address, newPV.Key.Address)
|
||||
assert.Equal(t, oldPV.Address, newPV.GetAddress())
|
||||
assert.Equal(t, oldPV.PubKey, newPV.Key.PubKey)
|
||||
assert.Equal(t, oldPV.PubKey, newPV.GetPubKey())
|
||||
assert.Equal(t, oldPV.PrivKey, newPV.Key.PrivKey)
|
||||
|
||||
assert.Equal(t, oldPV.LastHeight, newPV.LastSignState.Height)
|
||||
assert.Equal(t, oldPV.LastRound, newPV.LastSignState.Round)
|
||||
assert.Equal(t, oldPV.LastSignature, newPV.LastSignState.Signature)
|
||||
assert.Equal(t, oldPV.LastSignBytes, newPV.LastSignState.SignBytes)
|
||||
assert.Equal(t, oldPV.LastStep, newPV.LastSignState.Step)
|
||||
}
|
||||
|
||||
func initTmpOldFile(t *testing.T) string {
|
||||
tmpfile, err := ioutil.TempFile("", "priv_validator_*.json")
|
||||
require.NoError(t, err)
|
||||
t.Logf("created test file %s", tmpfile.Name())
|
||||
_, err = tmpfile.WriteString(oldPrivvalContent)
|
||||
require.NoError(t, err)
|
||||
|
||||
return tmpfile.Name()
|
||||
}
|
@@ -4,7 +4,8 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/tendermint/go-amino"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
@@ -12,68 +13,73 @@ import (
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// RemoteSignerClient implements PrivValidator, it uses a socket to request signatures
|
||||
// Socket errors.
|
||||
var (
|
||||
ErrConnTimeout = errors.New("remote signer timed out")
|
||||
)
|
||||
|
||||
// RemoteSignerClient implements PrivValidator.
|
||||
// It uses a net.Conn to request signatures
|
||||
// from an external process.
|
||||
type RemoteSignerClient struct {
|
||||
conn net.Conn
|
||||
lock sync.Mutex
|
||||
|
||||
// memoized
|
||||
consensusPubKey crypto.PubKey
|
||||
}
|
||||
|
||||
// Check that RemoteSignerClient implements PrivValidator.
|
||||
var _ types.PrivValidator = (*RemoteSignerClient)(nil)
|
||||
|
||||
// NewRemoteSignerClient returns an instance of RemoteSignerClient.
|
||||
func NewRemoteSignerClient(
|
||||
conn net.Conn,
|
||||
) *RemoteSignerClient {
|
||||
sc := &RemoteSignerClient{
|
||||
conn: conn,
|
||||
func NewRemoteSignerClient(conn net.Conn) (*RemoteSignerClient, error) {
|
||||
|
||||
// retrieve and memoize the consensus public key once.
|
||||
pubKey, err := getPubKey(conn)
|
||||
if err != nil {
|
||||
return nil, cmn.ErrorWrap(err, "error while retrieving public key for remote signer")
|
||||
}
|
||||
return sc
|
||||
return &RemoteSignerClient{
|
||||
conn: conn,
|
||||
consensusPubKey: pubKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetAddress implements PrivValidator.
|
||||
func (sc *RemoteSignerClient) GetAddress() types.Address {
|
||||
pubKey, err := sc.getPubKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return pubKey.Address()
|
||||
// Close calls Close on the underlying net.Conn.
|
||||
func (sc *RemoteSignerClient) Close() error {
|
||||
return sc.conn.Close()
|
||||
}
|
||||
|
||||
// GetPubKey implements PrivValidator.
|
||||
func (sc *RemoteSignerClient) GetPubKey() crypto.PubKey {
|
||||
pubKey, err := sc.getPubKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return pubKey
|
||||
return sc.consensusPubKey
|
||||
}
|
||||
|
||||
func (sc *RemoteSignerClient) getPubKey() (crypto.PubKey, error) {
|
||||
sc.lock.Lock()
|
||||
defer sc.lock.Unlock()
|
||||
|
||||
err := writeMsg(sc.conn, &PubKeyMsg{})
|
||||
// not thread-safe (only called on startup).
|
||||
func getPubKey(conn net.Conn) (crypto.PubKey, error) {
|
||||
err := writeMsg(conn, &PubKeyRequest{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := readMsg(sc.conn)
|
||||
res, err := readMsg(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pubKeyResp, ok := res.(*PubKeyResponse)
|
||||
if !ok {
|
||||
return nil, errors.Wrap(ErrUnexpectedResponse, "response is not PubKeyResponse")
|
||||
}
|
||||
|
||||
return res.(*PubKeyMsg).PubKey, nil
|
||||
if pubKeyResp.Error != nil {
|
||||
return nil, errors.Wrap(pubKeyResp.Error, "failed to get private validator's public key")
|
||||
}
|
||||
|
||||
return pubKeyResp.PubKey, nil
|
||||
}
|
||||
|
||||
// SignVote implements PrivValidator.
|
||||
func (sc *RemoteSignerClient) SignVote(chainID string, vote *types.Vote) error {
|
||||
sc.lock.Lock()
|
||||
defer sc.lock.Unlock()
|
||||
|
||||
err := writeMsg(sc.conn, &SignVoteRequest{Vote: vote})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -101,9 +107,6 @@ func (sc *RemoteSignerClient) SignProposal(
|
||||
chainID string,
|
||||
proposal *types.Proposal,
|
||||
) error {
|
||||
sc.lock.Lock()
|
||||
defer sc.lock.Unlock()
|
||||
|
||||
err := writeMsg(sc.conn, &SignProposalRequest{Proposal: proposal})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -127,9 +130,6 @@ func (sc *RemoteSignerClient) SignProposal(
|
||||
|
||||
// Ping is used to check connection health.
|
||||
func (sc *RemoteSignerClient) Ping() error {
|
||||
sc.lock.Lock()
|
||||
defer sc.lock.Unlock()
|
||||
|
||||
err := writeMsg(sc.conn, &PingRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -152,7 +152,8 @@ type RemoteSignerMsg interface{}
|
||||
|
||||
func RegisterRemoteSignerMsg(cdc *amino.Codec) {
|
||||
cdc.RegisterInterface((*RemoteSignerMsg)(nil), nil)
|
||||
cdc.RegisterConcrete(&PubKeyMsg{}, "tendermint/remotesigner/PubKeyMsg", nil)
|
||||
cdc.RegisterConcrete(&PubKeyRequest{}, "tendermint/remotesigner/PubKeyRequest", nil)
|
||||
cdc.RegisterConcrete(&PubKeyResponse{}, "tendermint/remotesigner/PubKeyResponse", nil)
|
||||
cdc.RegisterConcrete(&SignVoteRequest{}, "tendermint/remotesigner/SignVoteRequest", nil)
|
||||
cdc.RegisterConcrete(&SignedVoteResponse{}, "tendermint/remotesigner/SignedVoteResponse", nil)
|
||||
cdc.RegisterConcrete(&SignProposalRequest{}, "tendermint/remotesigner/SignProposalRequest", nil)
|
||||
@@ -161,9 +162,13 @@ func RegisterRemoteSignerMsg(cdc *amino.Codec) {
|
||||
cdc.RegisterConcrete(&PingResponse{}, "tendermint/remotesigner/PingResponse", nil)
|
||||
}
|
||||
|
||||
// PubKeyMsg is a PrivValidatorSocket message containing the public key.
|
||||
type PubKeyMsg struct {
|
||||
// PubKeyRequest requests the consensus public key from the remote signer.
|
||||
type PubKeyRequest struct{}
|
||||
|
||||
// PubKeyResponse is a PrivValidatorSocket message containing the public key.
|
||||
type PubKeyResponse struct {
|
||||
PubKey crypto.PubKey
|
||||
Error *RemoteSignerError
|
||||
}
|
||||
|
||||
// SignVoteRequest is a PrivValidatorSocket message containing a vote.
|
||||
@@ -227,10 +232,10 @@ func handleRequest(req RemoteSignerMsg, chainID string, privVal types.PrivValida
|
||||
var err error
|
||||
|
||||
switch r := req.(type) {
|
||||
case *PubKeyMsg:
|
||||
case *PubKeyRequest:
|
||||
var p crypto.PubKey
|
||||
p = privVal.GetPubKey()
|
||||
res = &PubKeyMsg{p}
|
||||
res = &PubKeyResponse{p, nil}
|
||||
case *SignVoteRequest:
|
||||
err = privVal.SignVote(chainID, r.Vote)
|
||||
if err != nil {
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
@@ -12,6 +13,11 @@ import (
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// Socket errors.
|
||||
var (
|
||||
ErrDialRetryMax = errors.New("dialed maximum retries")
|
||||
)
|
||||
|
||||
// RemoteSignerOption sets an optional parameter on the RemoteSigner.
|
||||
type RemoteSignerOption func(*RemoteSigner)
|
||||
|
||||
@@ -26,38 +32,64 @@ func RemoteSignerConnRetries(retries int) RemoteSignerOption {
|
||||
return func(ss *RemoteSigner) { ss.connRetries = retries }
|
||||
}
|
||||
|
||||
// RemoteSigner implements PrivValidator by dialing to a socket.
|
||||
// RemoteSigner dials using its dialer and responds to any
|
||||
// signature requests using its privVal.
|
||||
type RemoteSigner struct {
|
||||
cmn.BaseService
|
||||
|
||||
addr string
|
||||
chainID string
|
||||
connDeadline time.Duration
|
||||
connRetries int
|
||||
privKey ed25519.PrivKeyEd25519
|
||||
privVal types.PrivValidator
|
||||
|
||||
conn net.Conn
|
||||
dialer Dialer
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
// NewRemoteSigner returns an instance of RemoteSigner.
|
||||
// Dialer dials a remote address and returns a net.Conn or an error.
|
||||
type Dialer func() (net.Conn, error)
|
||||
|
||||
// DialTCPFn dials the given tcp addr, using the given connTimeout and privKey for the
|
||||
// authenticated encryption handshake.
|
||||
func DialTCPFn(addr string, connTimeout time.Duration, privKey ed25519.PrivKeyEd25519) Dialer {
|
||||
return func() (net.Conn, error) {
|
||||
conn, err := cmn.Connect(addr)
|
||||
if err == nil {
|
||||
err = conn.SetDeadline(time.Now().Add(connTimeout))
|
||||
}
|
||||
if err == nil {
|
||||
conn, err = p2pconn.MakeSecretConnection(conn, privKey)
|
||||
}
|
||||
return conn, err
|
||||
}
|
||||
}
|
||||
|
||||
// DialUnixFn dials the given unix socket.
|
||||
func DialUnixFn(addr string) Dialer {
|
||||
return func() (net.Conn, error) {
|
||||
unixAddr := &net.UnixAddr{addr, "unix"}
|
||||
return net.DialUnix("unix", nil, unixAddr)
|
||||
}
|
||||
}
|
||||
|
||||
// NewRemoteSigner return a RemoteSigner that will dial using the given
|
||||
// dialer and respond to any signature requests over the connection
|
||||
// using the given privVal.
|
||||
func NewRemoteSigner(
|
||||
logger log.Logger,
|
||||
chainID, socketAddr string,
|
||||
chainID string,
|
||||
privVal types.PrivValidator,
|
||||
privKey ed25519.PrivKeyEd25519,
|
||||
dialer Dialer,
|
||||
) *RemoteSigner {
|
||||
rs := &RemoteSigner{
|
||||
addr: socketAddr,
|
||||
chainID: chainID,
|
||||
connDeadline: time.Second * defaultConnDeadlineSeconds,
|
||||
connRetries: defaultDialRetries,
|
||||
privKey: privKey,
|
||||
privVal: privVal,
|
||||
dialer: dialer,
|
||||
}
|
||||
|
||||
rs.BaseService = *cmn.NewBaseService(logger, "RemoteSigner", rs)
|
||||
|
||||
return rs
|
||||
}
|
||||
|
||||
@@ -68,6 +100,7 @@ func (rs *RemoteSigner) OnStart() error {
|
||||
rs.Logger.Error("OnStart", "err", err)
|
||||
return err
|
||||
}
|
||||
rs.conn = conn
|
||||
|
||||
go rs.handleConnection(conn)
|
||||
|
||||
@@ -91,36 +124,11 @@ func (rs *RemoteSigner) connect() (net.Conn, error) {
|
||||
if retries != rs.connRetries {
|
||||
time.Sleep(rs.connDeadline)
|
||||
}
|
||||
|
||||
conn, err := cmn.Connect(rs.addr)
|
||||
conn, err := rs.dialer()
|
||||
if err != nil {
|
||||
rs.Logger.Error(
|
||||
"connect",
|
||||
"addr", rs.addr,
|
||||
"err", err,
|
||||
)
|
||||
|
||||
rs.Logger.Error("dialing", "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := conn.SetDeadline(time.Now().Add(connTimeout)); err != nil {
|
||||
rs.Logger.Error(
|
||||
"connect",
|
||||
"err", err,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
conn, err = p2pconn.MakeSecretConnection(conn, rs.privKey)
|
||||
if err != nil {
|
||||
rs.Logger.Error(
|
||||
"connect",
|
||||
"err", err,
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
@@ -139,7 +147,7 @@ func (rs *RemoteSigner) handleConnection(conn net.Conn) {
|
||||
req, err := readMsg(conn)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
rs.Logger.Error("handleConnection", "err", err)
|
||||
rs.Logger.Error("handleConnection readMsg", "err", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -148,12 +156,12 @@ func (rs *RemoteSigner) handleConnection(conn net.Conn) {
|
||||
|
||||
if err != nil {
|
||||
// only log the error; we'll reply with an error in res
|
||||
rs.Logger.Error("handleConnection", "err", err)
|
||||
rs.Logger.Error("handleConnection handleRequest", "err", err)
|
||||
}
|
||||
|
||||
err = writeMsg(conn, res)
|
||||
if err != nil {
|
||||
rs.Logger.Error("handleConnection", "err", err)
|
||||
rs.Logger.Error("handleConnection writeMsg", "err", err)
|
||||
return
|
||||
}
|
||||
}
|
184
privval/socket.go
Normal file
184
privval/socket.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package privval
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
p2pconn "github.com/tendermint/tendermint/p2p/conn"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultAcceptDeadlineSeconds = 3
|
||||
defaultConnDeadlineSeconds = 3
|
||||
)
|
||||
|
||||
// timeoutError can be used to check if an error returned from the netp package
|
||||
// was due to a timeout.
|
||||
type timeoutError interface {
|
||||
Timeout() bool
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// TCP Listener
|
||||
|
||||
// TCPListenerOption sets an optional parameter on the tcpListener.
|
||||
type TCPListenerOption func(*tcpListener)
|
||||
|
||||
// TCPListenerAcceptDeadline sets the deadline for the listener.
|
||||
// A zero time value disables the deadline.
|
||||
func TCPListenerAcceptDeadline(deadline time.Duration) TCPListenerOption {
|
||||
return func(tl *tcpListener) { tl.acceptDeadline = deadline }
|
||||
}
|
||||
|
||||
// TCPListenerConnDeadline sets the read and write deadline for connections
|
||||
// from external signing processes.
|
||||
func TCPListenerConnDeadline(deadline time.Duration) TCPListenerOption {
|
||||
return func(tl *tcpListener) { tl.connDeadline = deadline }
|
||||
}
|
||||
|
||||
// tcpListener implements net.Listener.
|
||||
var _ net.Listener = (*tcpListener)(nil)
|
||||
|
||||
// tcpListener wraps a *net.TCPListener to standardise protocol timeouts
|
||||
// and potentially other tuning parameters. It also returns encrypted connections.
|
||||
type tcpListener struct {
|
||||
*net.TCPListener
|
||||
|
||||
secretConnKey ed25519.PrivKeyEd25519
|
||||
|
||||
acceptDeadline time.Duration
|
||||
connDeadline time.Duration
|
||||
}
|
||||
|
||||
// NewTCPListener returns a listener that accepts authenticated encrypted connections
|
||||
// using the given secretConnKey and the default timeout values.
|
||||
func NewTCPListener(ln net.Listener, secretConnKey ed25519.PrivKeyEd25519) *tcpListener {
|
||||
return &tcpListener{
|
||||
TCPListener: ln.(*net.TCPListener),
|
||||
secretConnKey: secretConnKey,
|
||||
acceptDeadline: time.Second * defaultAcceptDeadlineSeconds,
|
||||
connDeadline: time.Second * defaultConnDeadlineSeconds,
|
||||
}
|
||||
}
|
||||
|
||||
// Accept implements net.Listener.
|
||||
func (ln *tcpListener) Accept() (net.Conn, error) {
|
||||
err := ln.SetDeadline(time.Now().Add(ln.acceptDeadline))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tc, err := ln.AcceptTCP()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wrap the conn in our timeout and encryption wrappers
|
||||
timeoutConn := newTimeoutConn(tc, ln.connDeadline)
|
||||
secretConn, err := p2pconn.MakeSecretConnection(timeoutConn, ln.secretConnKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return secretConn, nil
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Unix Listener
|
||||
|
||||
// unixListener implements net.Listener.
|
||||
var _ net.Listener = (*unixListener)(nil)
|
||||
|
||||
type UnixListenerOption func(*unixListener)
|
||||
|
||||
// UnixListenerAcceptDeadline sets the deadline for the listener.
|
||||
// A zero time value disables the deadline.
|
||||
func UnixListenerAcceptDeadline(deadline time.Duration) UnixListenerOption {
|
||||
return func(ul *unixListener) { ul.acceptDeadline = deadline }
|
||||
}
|
||||
|
||||
// UnixListenerConnDeadline sets the read and write deadline for connections
|
||||
// from external signing processes.
|
||||
func UnixListenerConnDeadline(deadline time.Duration) UnixListenerOption {
|
||||
return func(ul *unixListener) { ul.connDeadline = deadline }
|
||||
}
|
||||
|
||||
// unixListener wraps a *net.UnixListener to standardise protocol timeouts
|
||||
// and potentially other tuning parameters. It returns unencrypted connections.
|
||||
type unixListener struct {
|
||||
*net.UnixListener
|
||||
|
||||
acceptDeadline time.Duration
|
||||
connDeadline time.Duration
|
||||
}
|
||||
|
||||
// NewUnixListener returns a listener that accepts unencrypted connections
|
||||
// using the default timeout values.
|
||||
func NewUnixListener(ln net.Listener) *unixListener {
|
||||
return &unixListener{
|
||||
UnixListener: ln.(*net.UnixListener),
|
||||
acceptDeadline: time.Second * defaultAcceptDeadlineSeconds,
|
||||
connDeadline: time.Second * defaultConnDeadlineSeconds,
|
||||
}
|
||||
}
|
||||
|
||||
// Accept implements net.Listener.
|
||||
func (ln *unixListener) Accept() (net.Conn, error) {
|
||||
err := ln.SetDeadline(time.Now().Add(ln.acceptDeadline))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tc, err := ln.AcceptUnix()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wrap the conn in our timeout wrapper
|
||||
conn := newTimeoutConn(tc, ln.connDeadline)
|
||||
|
||||
// TODO: wrap in something that authenticates
|
||||
// with a MAC - https://github.com/tendermint/tendermint/issues/3099
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Connection
|
||||
|
||||
// timeoutConn implements net.Conn.
|
||||
var _ net.Conn = (*timeoutConn)(nil)
|
||||
|
||||
// timeoutConn wraps a net.Conn to standardise protocol timeouts / deadline resets.
|
||||
type timeoutConn struct {
|
||||
net.Conn
|
||||
|
||||
connDeadline time.Duration
|
||||
}
|
||||
|
||||
// newTimeoutConn returns an instance of newTCPTimeoutConn.
|
||||
func newTimeoutConn(
|
||||
conn net.Conn,
|
||||
connDeadline time.Duration) *timeoutConn {
|
||||
return &timeoutConn{
|
||||
conn,
|
||||
connDeadline,
|
||||
}
|
||||
}
|
||||
|
||||
// Read implements net.Conn.
|
||||
func (c timeoutConn) Read(b []byte) (n int, err error) {
|
||||
// Reset deadline
|
||||
c.Conn.SetReadDeadline(time.Now().Add(c.connDeadline))
|
||||
|
||||
return c.Conn.Read(b)
|
||||
}
|
||||
|
||||
// Write implements net.Conn.
|
||||
func (c timeoutConn) Write(b []byte) (n int, err error) {
|
||||
// Reset deadline
|
||||
c.Conn.SetWriteDeadline(time.Now().Add(c.connDeadline))
|
||||
|
||||
return c.Conn.Write(b)
|
||||
}
|
@@ -4,17 +4,31 @@ import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
)
|
||||
|
||||
func TestTCPTimeoutListenerAcceptDeadline(t *testing.T) {
|
||||
//-------------------------------------------
|
||||
// helper funcs
|
||||
|
||||
func newPrivKey() ed25519.PrivKeyEd25519 {
|
||||
return ed25519.GenPrivKey()
|
||||
}
|
||||
|
||||
//-------------------------------------------
|
||||
// tests
|
||||
|
||||
func TestTCPListenerAcceptDeadline(t *testing.T) {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ln = newTCPTimeoutListener(ln, time.Millisecond, time.Second, time.Second)
|
||||
tcpLn := NewTCPListener(ln, newPrivKey())
|
||||
TCPListenerAcceptDeadline(time.Millisecond)(tcpLn)
|
||||
TCPListenerConnDeadline(time.Second)(tcpLn)
|
||||
|
||||
_, err = ln.Accept()
|
||||
_, err = tcpLn.Accept()
|
||||
opErr, ok := err.(*net.OpError)
|
||||
if !ok {
|
||||
t.Fatalf("have %v, want *net.OpError", err)
|
||||
@@ -25,14 +39,17 @@ func TestTCPTimeoutListenerAcceptDeadline(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTCPTimeoutListenerConnDeadline(t *testing.T) {
|
||||
func TestTCPListenerConnDeadline(t *testing.T) {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ln = newTCPTimeoutListener(ln, time.Second, time.Millisecond, time.Second)
|
||||
tcpLn := NewTCPListener(ln, newPrivKey())
|
||||
TCPListenerAcceptDeadline(time.Second)(tcpLn)
|
||||
TCPListenerConnDeadline(time.Millisecond)(tcpLn)
|
||||
|
||||
readyc := make(chan struct{})
|
||||
donec := make(chan struct{})
|
||||
go func(ln net.Listener) {
|
||||
defer close(donec)
|
||||
@@ -41,6 +58,7 @@ func TestTCPTimeoutListenerConnDeadline(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
<-readyc
|
||||
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
|
||||
@@ -54,12 +72,13 @@ func TestTCPTimeoutListenerConnDeadline(t *testing.T) {
|
||||
if have, want := opErr.Op, "read"; have != want {
|
||||
t.Errorf("have %v, want %v", have, want)
|
||||
}
|
||||
}(ln)
|
||||
}(tcpLn)
|
||||
|
||||
_, err = net.Dial("tcp", ln.Addr().String())
|
||||
dialer := DialTCPFn(ln.Addr().String(), testConnDeadline, newPrivKey())
|
||||
_, err = dialer()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
close(readyc)
|
||||
<-donec
|
||||
}
|
214
privval/tcp.go
214
privval/tcp.go
@@ -1,214 +0,0 @@
|
||||
package privval
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
p2pconn "github.com/tendermint/tendermint/p2p/conn"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultAcceptDeadlineSeconds = 3
|
||||
defaultConnDeadlineSeconds = 3
|
||||
defaultConnHeartBeatSeconds = 2
|
||||
defaultDialRetries = 10
|
||||
)
|
||||
|
||||
// Socket errors.
|
||||
var (
|
||||
ErrDialRetryMax = errors.New("dialed maximum retries")
|
||||
ErrConnTimeout = errors.New("remote signer timed out")
|
||||
ErrUnexpectedResponse = errors.New("received unexpected response")
|
||||
)
|
||||
|
||||
var (
|
||||
acceptDeadline = time.Second * defaultAcceptDeadlineSeconds
|
||||
connTimeout = time.Second * defaultConnDeadlineSeconds
|
||||
connHeartbeat = time.Second * defaultConnHeartBeatSeconds
|
||||
)
|
||||
|
||||
// TCPValOption sets an optional parameter on the SocketPV.
|
||||
type TCPValOption func(*TCPVal)
|
||||
|
||||
// TCPValAcceptDeadline sets the deadline for the TCPVal listener.
|
||||
// A zero time value disables the deadline.
|
||||
func TCPValAcceptDeadline(deadline time.Duration) TCPValOption {
|
||||
return func(sc *TCPVal) { sc.acceptDeadline = deadline }
|
||||
}
|
||||
|
||||
// TCPValConnTimeout sets the read and write timeout for connections
|
||||
// from external signing processes.
|
||||
func TCPValConnTimeout(timeout time.Duration) TCPValOption {
|
||||
return func(sc *TCPVal) { sc.connTimeout = timeout }
|
||||
}
|
||||
|
||||
// TCPValHeartbeat sets the period on which to check the liveness of the
|
||||
// connected Signer connections.
|
||||
func TCPValHeartbeat(period time.Duration) TCPValOption {
|
||||
return func(sc *TCPVal) { sc.connHeartbeat = period }
|
||||
}
|
||||
|
||||
// TCPVal implements PrivValidator, it uses a socket to request signatures
|
||||
// from an external process.
|
||||
type TCPVal struct {
|
||||
cmn.BaseService
|
||||
*RemoteSignerClient
|
||||
|
||||
addr string
|
||||
acceptDeadline time.Duration
|
||||
connTimeout time.Duration
|
||||
connHeartbeat time.Duration
|
||||
privKey ed25519.PrivKeyEd25519
|
||||
|
||||
conn net.Conn
|
||||
listener net.Listener
|
||||
cancelPing chan struct{}
|
||||
pingTicker *time.Ticker
|
||||
}
|
||||
|
||||
// Check that TCPVal implements PrivValidator.
|
||||
var _ types.PrivValidator = (*TCPVal)(nil)
|
||||
|
||||
// NewTCPVal returns an instance of TCPVal.
|
||||
func NewTCPVal(
|
||||
logger log.Logger,
|
||||
socketAddr string,
|
||||
privKey ed25519.PrivKeyEd25519,
|
||||
) *TCPVal {
|
||||
sc := &TCPVal{
|
||||
addr: socketAddr,
|
||||
acceptDeadline: acceptDeadline,
|
||||
connTimeout: connTimeout,
|
||||
connHeartbeat: connHeartbeat,
|
||||
privKey: privKey,
|
||||
}
|
||||
|
||||
sc.BaseService = *cmn.NewBaseService(logger, "TCPVal", sc)
|
||||
|
||||
return sc
|
||||
}
|
||||
|
||||
// OnStart implements cmn.Service.
|
||||
func (sc *TCPVal) OnStart() error {
|
||||
if err := sc.listen(); err != nil {
|
||||
sc.Logger.Error("OnStart", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
conn, err := sc.waitConnection()
|
||||
if err != nil {
|
||||
sc.Logger.Error("OnStart", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
sc.conn = conn
|
||||
|
||||
sc.RemoteSignerClient = NewRemoteSignerClient(sc.conn)
|
||||
|
||||
// Start a routine to keep the connection alive
|
||||
sc.cancelPing = make(chan struct{}, 1)
|
||||
sc.pingTicker = time.NewTicker(sc.connHeartbeat)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-sc.pingTicker.C:
|
||||
err := sc.Ping()
|
||||
if err != nil {
|
||||
sc.Logger.Error(
|
||||
"Ping",
|
||||
"err", err,
|
||||
)
|
||||
}
|
||||
case <-sc.cancelPing:
|
||||
sc.pingTicker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnStop implements cmn.Service.
|
||||
func (sc *TCPVal) OnStop() {
|
||||
if sc.cancelPing != nil {
|
||||
close(sc.cancelPing)
|
||||
}
|
||||
|
||||
if sc.conn != nil {
|
||||
if err := sc.conn.Close(); err != nil {
|
||||
sc.Logger.Error("OnStop", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
if sc.listener != nil {
|
||||
if err := sc.listener.Close(); err != nil {
|
||||
sc.Logger.Error("OnStop", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *TCPVal) acceptConnection() (net.Conn, error) {
|
||||
conn, err := sc.listener.Accept()
|
||||
if err != nil {
|
||||
if !sc.IsRunning() {
|
||||
return nil, nil // Ignore error from listener closing.
|
||||
}
|
||||
return nil, err
|
||||
|
||||
}
|
||||
|
||||
conn, err = p2pconn.MakeSecretConnection(conn, sc.privKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (sc *TCPVal) listen() error {
|
||||
ln, err := net.Listen(cmn.ProtocolAndAddress(sc.addr))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sc.listener = newTCPTimeoutListener(
|
||||
ln,
|
||||
sc.acceptDeadline,
|
||||
sc.connTimeout,
|
||||
sc.connHeartbeat,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// waitConnection uses the configured wait timeout to error if no external
|
||||
// process connects in the time period.
|
||||
func (sc *TCPVal) waitConnection() (net.Conn, error) {
|
||||
var (
|
||||
connc = make(chan net.Conn, 1)
|
||||
errc = make(chan error, 1)
|
||||
)
|
||||
|
||||
go func(connc chan<- net.Conn, errc chan<- error) {
|
||||
conn, err := sc.acceptConnection()
|
||||
if err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
|
||||
connc <- conn
|
||||
}(connc, errc)
|
||||
|
||||
select {
|
||||
case conn := <-connc:
|
||||
return conn, nil
|
||||
case err := <-errc:
|
||||
return nil, err
|
||||
}
|
||||
}
|
@@ -1,90 +0,0 @@
|
||||
package privval
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// timeoutError can be used to check if an error returned from the netp package
|
||||
// was due to a timeout.
|
||||
type timeoutError interface {
|
||||
Timeout() bool
|
||||
}
|
||||
|
||||
// tcpTimeoutListener implements net.Listener.
|
||||
var _ net.Listener = (*tcpTimeoutListener)(nil)
|
||||
|
||||
// tcpTimeoutListener wraps a *net.TCPListener to standardise protocol timeouts
|
||||
// and potentially other tuning parameters.
|
||||
type tcpTimeoutListener struct {
|
||||
*net.TCPListener
|
||||
|
||||
acceptDeadline time.Duration
|
||||
connDeadline time.Duration
|
||||
period time.Duration
|
||||
}
|
||||
|
||||
// timeoutConn wraps a net.Conn to standardise protocol timeouts / deadline resets.
|
||||
type timeoutConn struct {
|
||||
net.Conn
|
||||
|
||||
connDeadline time.Duration
|
||||
}
|
||||
|
||||
// newTCPTimeoutListener returns an instance of tcpTimeoutListener.
|
||||
func newTCPTimeoutListener(
|
||||
ln net.Listener,
|
||||
acceptDeadline, connDeadline time.Duration,
|
||||
period time.Duration,
|
||||
) tcpTimeoutListener {
|
||||
return tcpTimeoutListener{
|
||||
TCPListener: ln.(*net.TCPListener),
|
||||
acceptDeadline: acceptDeadline,
|
||||
connDeadline: connDeadline,
|
||||
period: period,
|
||||
}
|
||||
}
|
||||
|
||||
// newTimeoutConn returns an instance of newTCPTimeoutConn.
|
||||
func newTimeoutConn(
|
||||
conn net.Conn,
|
||||
connDeadline time.Duration) *timeoutConn {
|
||||
return &timeoutConn{
|
||||
conn,
|
||||
connDeadline,
|
||||
}
|
||||
}
|
||||
|
||||
// Accept implements net.Listener.
|
||||
func (ln tcpTimeoutListener) Accept() (net.Conn, error) {
|
||||
err := ln.SetDeadline(time.Now().Add(ln.acceptDeadline))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tc, err := ln.AcceptTCP()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wrap the conn in our timeout wrapper
|
||||
conn := newTimeoutConn(tc, ln.connDeadline)
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Read implements net.Listener.
|
||||
func (c timeoutConn) Read(b []byte) (n int, err error) {
|
||||
// Reset deadline
|
||||
c.Conn.SetReadDeadline(time.Now().Add(c.connDeadline))
|
||||
|
||||
return c.Conn.Read(b)
|
||||
}
|
||||
|
||||
// Write implements net.Listener.
|
||||
func (c timeoutConn) Write(b []byte) (n int, err error) {
|
||||
// Reset deadline
|
||||
c.Conn.SetWriteDeadline(time.Now().Add(c.connDeadline))
|
||||
|
||||
return c.Conn.Write(b)
|
||||
}
|
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
abcicli "github.com/tendermint/tendermint/abci/client"
|
||||
"github.com/tendermint/tendermint/abci/example/counter"
|
||||
"github.com/tendermint/tendermint/abci/example/kvstore"
|
||||
"github.com/tendermint/tendermint/abci/types"
|
||||
)
|
||||
@@ -64,15 +65,15 @@ func (r *remoteClientCreator) NewABCIClient() (abcicli.Client, error) {
|
||||
|
||||
func DefaultClientCreator(addr, transport, dbDir string) ClientCreator {
|
||||
switch addr {
|
||||
case "counter":
|
||||
return NewLocalClientCreator(counter.NewCounterApplication(false))
|
||||
case "counter_serial":
|
||||
return NewLocalClientCreator(counter.NewCounterApplication(true))
|
||||
case "kvstore":
|
||||
fallthrough
|
||||
case "dummy":
|
||||
return NewLocalClientCreator(kvstore.NewKVStoreApplication())
|
||||
case "persistent_kvstore":
|
||||
fallthrough
|
||||
case "persistent_dummy":
|
||||
return NewLocalClientCreator(kvstore.NewPersistentKVStoreApplication(dbDir))
|
||||
case "nilapp":
|
||||
case "noop":
|
||||
return NewLocalClientCreator(types.NewBaseApplication())
|
||||
default:
|
||||
mustConnect := false // loop retrying
|
||||
|
@@ -109,6 +109,24 @@ func (c *HTTP) broadcastTX(route string, tx types.Tx) (*ctypes.ResultBroadcastTx
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *HTTP) UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error) {
|
||||
result := new(ctypes.ResultUnconfirmedTxs)
|
||||
_, err := c.rpc.Call("unconfirmed_txs", map[string]interface{}{"limit": limit}, result)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unconfirmed_txs")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *HTTP) NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error) {
|
||||
result := new(ctypes.ResultUnconfirmedTxs)
|
||||
_, err := c.rpc.Call("num_unconfirmed_txs", map[string]interface{}{}, result)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "num_unconfirmed_txs")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *HTTP) NetInfo() (*ctypes.ResultNetInfo, error) {
|
||||
result := new(ctypes.ResultNetInfo)
|
||||
_, err := c.rpc.Call("net_info", map[string]interface{}{}, result)
|
||||
|
@@ -93,3 +93,9 @@ type NetworkClient interface {
|
||||
type EventsClient interface {
|
||||
types.EventBusSubscriber
|
||||
}
|
||||
|
||||
// MempoolClient shows us data about current mempool state.
|
||||
type MempoolClient interface {
|
||||
UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error)
|
||||
NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error)
|
||||
}
|
||||
|
@@ -76,6 +76,14 @@ func (Local) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
|
||||
return core.BroadcastTxSync(tx)
|
||||
}
|
||||
|
||||
func (Local) UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error) {
|
||||
return core.UnconfirmedTxs(limit)
|
||||
}
|
||||
|
||||
func (Local) NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error) {
|
||||
return core.NumUnconfirmedTxs()
|
||||
}
|
||||
|
||||
func (Local) NetInfo() (*ctypes.ResultNetInfo, error) {
|
||||
return core.NetInfo()
|
||||
}
|
||||
|
@@ -281,6 +281,42 @@ func TestBroadcastTxCommit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnconfirmedTxs(t *testing.T) {
|
||||
_, _, tx := MakeTxKV()
|
||||
|
||||
mempool := node.MempoolReactor().Mempool
|
||||
_ = mempool.CheckTx(tx, nil)
|
||||
|
||||
for i, c := range GetClients() {
|
||||
mc, ok := c.(client.MempoolClient)
|
||||
require.True(t, ok, "%d", i)
|
||||
txs, err := mc.UnconfirmedTxs(1)
|
||||
require.Nil(t, err, "%d: %+v", i, err)
|
||||
assert.Exactly(t, types.Txs{tx}, types.Txs(txs.Txs))
|
||||
}
|
||||
|
||||
mempool.Flush()
|
||||
}
|
||||
|
||||
func TestNumUnconfirmedTxs(t *testing.T) {
|
||||
_, _, tx := MakeTxKV()
|
||||
|
||||
mempool := node.MempoolReactor().Mempool
|
||||
_ = mempool.CheckTx(tx, nil)
|
||||
mempoolSize := mempool.Size()
|
||||
|
||||
for i, c := range GetClients() {
|
||||
mc, ok := c.(client.MempoolClient)
|
||||
require.True(t, ok, "%d", i)
|
||||
res, err := mc.NumUnconfirmedTxs()
|
||||
require.Nil(t, err, "%d: %+v", i, err)
|
||||
|
||||
assert.Equal(t, mempoolSize, res.N)
|
||||
}
|
||||
|
||||
mempool.Flush()
|
||||
}
|
||||
|
||||
func TestTx(t *testing.T) {
|
||||
// first we broadcast a tx
|
||||
c := getHTTPClient()
|
||||
@@ -392,5 +428,10 @@ func TestTxSearch(t *testing.T) {
|
||||
if len(result.Txs) == 0 {
|
||||
t.Fatal("expected a lot of transactions")
|
||||
}
|
||||
|
||||
// query a non existing tx with page 1 and txsPerPage 1
|
||||
result, err = c.TxSearch("app.creator='Cosmoshi Neetowoko'", true, 1, 1)
|
||||
require.Nil(t, err, "%+v", err)
|
||||
require.Len(t, result.Txs, 0)
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user