mirror of
https://github.com/fluencelabs/tendermint
synced 2025-06-15 22:31:21 +00:00
Merge pull request #3135 from tendermint/release/v0.28.0
Release/v0.28.0
This commit is contained in:
@ -3,7 +3,7 @@ version: 2
|
|||||||
defaults: &defaults
|
defaults: &defaults
|
||||||
working_directory: /go/src/github.com/tendermint/tendermint
|
working_directory: /go/src/github.com/tendermint/tendermint
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.10.3
|
- image: circleci/golang:1.11.4
|
||||||
environment:
|
environment:
|
||||||
GOBIN: /tmp/workspace/bin
|
GOBIN: /tmp/workspace/bin
|
||||||
|
|
||||||
|
58
CHANGELOG.md
58
CHANGELOG.md
@ -1,5 +1,60 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v0.28.0
|
||||||
|
|
||||||
|
*January 16th, 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.
|
||||||
|
|
||||||
|
**Note:** Please backup your existing `priv_validator.json` before using this
|
||||||
|
version.
|
||||||
|
|
||||||
|
See [UPGRADING.md](UPGRADING.md) for more details.
|
||||||
|
|
||||||
|
### BREAKING CHANGES:
|
||||||
|
|
||||||
|
* CLI/RPC/Config
|
||||||
|
- [cli] Removed `--proxy_app=dummy` option. Use `kvstore` (`persistent_kvstore`) instead.
|
||||||
|
- [cli] Renamed `--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 `priv_validator.json` into immutable (`config/priv_validator_key.json`) and mutable (`data/priv_validator_state.json`) parts (@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 specification for 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:
|
||||||
|
|
||||||
|
- [build] [\#3085](https://github.com/tendermint/tendermint/issues/3085) Fix `Version` field in build scripts (@husio)
|
||||||
|
- [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
|
||||||
|
- [p2p/conn] [\#3111](https://github.com/tendermint/tendermint/issues/3111) Make SecretConnection thread safe
|
||||||
|
- [rpc] [\#3053](https://github.com/tendermint/tendermint/issues/3053) Fix internal error in `/tx_search` when results are empty
|
||||||
|
(@gianfelipe93)
|
||||||
|
- [types] [\#2926](https://github.com/tendermint/tendermint/issues/2926) Do not panic if retrieving the privval's public key fails
|
||||||
|
|
||||||
## v0.27.4
|
## v0.27.4
|
||||||
|
|
||||||
*December 21st, 2018*
|
*December 21st, 2018*
|
||||||
@ -17,8 +72,7 @@
|
|||||||
### BREAKING CHANGES:
|
### BREAKING CHANGES:
|
||||||
|
|
||||||
* Go API
|
* Go API
|
||||||
|
- [dep] [\#3027](https://github.com/tendermint/tendermint/issues/3027) Revert to mainline Go crypto library, eliminating the modified
|
||||||
- [dep] [\#3027](https://github.com/tendermint/tendermint/issues/3027) Revert to mainline Go crypto library, eliminating the modified
|
|
||||||
`bcrypt.GenerateFromPassword`
|
`bcrypt.GenerateFromPassword`
|
||||||
|
|
||||||
## v0.27.2
|
## v0.27.2
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
## v0.27.4
|
## v0.29.0
|
||||||
|
|
||||||
*TBD*
|
*TBD*
|
||||||
|
|
||||||
@ -21,4 +21,3 @@ Special thanks to external contributors on this release:
|
|||||||
### IMPROVEMENTS:
|
### IMPROVEMENTS:
|
||||||
|
|
||||||
### BUG FIXES:
|
### BUG FIXES:
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ set -e
|
|||||||
|
|
||||||
# Get the tag from the version, or try to figure it out.
|
# Get the tag from the version, or try to figure it out.
|
||||||
if [ -z "$TAG" ]; then
|
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
|
fi
|
||||||
if [ -z "$TAG" ]; then
|
if [ -z "$TAG" ]; then
|
||||||
echo "Please specify a tag."
|
echo "Please specify a tag."
|
||||||
|
@ -3,7 +3,7 @@ set -e
|
|||||||
|
|
||||||
# Get the tag from the version, or try to figure it out.
|
# Get the tag from the version, or try to figure it out.
|
||||||
if [ -z "$TAG" ]; then
|
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
|
fi
|
||||||
if [ -z "$TAG" ]; then
|
if [ -z "$TAG" ]; then
|
||||||
echo "Please specify a tag."
|
echo "Please specify a tag."
|
||||||
|
95
README.md
95
README.md
@ -1,8 +1,8 @@
|
|||||||
# Tendermint
|
# Tendermint
|
||||||
|
|
||||||
[Byzantine-Fault Tolerant](https://en.wikipedia.org/wiki/Byzantine_fault_tolerance)
|
[Byzantine-Fault Tolerant](https://en.wikipedia.org/wiki/Byzantine_fault_tolerance)
|
||||||
[State Machine Replication](https://en.wikipedia.org/wiki/State_machine_replication).
|
[State Machines](https://en.wikipedia.org/wiki/State_machine_replication).
|
||||||
Or [Blockchain](https://en.wikipedia.org/wiki/Blockchain_(database)) for short.
|
Or [Blockchain](https://en.wikipedia.org/wiki/Blockchain_(database)), for short.
|
||||||
|
|
||||||
[](https://github.com/tendermint/tendermint/releases/latest)
|
[](https://github.com/tendermint/tendermint/releases/latest)
|
||||||
[
|
|||||||
- [Remote cluster using terraform and ansible](/docs/networks/terraform-and-ansible.md)
|
- [Remote cluster using terraform and ansible](/docs/networks/terraform-and-ansible.md)
|
||||||
- [Join the Cosmos testnet](https://cosmos.network/testnet)
|
- [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
|
## 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
|
## 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
|
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
|
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
|
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)).
|
(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).
|
|
||||||
|
49
UPGRADING.md
49
UPGRADING.md
@ -3,6 +3,55 @@
|
|||||||
This guide provides steps to be followed when you upgrade your applications to
|
This guide provides steps to be followed when you upgrade your applications to
|
||||||
a newer version of Tendermint Core.
|
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 nor the
|
||||||
|
P2PProtocol have changed).
|
||||||
|
|
||||||
|
Please read carefully for details about upgrading.
|
||||||
|
|
||||||
|
**Note:** 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
|
||||||
|
message 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 separate `Request` and `Response` types
|
||||||
|
for consistency with other messages.
|
||||||
|
|
||||||
|
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
|
## v0.27.0
|
||||||
|
|
||||||
This release contains some breaking changes to the block and p2p protocols,
|
This release contains some breaking changes to the block and p2p protocols,
|
||||||
|
@ -58,7 +58,7 @@ var RootCmd = &cobra.Command{
|
|||||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
switch cmd.Use {
|
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
|
return nil
|
||||||
case "version": // skip running for version command
|
case "version": // skip running for version command
|
||||||
return nil
|
return nil
|
||||||
@ -127,10 +127,6 @@ func addCounterFlags() {
|
|||||||
counterCmd.PersistentFlags().BoolVarP(&flagSerial, "serial", "", false, "enforce incrementing (serial) transactions")
|
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() {
|
func addKVStoreFlags() {
|
||||||
kvstoreCmd.PersistentFlags().StringVarP(&flagPersist, "persist", "", "", "directory to use for a database")
|
kvstoreCmd.PersistentFlags().StringVarP(&flagPersist, "persist", "", "", "directory to use for a database")
|
||||||
}
|
}
|
||||||
@ -152,10 +148,6 @@ func addCommands() {
|
|||||||
// examples
|
// examples
|
||||||
addCounterFlags()
|
addCounterFlags()
|
||||||
RootCmd.AddCommand(counterCmd)
|
RootCmd.AddCommand(counterCmd)
|
||||||
// deprecated, left for backwards compatibility
|
|
||||||
addDummyFlags()
|
|
||||||
RootCmd.AddCommand(dummyCmd)
|
|
||||||
// replaces dummy, see issue #196
|
|
||||||
addKVStoreFlags()
|
addKVStoreFlags()
|
||||||
RootCmd.AddCommand(kvstoreCmd)
|
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{
|
var kvstoreCmd = &cobra.Command{
|
||||||
Use: "kvstore",
|
Use: "kvstore",
|
||||||
Short: "ABCI demo example",
|
Short: "ABCI demo example",
|
||||||
|
@ -432,11 +432,7 @@ type bcBlockResponseMessage struct {
|
|||||||
|
|
||||||
// ValidateBasic performs basic validation.
|
// ValidateBasic performs basic validation.
|
||||||
func (m *bcBlockResponseMessage) ValidateBasic() error {
|
func (m *bcBlockResponseMessage) ValidateBasic() error {
|
||||||
if err := m.Block.ValidateBasic(); err != nil {
|
return m.Block.ValidateBasic()
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *bcBlockResponseMessage) String() string {
|
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 {
|
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)
|
idx, _ := valset.GetByAddress(addr)
|
||||||
vote := &types.Vote{
|
vote := &types.Vote{
|
||||||
ValidatorAddress: addr,
|
ValidatorAddress: addr,
|
||||||
|
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||||
cmn "github.com/tendermint/tendermint/libs/common"
|
cmn "github.com/tendermint/tendermint/libs/common"
|
||||||
@ -15,7 +16,8 @@ func main() {
|
|||||||
var (
|
var (
|
||||||
addr = flag.String("addr", ":26659", "Address of client to connect to")
|
addr = flag.String("addr", ":26659", "Address of client to connect to")
|
||||||
chainID = flag.String("chain-id", "mychain", "chain id")
|
chainID = flag.String("chain-id", "mychain", "chain id")
|
||||||
privValPath = flag.String("priv", "", "priv val file path")
|
privValKeyPath = flag.String("priv-key", "", "priv val key file path")
|
||||||
|
privValStatePath = flag.String("priv-state", "", "priv val state file path")
|
||||||
|
|
||||||
logger = log.NewTMLogger(
|
logger = log.NewTMLogger(
|
||||||
log.NewSyncWriter(os.Stdout),
|
log.NewSyncWriter(os.Stdout),
|
||||||
@ -27,18 +29,26 @@ func main() {
|
|||||||
"Starting private validator",
|
"Starting private validator",
|
||||||
"addr", *addr,
|
"addr", *addr,
|
||||||
"chainID", *chainID,
|
"chainID", *chainID,
|
||||||
"privPath", *privValPath,
|
"privKeyPath", *privValKeyPath,
|
||||||
|
"privStatePath", *privValStatePath,
|
||||||
)
|
)
|
||||||
|
|
||||||
pv := privval.LoadFilePV(*privValPath)
|
pv := privval.LoadFilePV(*privValKeyPath, *privValStatePath)
|
||||||
|
|
||||||
rs := privval.NewRemoteSigner(
|
var dialer privval.Dialer
|
||||||
logger,
|
protocol, address := cmn.ProtocolAndAddress(*addr)
|
||||||
*chainID,
|
switch protocol {
|
||||||
*addr,
|
case "unix":
|
||||||
pv,
|
dialer = privval.DialUnixFn(address)
|
||||||
ed25519.GenPrivKey(),
|
case "tcp":
|
||||||
)
|
connTimeout := 3 * time.Second // TODO
|
||||||
|
dialer = privval.DialTCPFn(address, connTimeout, ed25519.GenPrivKey())
|
||||||
|
default:
|
||||||
|
logger.Error("Unknown protocol", "protocol", protocol)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
rs := privval.NewRemoteSigner(logger, *chainID, pv, dialer)
|
||||||
err := rs.Start()
|
err := rs.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -17,7 +17,7 @@ var GenValidatorCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func genValidator(cmd *cobra.Command, args []string) {
|
func genValidator(cmd *cobra.Command, args []string) {
|
||||||
pv := privval.GenFilePV("")
|
pv := privval.GenFilePV("", "")
|
||||||
jsbz, err := cdc.MarshalJSON(pv)
|
jsbz, err := cdc.MarshalJSON(pv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
cfg "github.com/tendermint/tendermint/config"
|
cfg "github.com/tendermint/tendermint/config"
|
||||||
cmn "github.com/tendermint/tendermint/libs/common"
|
cmn "github.com/tendermint/tendermint/libs/common"
|
||||||
"github.com/tendermint/tendermint/p2p"
|
"github.com/tendermint/tendermint/p2p"
|
||||||
@ -26,15 +25,18 @@ func initFiles(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
func initFilesWithConfig(config *cfg.Config) error {
|
func initFilesWithConfig(config *cfg.Config) error {
|
||||||
// private validator
|
// private validator
|
||||||
privValFile := config.PrivValidatorFile()
|
privValKeyFile := config.PrivValidatorKeyFile()
|
||||||
|
privValStateFile := config.PrivValidatorStateFile()
|
||||||
var pv *privval.FilePV
|
var pv *privval.FilePV
|
||||||
if cmn.FileExists(privValFile) {
|
if cmn.FileExists(privValKeyFile) {
|
||||||
pv = privval.LoadFilePV(privValFile)
|
pv = privval.LoadFilePV(privValKeyFile, privValStateFile)
|
||||||
logger.Info("Found private validator", "path", privValFile)
|
logger.Info("Found private validator", "keyFile", privValKeyFile,
|
||||||
|
"stateFile", privValStateFile)
|
||||||
} else {
|
} else {
|
||||||
pv = privval.GenFilePV(privValFile)
|
pv = privval.GenFilePV(privValKeyFile, privValStateFile)
|
||||||
pv.Save()
|
pv.Save()
|
||||||
logger.Info("Generated private validator", "path", privValFile)
|
logger.Info("Generated private validator", "keyFile", privValKeyFile,
|
||||||
|
"stateFile", privValStateFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeKeyFile := config.NodeKeyFile()
|
nodeKeyFile := config.NodeKeyFile()
|
||||||
@ -57,9 +59,10 @@ func initFilesWithConfig(config *cfg.Config) error {
|
|||||||
GenesisTime: tmtime.Now(),
|
GenesisTime: tmtime.Now(),
|
||||||
ConsensusParams: types.DefaultConsensusParams(),
|
ConsensusParams: types.DefaultConsensusParams(),
|
||||||
}
|
}
|
||||||
|
key := pv.GetPubKey()
|
||||||
genDoc.Validators = []types.GenesisValidator{{
|
genDoc.Validators = []types.GenesisValidator{{
|
||||||
Address: pv.GetPubKey().Address(),
|
Address: key.Address(),
|
||||||
PubKey: pv.GetPubKey(),
|
PubKey: key,
|
||||||
Power: 10,
|
Power: 10,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
cmn "github.com/tendermint/tendermint/libs/common"
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
"github.com/tendermint/tendermint/privval"
|
"github.com/tendermint/tendermint/privval"
|
||||||
)
|
)
|
||||||
@ -27,36 +28,41 @@ var ResetPrivValidatorCmd = &cobra.Command{
|
|||||||
// XXX: this is totally unsafe.
|
// XXX: this is totally unsafe.
|
||||||
// it's only suitable for testnets.
|
// it's only suitable for testnets.
|
||||||
func resetAll(cmd *cobra.Command, args []string) {
|
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.
|
// XXX: this is totally unsafe.
|
||||||
// it's only suitable for testnets.
|
// it's only suitable for testnets.
|
||||||
func resetPrivValidator(cmd *cobra.Command, args []string) {
|
func resetPrivValidator(cmd *cobra.Command, args []string) {
|
||||||
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.
|
// Exported so other CLI tools can use it.
|
||||||
func ResetAll(dbDir, addrBookFile, privValFile string, logger log.Logger) {
|
func ResetAll(dbDir, addrBookFile, privValKeyFile, privValStateFile string, logger log.Logger) {
|
||||||
resetFilePV(privValFile, logger)
|
|
||||||
removeAddrBook(addrBookFile, logger)
|
removeAddrBook(addrBookFile, logger)
|
||||||
if err := os.RemoveAll(dbDir); err == nil {
|
if err := os.RemoveAll(dbDir); err == nil {
|
||||||
logger.Info("Removed all blockchain history", "dir", dbDir)
|
logger.Info("Removed all blockchain history", "dir", dbDir)
|
||||||
} else {
|
} else {
|
||||||
logger.Error("Error removing all blockchain history", "dir", dbDir, "err", err)
|
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) {
|
func resetFilePV(privValKeyFile, privValStateFile string, logger log.Logger) {
|
||||||
if _, err := os.Stat(privValFile); err == nil {
|
if _, err := os.Stat(privValKeyFile); err == nil {
|
||||||
pv := privval.LoadFilePV(privValFile)
|
pv := privval.LoadFilePVEmptyState(privValKeyFile, privValStateFile)
|
||||||
pv.Reset()
|
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 {
|
} else {
|
||||||
pv := privval.GenFilePV(privValFile)
|
pv := privval.GenFilePV(privValKeyFile, privValStateFile)
|
||||||
pv.Save()
|
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")
|
cmd.Flags().Bool("fast_sync", config.FastSync, "Fast blockchain syncing")
|
||||||
|
|
||||||
// abci flags
|
// 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)")
|
cmd.Flags().String("abci", config.ABCI, "Specify abci transport (socket | grpc)")
|
||||||
|
|
||||||
// rpc flags
|
// rpc flags
|
||||||
|
@ -16,7 +16,7 @@ var ShowValidatorCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func showValidator(cmd *cobra.Command, args []string) {
|
func showValidator(cmd *cobra.Command, args []string) {
|
||||||
privValidator := privval.LoadOrGenFilePV(config.PrivValidatorFile())
|
privValidator := privval.LoadOrGenFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile())
|
||||||
pubKeyJSONBytes, _ := cdc.MarshalJSON(privValidator.GetPubKey())
|
pubKeyJSONBytes, _ := cdc.MarshalJSON(privValidator.GetPubKey())
|
||||||
fmt.Println(string(pubKeyJSONBytes))
|
fmt.Println(string(pubKeyJSONBytes))
|
||||||
}
|
}
|
||||||
|
@ -85,11 +85,18 @@ func testnetFiles(cmd *cobra.Command, args []string) error {
|
|||||||
_ = os.RemoveAll(outputDir)
|
_ = os.RemoveAll(outputDir)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
err = os.MkdirAll(filepath.Join(nodeDir, "data"), nodeDirPerm)
|
||||||
|
if err != nil {
|
||||||
|
_ = os.RemoveAll(outputDir)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
initFilesWithConfig(config)
|
initFilesWithConfig(config)
|
||||||
|
|
||||||
pvFile := filepath.Join(nodeDir, config.BaseConfig.PrivValidator)
|
pvKeyFile := filepath.Join(nodeDir, config.BaseConfig.PrivValidatorKey)
|
||||||
pv := privval.LoadFilePV(pvFile)
|
pvStateFile := filepath.Join(nodeDir, config.BaseConfig.PrivValidatorState)
|
||||||
|
|
||||||
|
pv := privval.LoadFilePV(pvKeyFile, pvStateFile)
|
||||||
genVals[i] = types.GenesisValidator{
|
genVals[i] = types.GenesisValidator{
|
||||||
Address: pv.GetPubKey().Address(),
|
Address: pv.GetPubKey().Address(),
|
||||||
PubKey: pv.GetPubKey(),
|
PubKey: pv.GetPubKey(),
|
||||||
@ -145,6 +152,7 @@ func testnetFiles(cmd *cobra.Command, args []string) error {
|
|||||||
nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i))
|
nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i))
|
||||||
config.SetRoot(nodeDir)
|
config.SetRoot(nodeDir)
|
||||||
config.P2P.AddrBookStrict = false
|
config.P2P.AddrBookStrict = false
|
||||||
|
config.P2P.AllowDuplicateIP = true
|
||||||
if populatePersistentPeers {
|
if populatePersistentPeers {
|
||||||
config.P2P.PersistentPeers = persistentPeers
|
config.P2P.PersistentPeers = persistentPeers
|
||||||
}
|
}
|
||||||
|
@ -35,17 +35,26 @@ var (
|
|||||||
defaultConfigFileName = "config.toml"
|
defaultConfigFileName = "config.toml"
|
||||||
defaultGenesisJSONName = "genesis.json"
|
defaultGenesisJSONName = "genesis.json"
|
||||||
|
|
||||||
defaultPrivValName = "priv_validator.json"
|
defaultPrivValKeyName = "priv_validator_key.json"
|
||||||
|
defaultPrivValStateName = "priv_validator_state.json"
|
||||||
|
|
||||||
defaultNodeKeyName = "node_key.json"
|
defaultNodeKeyName = "node_key.json"
|
||||||
defaultAddrBookName = "addrbook.json"
|
defaultAddrBookName = "addrbook.json"
|
||||||
|
|
||||||
defaultConfigFilePath = filepath.Join(defaultConfigDir, defaultConfigFileName)
|
defaultConfigFilePath = filepath.Join(defaultConfigDir, defaultConfigFileName)
|
||||||
defaultGenesisJSONPath = filepath.Join(defaultConfigDir, defaultGenesisJSONName)
|
defaultGenesisJSONPath = filepath.Join(defaultConfigDir, defaultGenesisJSONName)
|
||||||
defaultPrivValPath = filepath.Join(defaultConfigDir, defaultPrivValName)
|
defaultPrivValKeyPath = filepath.Join(defaultConfigDir, defaultPrivValKeyName)
|
||||||
|
defaultPrivValStatePath = filepath.Join(defaultDataDir, defaultPrivValStateName)
|
||||||
|
|
||||||
defaultNodeKeyPath = filepath.Join(defaultConfigDir, defaultNodeKeyName)
|
defaultNodeKeyPath = filepath.Join(defaultConfigDir, defaultNodeKeyName)
|
||||||
defaultAddrBookPath = filepath.Join(defaultConfigDir, defaultAddrBookName)
|
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
|
// Config defines the top level configuration for a Tendermint node
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// Top level options use an anonymous struct
|
// Top level options use an anonymous struct
|
||||||
@ -160,7 +169,10 @@ type BaseConfig struct {
|
|||||||
Genesis string `mapstructure:"genesis_file"`
|
Genesis string `mapstructure:"genesis_file"`
|
||||||
|
|
||||||
// Path to the JSON file containing the private key to use as a validator in the consensus protocol
|
// 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
|
// TCP or UNIX socket address for Tendermint to listen on for
|
||||||
// connections from an external PrivValidator process
|
// connections from an external PrivValidator process
|
||||||
@ -184,7 +196,8 @@ type BaseConfig struct {
|
|||||||
func DefaultBaseConfig() BaseConfig {
|
func DefaultBaseConfig() BaseConfig {
|
||||||
return BaseConfig{
|
return BaseConfig{
|
||||||
Genesis: defaultGenesisJSONPath,
|
Genesis: defaultGenesisJSONPath,
|
||||||
PrivValidator: defaultPrivValPath,
|
PrivValidatorKey: defaultPrivValKeyPath,
|
||||||
|
PrivValidatorState: defaultPrivValStatePath,
|
||||||
NodeKey: defaultNodeKeyPath,
|
NodeKey: defaultNodeKeyPath,
|
||||||
Moniker: defaultMoniker,
|
Moniker: defaultMoniker,
|
||||||
ProxyApp: "tcp://127.0.0.1:26658",
|
ProxyApp: "tcp://127.0.0.1:26658",
|
||||||
@ -218,9 +231,20 @@ func (cfg BaseConfig) GenesisFile() string {
|
|||||||
return rootify(cfg.Genesis, cfg.RootDir)
|
return rootify(cfg.Genesis, cfg.RootDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrivValidatorFile returns the full path to the priv_validator.json file
|
// PrivValidatorKeyFile returns the full path to the priv_validator_key.json file
|
||||||
func (cfg BaseConfig) PrivValidatorFile() string {
|
func (cfg BaseConfig) PrivValidatorKeyFile() string {
|
||||||
return rootify(cfg.PrivValidator, cfg.RootDir)
|
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
|
// NodeKeyFile returns the full path to the node_key.json file
|
||||||
@ -434,7 +458,7 @@ func DefaultP2PConfig() *P2PConfig {
|
|||||||
RecvRate: 5120000, // 5 mB/s
|
RecvRate: 5120000, // 5 mB/s
|
||||||
PexReactor: true,
|
PexReactor: true,
|
||||||
SeedMode: false,
|
SeedMode: false,
|
||||||
AllowDuplicateIP: true, // so non-breaking yet
|
AllowDuplicateIP: false,
|
||||||
HandshakeTimeout: 20 * time.Second,
|
HandshakeTimeout: 20 * time.Second,
|
||||||
DialTimeout: 3 * time.Second,
|
DialTimeout: 3 * time.Second,
|
||||||
TestDialFail: false,
|
TestDialFail: false,
|
||||||
|
@ -95,7 +95,10 @@ log_format = "{{ .BaseConfig.LogFormat }}"
|
|||||||
genesis_file = "{{ js .BaseConfig.Genesis }}"
|
genesis_file = "{{ js .BaseConfig.Genesis }}"
|
||||||
|
|
||||||
# Path to the JSON file containing the private key to use as a validator in the consensus protocol
|
# 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
|
# TCP or UNIX socket address for Tendermint to listen on for
|
||||||
# connections from an external PrivValidator process
|
# connections from an external PrivValidator process
|
||||||
@ -342,7 +345,8 @@ func ResetTestRoot(testName string) *Config {
|
|||||||
baseConfig := DefaultBaseConfig()
|
baseConfig := DefaultBaseConfig()
|
||||||
configFilePath := filepath.Join(rootDir, defaultConfigFilePath)
|
configFilePath := filepath.Join(rootDir, defaultConfigFilePath)
|
||||||
genesisFilePath := filepath.Join(rootDir, baseConfig.Genesis)
|
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.
|
// Write default config file if missing.
|
||||||
if !cmn.FileExists(configFilePath) {
|
if !cmn.FileExists(configFilePath) {
|
||||||
@ -352,7 +356,8 @@ func ResetTestRoot(testName string) *Config {
|
|||||||
cmn.MustWriteFile(genesisFilePath, []byte(testGenesis), 0644)
|
cmn.MustWriteFile(genesisFilePath, []byte(testGenesis), 0644)
|
||||||
}
|
}
|
||||||
// we always overwrite the priv val
|
// 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)
|
config := TestConfig().SetRoot(rootDir)
|
||||||
return config
|
return config
|
||||||
@ -374,7 +379,7 @@ var testGenesis = `{
|
|||||||
"app_hash": ""
|
"app_hash": ""
|
||||||
}`
|
}`
|
||||||
|
|
||||||
var testPrivValidator = `{
|
var testPrivValidatorKey = `{
|
||||||
"address": "A3258DCBF45DCA0DF052981870F2D1441A36D145",
|
"address": "A3258DCBF45DCA0DF052981870F2D1441A36D145",
|
||||||
"pub_key": {
|
"pub_key": {
|
||||||
"type": "tendermint/PubKeyEd25519",
|
"type": "tendermint/PubKeyEd25519",
|
||||||
@ -383,8 +388,11 @@ var testPrivValidator = `{
|
|||||||
"priv_key": {
|
"priv_key": {
|
||||||
"type": "tendermint/PrivKeyEd25519",
|
"type": "tendermint/PrivKeyEd25519",
|
||||||
"value": "EVkqJO/jIXp3rkASXfh9YnyToYXRXhBr6g9cQVxPFnQBP/5povV4HTjvsy530kybxKHwEi85iU8YL0qQhSYVoQ=="
|
"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!
|
// TODO: make sure the cfg returned and testconfig are the same!
|
||||||
baseConfig := DefaultBaseConfig()
|
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 {
|
func checkConfig(configFile string) bool {
|
||||||
|
@ -6,14 +6,18 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-kit/kit/log/term"
|
||||||
|
|
||||||
abcicli "github.com/tendermint/tendermint/abci/client"
|
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"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
bc "github.com/tendermint/tendermint/blockchain"
|
bc "github.com/tendermint/tendermint/blockchain"
|
||||||
cfg "github.com/tendermint/tendermint/config"
|
cfg "github.com/tendermint/tendermint/config"
|
||||||
@ -27,11 +31,6 @@ import (
|
|||||||
sm "github.com/tendermint/tendermint/state"
|
sm "github.com/tendermint/tendermint/state"
|
||||||
"github.com/tendermint/tendermint/types"
|
"github.com/tendermint/tendermint/types"
|
||||||
tmtime "github.com/tendermint/tendermint/types/time"
|
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 (
|
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) {
|
func (vs *validatorStub) signVote(voteType types.SignedMsgType, hash []byte, header types.PartSetHeader) (*types.Vote, error) {
|
||||||
|
addr := vs.PrivValidator.GetPubKey().Address()
|
||||||
vote := &types.Vote{
|
vote := &types.Vote{
|
||||||
ValidatorIndex: vs.Index,
|
ValidatorIndex: vs.Index,
|
||||||
ValidatorAddress: vs.PrivValidator.GetAddress(),
|
ValidatorAddress: addr,
|
||||||
Height: vs.Height,
|
Height: vs.Height,
|
||||||
Round: vs.Round,
|
Round: vs.Round,
|
||||||
Timestamp: tmtime.Now(),
|
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) {
|
func validatePrevote(t *testing.T, cs *ConsensusState, round int, privVal *validatorStub, blockHash []byte) {
|
||||||
prevotes := cs.Votes.Prevotes(round)
|
prevotes := cs.Votes.Prevotes(round)
|
||||||
|
address := privVal.GetPubKey().Address()
|
||||||
var vote *types.Vote
|
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")
|
panic("Failed to find prevote from validator")
|
||||||
}
|
}
|
||||||
if blockHash == nil {
|
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) {
|
func validateLastPrecommit(t *testing.T, cs *ConsensusState, privVal *validatorStub, blockHash []byte) {
|
||||||
votes := cs.LastCommit
|
votes := cs.LastCommit
|
||||||
|
address := privVal.GetPubKey().Address()
|
||||||
var vote *types.Vote
|
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")
|
panic("Failed to find precommit from validator")
|
||||||
}
|
}
|
||||||
if !bytes.Equal(vote.BlockID.Hash, blockHash) {
|
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) {
|
func validatePrecommit(t *testing.T, cs *ConsensusState, thisRound, lockRound int, privVal *validatorStub, votedBlockHash, lockedBlockHash []byte) {
|
||||||
precommits := cs.Votes.Precommits(thisRound)
|
precommits := cs.Votes.Precommits(thisRound)
|
||||||
|
address := privVal.GetPubKey().Address()
|
||||||
var vote *types.Vote
|
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")
|
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 {
|
func loadPrivValidator(config *cfg.Config) *privval.FilePV {
|
||||||
privValidatorFile := config.PrivValidatorFile()
|
privValidatorKeyFile := config.PrivValidatorKeyFile()
|
||||||
ensureDir(path.Dir(privValidatorFile), 0700)
|
ensureDir(filepath.Dir(privValidatorKeyFile), 0700)
|
||||||
privValidator := privval.LoadOrGenFilePV(privValidatorFile)
|
privValidatorStateFile := config.PrivValidatorStateFile()
|
||||||
|
privValidator := privval.LoadOrGenFilePV(privValidatorKeyFile, privValidatorStateFile)
|
||||||
privValidator.Reset()
|
privValidator.Reset()
|
||||||
return privValidator
|
return privValidator
|
||||||
}
|
}
|
||||||
@ -591,7 +595,7 @@ func randConsensusNet(nValidators int, testName string, tickerFunc func() Timeou
|
|||||||
for _, opt := range configOpts {
|
for _, opt := range configOpts {
|
||||||
opt(thisConfig)
|
opt(thisConfig)
|
||||||
}
|
}
|
||||||
ensureDir(path.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal
|
ensureDir(filepath.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal
|
||||||
app := appFunc()
|
app := appFunc()
|
||||||
vals := types.TM2PB.ValidatorUpdates(state.Validators)
|
vals := types.TM2PB.ValidatorUpdates(state.Validators)
|
||||||
app.InitChain(abci.RequestInitChain{Validators: vals})
|
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
|
stateDB := dbm.NewMemDB() // each state needs its own db
|
||||||
state, _ := sm.LoadStateFromDBOrGenesisDoc(stateDB, genDoc)
|
state, _ := sm.LoadStateFromDBOrGenesisDoc(stateDB, genDoc)
|
||||||
thisConfig := ResetConfig(fmt.Sprintf("%s_%d", testName, i))
|
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
|
var privVal types.PrivValidator
|
||||||
if i < nValidators {
|
if i < nValidators {
|
||||||
privVal = privVals[i]
|
privVal = privVals[i]
|
||||||
} else {
|
} else {
|
||||||
tempFile, err := ioutil.TempFile("", "priv_validator_")
|
tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
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()
|
app := appFunc()
|
||||||
|
@ -143,7 +143,8 @@ func TestReactorWithEvidence(t *testing.T) {
|
|||||||
// mock the evidence pool
|
// mock the evidence pool
|
||||||
// everyone includes evidence of another double signing
|
// everyone includes evidence of another double signing
|
||||||
vIdx := (i + 1) % nValidators
|
vIdx := (i + 1) % nValidators
|
||||||
evpool := newMockEvidencePool(privVals[vIdx].GetAddress())
|
addr := privVals[vIdx].GetPubKey().Address()
|
||||||
|
evpool := newMockEvidencePool(addr)
|
||||||
|
|
||||||
// Make ConsensusState
|
// Make ConsensusState
|
||||||
blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyAppConnCon, mempool, evpool)
|
blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyAppConnCon, mempool, evpool)
|
||||||
@ -268,7 +269,8 @@ func TestReactorVotingPowerChange(t *testing.T) {
|
|||||||
// map of active validators
|
// map of active validators
|
||||||
activeVals := make(map[string]struct{})
|
activeVals := make(map[string]struct{})
|
||||||
for i := 0; i < nVals; i++ {
|
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
|
// wait till everyone makes block 1
|
||||||
@ -331,7 +333,8 @@ func TestReactorValidatorSetChanges(t *testing.T) {
|
|||||||
// map of active validators
|
// map of active validators
|
||||||
activeVals := make(map[string]struct{})
|
activeVals := make(map[string]struct{})
|
||||||
for i := 0; i < nVals; i++ {
|
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
|
// wait till everyone makes block 1
|
||||||
|
@ -319,7 +319,7 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) {
|
|||||||
walFile := tempWALWithData(walBody)
|
walFile := tempWALWithData(walBody)
|
||||||
config.Consensus.SetWalFile(walFile)
|
config.Consensus.SetWalFile(walFile)
|
||||||
|
|
||||||
privVal := privval.LoadFilePV(config.PrivValidatorFile())
|
privVal := privval.LoadFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile())
|
||||||
|
|
||||||
wal, err := NewWAL(walFile)
|
wal, err := NewWAL(walFile)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -633,7 +633,7 @@ func TestInitChainUpdateValidators(t *testing.T) {
|
|||||||
clientCreator := proxy.NewLocalClientCreator(app)
|
clientCreator := proxy.NewLocalClientCreator(app)
|
||||||
|
|
||||||
config := ResetConfig("proxy_test_")
|
config := ResetConfig("proxy_test_")
|
||||||
privVal := privval.LoadFilePV(config.PrivValidatorFile())
|
privVal := privval.LoadFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile())
|
||||||
stateDB, state, store := stateAndStore(config, privVal.GetPubKey(), 0x0)
|
stateDB, state, store := stateAndStore(config, privVal.GetPubKey(), 0x0)
|
||||||
|
|
||||||
oldValAddr := state.Validators.Validators[0].Address
|
oldValAddr := state.Validators.Validators[0].Address
|
||||||
@ -659,12 +659,6 @@ func TestInitChainUpdateValidators(t *testing.T) {
|
|||||||
assert.Equal(t, newValAddr, expectValAddr)
|
assert.Equal(t, newValAddr, expectValAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newInitChainApp(vals []abci.ValidatorUpdate) *initChainApp {
|
|
||||||
return &initChainApp{
|
|
||||||
vals: vals,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns the vals on InitChain
|
// returns the vals on InitChain
|
||||||
type initChainApp struct {
|
type initChainApp struct {
|
||||||
abci.BaseApplication
|
abci.BaseApplication
|
||||||
|
@ -2,13 +2,14 @@ package consensus
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
cmn "github.com/tendermint/tendermint/libs/common"
|
cmn "github.com/tendermint/tendermint/libs/common"
|
||||||
"github.com/tendermint/tendermint/libs/fail"
|
"github.com/tendermint/tendermint/libs/fail"
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"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 not a validator, we're done
|
||||||
if !cs.Validators.HasAddress(cs.privValidator.GetAddress()) {
|
address := cs.privValidator.GetPubKey().Address()
|
||||||
logger.Debug("This node is not a validator", "addr", cs.privValidator.GetAddress(), "vals", cs.Validators)
|
if !cs.Validators.HasAddress(address) {
|
||||||
|
logger.Debug("This node is not a validator", "addr", address, "vals", cs.Validators)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Debug("This node is a validator")
|
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)
|
logger.Info("enterPropose: Our turn to propose", "proposer", cs.Validators.GetProposer().Address, "privValidator", cs.privValidator)
|
||||||
cs.decideProposal(height, round)
|
cs.decideProposal(height, round)
|
||||||
} else {
|
} else {
|
||||||
@ -843,8 +845,8 @@ func (cs *ConsensusState) enterPropose(height int64, round int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *ConsensusState) isProposer() bool {
|
func (cs *ConsensusState) isProposer(address []byte) bool {
|
||||||
return bytes.Equal(cs.Validators.GetProposer().Address, cs.privValidator.GetAddress())
|
return bytes.Equal(cs.Validators.GetProposer().Address, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *ConsensusState) defaultDecideProposal(height int64, round int) {
|
func (cs *ConsensusState) defaultDecideProposal(height int64, round int) {
|
||||||
@ -929,7 +931,7 @@ func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts
|
|||||||
cs.state.Validators.Size(),
|
cs.state.Validators.Size(),
|
||||||
len(evidence),
|
len(evidence),
|
||||||
), maxGas)
|
), maxGas)
|
||||||
proposerAddr := cs.privValidator.GetAddress()
|
proposerAddr := cs.privValidator.GetPubKey().Address()
|
||||||
block, parts := cs.state.MakeBlock(cs.Height, txs, commit, evidence, proposerAddr)
|
block, parts := cs.state.MakeBlock(cs.Height, txs, commit, evidence, proposerAddr)
|
||||||
|
|
||||||
return block, parts
|
return block, parts
|
||||||
@ -1474,7 +1476,8 @@ func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerID p2p.ID) (bool, err
|
|||||||
if err == ErrVoteHeightMismatch {
|
if err == ErrVoteHeightMismatch {
|
||||||
return added, err
|
return added, err
|
||||||
} else if voteErr, ok := err.(*types.ErrVoteConflictingVotes); ok {
|
} 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)
|
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
|
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.
|
// Not necessarily a bad peer, but not favourable behaviour.
|
||||||
if vote.Height != cs.Height {
|
if vote.Height != cs.Height {
|
||||||
err = ErrVoteHeightMismatch
|
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
|
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) {
|
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)
|
valIndex, _ := cs.Validators.GetByAddress(addr)
|
||||||
|
|
||||||
vote := &types.Vote{
|
vote := &types.Vote{
|
||||||
@ -1675,7 +1678,7 @@ func (cs *ConsensusState) voteTime() time.Time {
|
|||||||
// sign the vote and publish on internalMsgQueue
|
// sign the vote and publish on internalMsgQueue
|
||||||
func (cs *ConsensusState) signAddVote(type_ types.SignedMsgType, hash []byte, header types.PartSetHeader) *types.Vote {
|
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 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
|
return nil
|
||||||
}
|
}
|
||||||
vote, err := cs.signVote(type_, hash, header)
|
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.
|
// Commit a block and ensure proposer for the next height is correct.
|
||||||
prop := cs1.GetRoundState().Validators.GetProposer()
|
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)
|
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)
|
ensureNewRound(newRoundCh, height+1, 0)
|
||||||
|
|
||||||
prop = cs1.GetRoundState().Validators.GetProposer()
|
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))
|
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
|
// everyone just votes nil. we get a new proposer each round
|
||||||
for i := 0; i < len(vss); i++ {
|
for i := 0; i < len(vss); i++ {
|
||||||
prop := cs1.GetRoundState().Validators.GetProposer()
|
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) {
|
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))
|
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)
|
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||||
proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal)
|
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)
|
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||||
newBlockCh := subscribe(cs1.eventBus, types.EventQueryNewBlockHeader)
|
newBlockCh := subscribe(cs1.eventBus, types.EventQueryNewBlockHeader)
|
||||||
|
|
||||||
@ -596,7 +600,8 @@ func TestStateLockPOLUnlock(t *testing.T) {
|
|||||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||||
unlockCh := subscribe(cs1.eventBus, types.EventQueryUnlock)
|
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
|
// everything done from perspective of cs1
|
||||||
|
|
||||||
@ -689,7 +694,8 @@ func TestStateLockPOLSafety1(t *testing.T) {
|
|||||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
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
|
// start round and wait for propose and prevote
|
||||||
startTestRound(cs1, cs1.Height, round)
|
startTestRound(cs1, cs1.Height, round)
|
||||||
@ -805,7 +811,8 @@ func TestStateLockPOLSafety2(t *testing.T) {
|
|||||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||||
unlockCh := subscribe(cs1.eventBus, types.EventQueryUnlock)
|
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
|
// the block for R0: gets polkad but we miss it
|
||||||
// (even though we signed it, shhh)
|
// (even though we signed it, shhh)
|
||||||
@ -896,7 +903,8 @@ func TestProposeValidBlock(t *testing.T) {
|
|||||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||||
unlockCh := subscribe(cs1.eventBus, types.EventQueryUnlock)
|
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
|
// start round and wait for propose and prevote
|
||||||
startTestRound(cs1, cs1.Height, round)
|
startTestRound(cs1, cs1.Height, round)
|
||||||
@ -982,7 +990,8 @@ func TestSetValidBlockOnDelayedPrevote(t *testing.T) {
|
|||||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||||
validBlockCh := subscribe(cs1.eventBus, types.EventQueryValidBlock)
|
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
|
// start round and wait for propose and prevote
|
||||||
startTestRound(cs1, cs1.Height, round)
|
startTestRound(cs1, cs1.Height, round)
|
||||||
@ -1041,7 +1050,8 @@ func TestSetValidBlockOnDelayedProposal(t *testing.T) {
|
|||||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||||
validBlockCh := subscribe(cs1.eventBus, types.EventQueryValidBlock)
|
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)
|
proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal)
|
||||||
|
|
||||||
round = round + 1 // move to round in which P0 is not proposer
|
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)
|
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||||
voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress())
|
addr := cs1.privValidator.GetPubKey().Address()
|
||||||
|
voteCh := subscribeToVoter(cs1, addr)
|
||||||
|
|
||||||
// start round
|
// start round
|
||||||
startTestRound(cs1, height, round)
|
startTestRound(cs1, height, round)
|
||||||
@ -1144,7 +1155,8 @@ func TestRoundSkipOnNilPolkaFromHigherRound(t *testing.T) {
|
|||||||
|
|
||||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||||
voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress())
|
addr := cs1.privValidator.GetPubKey().Address()
|
||||||
|
voteCh := subscribeToVoter(cs1, addr)
|
||||||
|
|
||||||
// start round
|
// start round
|
||||||
startTestRound(cs1, height, round)
|
startTestRound(cs1, height, round)
|
||||||
@ -1177,7 +1189,8 @@ func TestWaitTimeoutProposeOnNilPolkaForTheCurrentRound(t *testing.T) {
|
|||||||
|
|
||||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
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
|
// start round in which PO is not proposer
|
||||||
startTestRound(cs1, height, round)
|
startTestRound(cs1, height, round)
|
||||||
@ -1361,7 +1374,8 @@ func TestStateHalt1(t *testing.T) {
|
|||||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||||
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
|
||||||
newBlockCh := subscribe(cs1.eventBus, types.EventQueryNewBlock)
|
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
|
// start round and wait for propose and prevote
|
||||||
startTestRound(cs1, height, round)
|
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 {
|
func makeVoteHR(t *testing.T, height int64, round int, privVals []types.PrivValidator, valIndex int) *types.Vote {
|
||||||
privVal := privVals[valIndex]
|
privVal := privVals[valIndex]
|
||||||
|
addr := privVal.GetPubKey().Address()
|
||||||
vote := &types.Vote{
|
vote := &types.Vote{
|
||||||
ValidatorAddress: privVal.GetAddress(),
|
ValidatorAddress: addr,
|
||||||
ValidatorIndex: valIndex,
|
ValidatorIndex: valIndex,
|
||||||
Height: height,
|
Height: height,
|
||||||
Round: round,
|
Round: round,
|
||||||
|
@ -40,8 +40,9 @@ func WALGenerateNBlocks(wr io.Writer, numBlocks int) (err error) {
|
|||||||
// COPY PASTE FROM node.go WITH A FEW MODIFICATIONS
|
// COPY PASTE FROM node.go WITH A FEW MODIFICATIONS
|
||||||
// NOTE: we can't import node package because of circular dependency.
|
// NOTE: we can't import node package because of circular dependency.
|
||||||
// NOTE: we don't do handshake so need to set state.Version.Consensus.App directly.
|
// NOTE: we don't do handshake so need to set state.Version.Consensus.App directly.
|
||||||
privValidatorFile := config.PrivValidatorFile()
|
privValidatorKeyFile := config.PrivValidatorKeyFile()
|
||||||
privValidator := privval.LoadOrGenFilePV(privValidatorFile)
|
privValidatorStateFile := config.PrivValidatorStateFile()
|
||||||
|
privValidator := privval.LoadOrGenFilePV(privValidatorKeyFile, privValidatorStateFile)
|
||||||
genDoc, err := types.GenesisDocFromFile(config.GenesisFile())
|
genDoc, err := types.GenesisDocFromFile(config.GenesisFile())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to read genesis file")
|
return errors.Wrap(err, "failed to read genesis file")
|
||||||
|
@ -18,8 +18,8 @@ import (
|
|||||||
var _ crypto.PrivKey = PrivKeyEd25519{}
|
var _ crypto.PrivKey = PrivKeyEd25519{}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PrivKeyAminoRoute = "tendermint/PrivKeyEd25519"
|
PrivKeyAminoName = "tendermint/PrivKeyEd25519"
|
||||||
PubKeyAminoRoute = "tendermint/PubKeyEd25519"
|
PubKeyAminoName = "tendermint/PubKeyEd25519"
|
||||||
// Size of an Edwards25519 signature. Namely the size of a compressed
|
// Size of an Edwards25519 signature. Namely the size of a compressed
|
||||||
// Edwards25519 point, and a field element. Both of which are 32 bytes.
|
// Edwards25519 point, and a field element. Both of which are 32 bytes.
|
||||||
SignatureSize = 64
|
SignatureSize = 64
|
||||||
@ -30,11 +30,11 @@ var cdc = amino.NewCodec()
|
|||||||
func init() {
|
func init() {
|
||||||
cdc.RegisterInterface((*crypto.PubKey)(nil), nil)
|
cdc.RegisterInterface((*crypto.PubKey)(nil), nil)
|
||||||
cdc.RegisterConcrete(PubKeyEd25519{},
|
cdc.RegisterConcrete(PubKeyEd25519{},
|
||||||
PubKeyAminoRoute, nil)
|
PubKeyAminoName, nil)
|
||||||
|
|
||||||
cdc.RegisterInterface((*crypto.PrivKey)(nil), nil)
|
cdc.RegisterInterface((*crypto.PrivKey)(nil), nil)
|
||||||
cdc.RegisterConcrete(PrivKeyEd25519{},
|
cdc.RegisterConcrete(PrivKeyEd25519{},
|
||||||
PrivKeyAminoRoute, nil)
|
PrivKeyAminoName, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrivKeyEd25519 implements crypto.PrivKey.
|
// PrivKeyEd25519 implements crypto.PrivKey.
|
||||||
|
@ -12,11 +12,11 @@ import (
|
|||||||
|
|
||||||
var cdc = amino.NewCodec()
|
var cdc = amino.NewCodec()
|
||||||
|
|
||||||
// routeTable is used to map public key concrete types back
|
// nameTable is used to map public key concrete types back
|
||||||
// to their amino routes. This should eventually be handled
|
// to their registered amino names. This should eventually be handled
|
||||||
// by amino. Example usage:
|
// by amino. Example usage:
|
||||||
// routeTable[reflect.TypeOf(ed25519.PubKeyEd25519{})] = ed25519.PubKeyAminoRoute
|
// nameTable[reflect.TypeOf(ed25519.PubKeyEd25519{})] = ed25519.PubKeyAminoName
|
||||||
var routeTable = make(map[reflect.Type]string, 3)
|
var nameTable = make(map[reflect.Type]string, 3)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// NOTE: It's important that there be no conflicts here,
|
// 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.
|
// TODO: Have amino provide a way to go from concrete struct to route directly.
|
||||||
// Its currently a private API
|
// Its currently a private API
|
||||||
routeTable[reflect.TypeOf(ed25519.PubKeyEd25519{})] = ed25519.PubKeyAminoRoute
|
nameTable[reflect.TypeOf(ed25519.PubKeyEd25519{})] = ed25519.PubKeyAminoName
|
||||||
routeTable[reflect.TypeOf(secp256k1.PubKeySecp256k1{})] = secp256k1.PubKeyAminoRoute
|
nameTable[reflect.TypeOf(secp256k1.PubKeySecp256k1{})] = secp256k1.PubKeyAminoName
|
||||||
routeTable[reflect.TypeOf(&multisig.PubKeyMultisigThreshold{})] = multisig.PubKeyMultisigThresholdAminoRoute
|
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
|
// cdc is currently passed in, as eventually this will not be using
|
||||||
// a package level codec.
|
// a package level codec.
|
||||||
func PubkeyAminoRoute(cdc *amino.Codec, key crypto.PubKey) (string, bool) {
|
func PubkeyAminoName(cdc *amino.Codec, key crypto.PubKey) (string, bool) {
|
||||||
route, found := routeTable[reflect.TypeOf(key)]
|
route, found := nameTable[reflect.TypeOf(key)]
|
||||||
return route, found
|
return route, found
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,17 +47,17 @@ func RegisterAmino(cdc *amino.Codec) {
|
|||||||
// These are all written here instead of
|
// These are all written here instead of
|
||||||
cdc.RegisterInterface((*crypto.PubKey)(nil), nil)
|
cdc.RegisterInterface((*crypto.PubKey)(nil), nil)
|
||||||
cdc.RegisterConcrete(ed25519.PubKeyEd25519{},
|
cdc.RegisterConcrete(ed25519.PubKeyEd25519{},
|
||||||
ed25519.PubKeyAminoRoute, nil)
|
ed25519.PubKeyAminoName, nil)
|
||||||
cdc.RegisterConcrete(secp256k1.PubKeySecp256k1{},
|
cdc.RegisterConcrete(secp256k1.PubKeySecp256k1{},
|
||||||
secp256k1.PubKeyAminoRoute, nil)
|
secp256k1.PubKeyAminoName, nil)
|
||||||
cdc.RegisterConcrete(multisig.PubKeyMultisigThreshold{},
|
cdc.RegisterConcrete(multisig.PubKeyMultisigThreshold{},
|
||||||
multisig.PubKeyMultisigThresholdAminoRoute, nil)
|
multisig.PubKeyMultisigThresholdAminoRoute, nil)
|
||||||
|
|
||||||
cdc.RegisterInterface((*crypto.PrivKey)(nil), nil)
|
cdc.RegisterInterface((*crypto.PrivKey)(nil), nil)
|
||||||
cdc.RegisterConcrete(ed25519.PrivKeyEd25519{},
|
cdc.RegisterConcrete(ed25519.PrivKeyEd25519{},
|
||||||
ed25519.PrivKeyAminoRoute, nil)
|
ed25519.PrivKeyAminoName, nil)
|
||||||
cdc.RegisterConcrete(secp256k1.PrivKeySecp256k1{},
|
cdc.RegisterConcrete(secp256k1.PrivKeySecp256k1{},
|
||||||
secp256k1.PrivKeyAminoRoute, nil)
|
secp256k1.PrivKeyAminoName, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func PrivKeyFromBytes(privKeyBytes []byte) (privKey crypto.PrivKey, err error) {
|
func PrivKeyFromBytes(privKeyBytes []byte) (privKey crypto.PrivKey, err error) {
|
||||||
|
@ -128,18 +128,18 @@ func TestPubKeyInvalidDataProperReturnsEmpty(t *testing.T) {
|
|||||||
require.Nil(t, pk)
|
require.Nil(t, pk)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPubkeyAminoRoute(t *testing.T) {
|
func TestPubkeyAminoName(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
key crypto.PubKey
|
key crypto.PubKey
|
||||||
want string
|
want string
|
||||||
found bool
|
found bool
|
||||||
}{
|
}{
|
||||||
{ed25519.PubKeyEd25519{}, ed25519.PubKeyAminoRoute, true},
|
{ed25519.PubKeyEd25519{}, ed25519.PubKeyAminoName, true},
|
||||||
{secp256k1.PubKeySecp256k1{}, secp256k1.PubKeyAminoRoute, true},
|
{secp256k1.PubKeySecp256k1{}, secp256k1.PubKeyAminoName, true},
|
||||||
{&multisig.PubKeyMultisigThreshold{}, multisig.PubKeyMultisigThresholdAminoRoute, true},
|
{multisig.PubKeyMultisigThreshold{}, multisig.PubKeyMultisigThresholdAminoRoute, true},
|
||||||
}
|
}
|
||||||
for i, tc := range tests {
|
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)
|
require.Equal(t, tc.found, found, "not equal on tc %d", i)
|
||||||
if tc.found {
|
if tc.found {
|
||||||
require.Equal(t, tc.want, got, "not equal on tc %d", i)
|
require.Equal(t, tc.want, got, "not equal on tc %d", i)
|
||||||
|
@ -2,7 +2,6 @@ package multisig
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/tendermint/tendermint/crypto"
|
"github.com/tendermint/tendermint/crypto"
|
||||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// PubKeyMultisigThreshold implements a K of N threshold multisig.
|
// PubKeyMultisigThreshold implements a K of N threshold multisig.
|
||||||
@ -11,7 +10,7 @@ type PubKeyMultisigThreshold struct {
|
|||||||
PubKeys []crypto.PubKey `json:"pubkeys"`
|
PubKeys []crypto.PubKey `json:"pubkeys"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ crypto.PubKey = &PubKeyMultisigThreshold{}
|
var _ crypto.PubKey = PubKeyMultisigThreshold{}
|
||||||
|
|
||||||
// NewPubKeyMultisigThreshold returns a new PubKeyMultisigThreshold.
|
// NewPubKeyMultisigThreshold returns a new PubKeyMultisigThreshold.
|
||||||
// Panics if len(pubkeys) < k or 0 >= k.
|
// 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 {
|
if len(pubkeys) < k {
|
||||||
panic("threshold k of n multisignature: 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.
|
// 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)
|
// 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
|
// The multisig uses a bitarray, so multiple signatures for the same key is not
|
||||||
// a concern.
|
// a concern.
|
||||||
func (pk *PubKeyMultisigThreshold) VerifyBytes(msg []byte, marshalledSig []byte) bool {
|
func (pk PubKeyMultisigThreshold) VerifyBytes(msg []byte, marshalledSig []byte) bool {
|
||||||
var sig *Multisignature
|
var sig Multisignature
|
||||||
err := cdc.UnmarshalBinaryBare(marshalledSig, &sig)
|
err := cdc.UnmarshalBinaryBare(marshalledSig, &sig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
@ -64,19 +63,19 @@ func (pk *PubKeyMultisigThreshold) VerifyBytes(msg []byte, marshalledSig []byte)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bytes returns the amino encoded version of the PubKeyMultisigThreshold
|
// Bytes returns the amino encoded version of the PubKeyMultisigThreshold
|
||||||
func (pk *PubKeyMultisigThreshold) Bytes() []byte {
|
func (pk PubKeyMultisigThreshold) Bytes() []byte {
|
||||||
return cdc.MustMarshalBinaryBare(pk)
|
return cdc.MustMarshalBinaryBare(pk)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Address returns tmhash(PubKeyMultisigThreshold.Bytes())
|
// Address returns tmhash(PubKeyMultisigThreshold.Bytes())
|
||||||
func (pk *PubKeyMultisigThreshold) Address() crypto.Address {
|
func (pk PubKeyMultisigThreshold) Address() crypto.Address {
|
||||||
return crypto.Address(tmhash.Sum(pk.Bytes()))
|
return crypto.AddressHash(pk.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equals returns true iff pk and other both have the same number of keys, and
|
// 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.
|
// all constituent keys are the same, and in the same order.
|
||||||
func (pk *PubKeyMultisigThreshold) Equals(other crypto.PubKey) bool {
|
func (pk PubKeyMultisigThreshold) Equals(other crypto.PubKey) bool {
|
||||||
otherKey, sameType := other.(*PubKeyMultisigThreshold)
|
otherKey, sameType := other.(PubKeyMultisigThreshold)
|
||||||
if !sameType {
|
if !sameType {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ func TestMultiSigPubKeyEquality(t *testing.T) {
|
|||||||
msg := []byte{1, 2, 3, 4}
|
msg := []byte{1, 2, 3, 4}
|
||||||
pubkeys, _ := generatePubKeysAndSignatures(5, msg)
|
pubkeys, _ := generatePubKeysAndSignatures(5, msg)
|
||||||
multisigKey := NewPubKeyMultisigThreshold(2, pubkeys)
|
multisigKey := NewPubKeyMultisigThreshold(2, pubkeys)
|
||||||
var unmarshalledMultisig *PubKeyMultisigThreshold
|
var unmarshalledMultisig PubKeyMultisigThreshold
|
||||||
cdc.MustUnmarshalBinaryBare(multisigKey.Bytes(), &unmarshalledMultisig)
|
cdc.MustUnmarshalBinaryBare(multisigKey.Bytes(), &unmarshalledMultisig)
|
||||||
require.True(t, multisigKey.Equals(unmarshalledMultisig))
|
require.True(t, multisigKey.Equals(unmarshalledMultisig))
|
||||||
|
|
||||||
@ -95,6 +95,29 @@ func TestMultiSigPubKeyEquality(t *testing.T) {
|
|||||||
require.False(t, multisigKey.Equals(multisigKey2))
|
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) {
|
func generatePubKeysAndSignatures(n int, msg []byte) (pubkeys []crypto.PubKey, signatures [][]byte) {
|
||||||
pubkeys = make([]crypto.PubKey, n)
|
pubkeys = make([]crypto.PubKey, n)
|
||||||
signatures = make([][]byte, n)
|
signatures = make([][]byte, n)
|
||||||
|
@ -20,7 +20,7 @@ func init() {
|
|||||||
cdc.RegisterConcrete(PubKeyMultisigThreshold{},
|
cdc.RegisterConcrete(PubKeyMultisigThreshold{},
|
||||||
PubKeyMultisigThresholdAminoRoute, nil)
|
PubKeyMultisigThresholdAminoRoute, nil)
|
||||||
cdc.RegisterConcrete(ed25519.PubKeyEd25519{},
|
cdc.RegisterConcrete(ed25519.PubKeyEd25519{},
|
||||||
ed25519.PubKeyAminoRoute, nil)
|
ed25519.PubKeyAminoName, nil)
|
||||||
cdc.RegisterConcrete(secp256k1.PubKeySecp256k1{},
|
cdc.RegisterConcrete(secp256k1.PubKeySecp256k1{},
|
||||||
secp256k1.PubKeyAminoRoute, nil)
|
secp256k1.PubKeyAminoName, nil)
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,8 @@ import (
|
|||||||
|
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
const (
|
const (
|
||||||
PrivKeyAminoRoute = "tendermint/PrivKeySecp256k1"
|
PrivKeyAminoName = "tendermint/PrivKeySecp256k1"
|
||||||
PubKeyAminoRoute = "tendermint/PubKeySecp256k1"
|
PubKeyAminoName = "tendermint/PubKeySecp256k1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cdc = amino.NewCodec()
|
var cdc = amino.NewCodec()
|
||||||
@ -25,11 +25,11 @@ var cdc = amino.NewCodec()
|
|||||||
func init() {
|
func init() {
|
||||||
cdc.RegisterInterface((*crypto.PubKey)(nil), nil)
|
cdc.RegisterInterface((*crypto.PubKey)(nil), nil)
|
||||||
cdc.RegisterConcrete(PubKeySecp256k1{},
|
cdc.RegisterConcrete(PubKeySecp256k1{},
|
||||||
PubKeyAminoRoute, nil)
|
PubKeyAminoName, nil)
|
||||||
|
|
||||||
cdc.RegisterInterface((*crypto.PrivKey)(nil), nil)
|
cdc.RegisterInterface((*crypto.PrivKey)(nil), nil)
|
||||||
cdc.RegisterConcrete(PrivKeySecp256k1{},
|
cdc.RegisterConcrete(PrivKeySecp256k1{},
|
||||||
PrivKeyAminoRoute, nil)
|
PrivKeyAminoName, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
|
@ -19,7 +19,10 @@ module.exports = {
|
|||||||
indexName: 'tendermint',
|
indexName: 'tendermint',
|
||||||
debug: false
|
debug: false
|
||||||
},
|
},
|
||||||
nav: [{ text: "Back to Tendermint", link: "https://tendermint.com" }],
|
nav: [
|
||||||
|
{ text: "Back to Tendermint", link: "https://tendermint.com" },
|
||||||
|
{ text: "RPC", link: "../rpc/" }
|
||||||
|
],
|
||||||
sidebar: [
|
sidebar: [
|
||||||
{
|
{
|
||||||
title: "Introduction",
|
title: "Introduction",
|
||||||
@ -31,6 +34,20 @@ module.exports = {
|
|||||||
"/introduction/what-is-tendermint"
|
"/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",
|
title: "Tendermint Core",
|
||||||
collapsable: false,
|
collapsable: false,
|
||||||
@ -49,15 +66,6 @@ module.exports = {
|
|||||||
"/tendermint-core/validators"
|
"/tendermint-core/validators"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: "Tools",
|
|
||||||
collapsable: false,
|
|
||||||
children: [
|
|
||||||
"/tools/",
|
|
||||||
"/tools/benchmarking",
|
|
||||||
"/tools/monitoring"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "Networks",
|
title: "Networks",
|
||||||
collapsable: false,
|
collapsable: false,
|
||||||
@ -68,17 +76,12 @@ module.exports = {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Apps",
|
title: "Tools",
|
||||||
collapsable: false,
|
collapsable: false,
|
||||||
children: [
|
children: [
|
||||||
"/app-dev/getting-started",
|
"/tools/",
|
||||||
"/app-dev/abci-cli",
|
"/tools/benchmarking",
|
||||||
"/app-dev/app-architecture",
|
"/tools/monitoring"
|
||||||
"/app-dev/app-development",
|
|
||||||
"/app-dev/subscribing-to-events-via-websocket",
|
|
||||||
"/app-dev/indexing-transactions",
|
|
||||||
"/app-dev/abci-spec",
|
|
||||||
"/app-dev/ecosystem"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -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
|
### Implementation roadmap
|
||||||
|
|
||||||
* implement proposed implementation
|
* implement proposed implementation
|
||||||
|
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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@ -407,21 +407,22 @@ If `storeBlockHeight == stateBlockHeight && appBlockHeight < storeBlockHeight`,
|
|||||||
replay all blocks in full from `appBlockHeight` to `storeBlockHeight`.
|
replay all blocks in full from `appBlockHeight` to `storeBlockHeight`.
|
||||||
This happens if we completed processing the block, but the app forgot its height.
|
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.
|
This happens if we crashed at an opportune spot.
|
||||||
|
|
||||||
If `storeBlockHeight == stateBlockHeight+1`
|
If `storeBlockHeight == stateBlockHeight+1`
|
||||||
This happens if we started processing the block but didn't finish.
|
This happens if we started processing the block but didn't finish.
|
||||||
|
|
||||||
If `appBlockHeight < stateBlockHeight`
|
If `appBlockHeight < stateBlockHeight`
|
||||||
replay all blocks in full from `appBlockHeight` to `storeBlockHeight-1`,
|
replay all blocks in full from `appBlockHeight` to `storeBlockHeight-1`,
|
||||||
and replay the block at `storeBlockHeight` using the WAL.
|
and replay the block at `storeBlockHeight` using the WAL.
|
||||||
This happens if the app forgot the last block it committed.
|
This happens if the app forgot the last block it committed.
|
||||||
|
|
||||||
If `appBlockHeight == stateBlockHeight`,
|
If `appBlockHeight == stateBlockHeight`,
|
||||||
replay the last block (storeBlockHeight) in full.
|
replay the last block (storeBlockHeight) in full.
|
||||||
This happens if we crashed before the app finished Commit
|
This happens if we crashed before the app finished Commit
|
||||||
|
|
||||||
If appBlockHeight == storeBlockHeight {
|
If `appBlockHeight == storeBlockHeight`
|
||||||
update the state using the saved ABCI responses but dont run the block against the real app.
|
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.
|
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
|
len(block.ChainID) < 50
|
||||||
```
|
```
|
||||||
|
|
||||||
ChainID must be maximum 50 UTF-8 symbols.
|
ChainID must be less than 50 bytes.
|
||||||
|
|
||||||
### Height
|
### 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.
|
@ -170,7 +170,7 @@ seed_mode = false
|
|||||||
private_peer_ids = ""
|
private_peer_ids = ""
|
||||||
|
|
||||||
# Toggle to disable guard against peers connecting from the same ip.
|
# Toggle to disable guard against peers connecting from the same ip.
|
||||||
allow_duplicate_ip = true
|
allow_duplicate_ip = false
|
||||||
|
|
||||||
# Peer connection configuration.
|
# Peer connection configuration.
|
||||||
handshake_timeout = "20s"
|
handshake_timeout = "20s"
|
||||||
|
@ -113,7 +113,7 @@ blocks are produced regularly, even if there are no transactions. See
|
|||||||
_No Empty Blocks_, below, to modify this setting.
|
_No Empty Blocks_, below, to modify this setting.
|
||||||
|
|
||||||
Tendermint supports in-process versions of the `counter`, `kvstore` and
|
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
|
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
|
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
|
`--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
50
node/node.go
50
node/node.go
@ -7,6 +7,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -86,8 +87,26 @@ func DefaultNewNode(config *cfg.Config, logger log.Logger) (*Node, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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,
|
return NewNode(config,
|
||||||
privval.LoadOrGenFilePV(config.PrivValidatorFile()),
|
privval.LoadOrGenFilePV(newPrivValKey, newPrivValState),
|
||||||
nodeKey,
|
nodeKey,
|
||||||
proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()),
|
proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()),
|
||||||
DefaultGenesisDocProviderFunc(config),
|
DefaultGenesisDocProviderFunc(config),
|
||||||
@ -240,16 +259,19 @@ func NewNode(config *cfg.Config,
|
|||||||
fastSync := config.FastSync
|
fastSync := config.FastSync
|
||||||
if state.Validators.Size() == 1 {
|
if state.Validators.Size() == 1 {
|
||||||
addr, _ := state.Validators.GetByIndex(0)
|
addr, _ := state.Validators.GetByIndex(0)
|
||||||
if bytes.Equal(privValidator.GetAddress(), addr) {
|
privValAddr := privValidator.GetPubKey().Address()
|
||||||
|
if bytes.Equal(privValAddr, addr) {
|
||||||
fastSync = false
|
fastSync = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pubKey := privValidator.GetPubKey()
|
||||||
|
addr := pubKey.Address()
|
||||||
// Log whether this node is a validator or an observer
|
// Log whether this node is a validator or an observer
|
||||||
if state.Validators.HasAddress(privValidator.GetAddress()) {
|
if state.Validators.HasAddress(addr) {
|
||||||
consensusLogger.Info("This node is a validator", "addr", privValidator.GetAddress(), "pubKey", privValidator.GetPubKey())
|
consensusLogger.Info("This node is a validator", "addr", addr, "pubKey", pubKey)
|
||||||
} else {
|
} 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()
|
csMetrics, p2pMetrics, memplMetrics, smMetrics := metricsProvider()
|
||||||
@ -617,7 +639,8 @@ func (n *Node) ConfigureRPC() {
|
|||||||
rpccore.SetEvidencePool(n.evidencePool)
|
rpccore.SetEvidencePool(n.evidencePool)
|
||||||
rpccore.SetP2PPeers(n.sw)
|
rpccore.SetP2PPeers(n.sw)
|
||||||
rpccore.SetP2PTransport(n)
|
rpccore.SetP2PTransport(n)
|
||||||
rpccore.SetPubKey(n.privValidator.GetPubKey())
|
pubKey := n.privValidator.GetPubKey()
|
||||||
|
rpccore.SetPubKey(pubKey)
|
||||||
rpccore.SetGenesisDoc(n.genesisDoc)
|
rpccore.SetGenesisDoc(n.genesisDoc)
|
||||||
rpccore.SetAddrBook(n.addrBook)
|
rpccore.SetAddrBook(n.addrBook)
|
||||||
rpccore.SetProxyAppQuery(n.proxyApp.Query())
|
rpccore.SetProxyAppQuery(n.proxyApp.Query())
|
||||||
@ -855,16 +878,20 @@ func createAndStartPrivValidatorSocketClient(
|
|||||||
listenAddr string,
|
listenAddr string,
|
||||||
logger log.Logger,
|
logger log.Logger,
|
||||||
) (types.PrivValidator, error) {
|
) (types.PrivValidator, error) {
|
||||||
var pvsc types.PrivValidator
|
var listener net.Listener
|
||||||
|
|
||||||
protocol, address := cmn.ProtocolAndAddress(listenAddr)
|
protocol, address := cmn.ProtocolAndAddress(listenAddr)
|
||||||
|
ln, err := net.Listen(protocol, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
switch protocol {
|
switch protocol {
|
||||||
case "unix":
|
case "unix":
|
||||||
pvsc = privval.NewIPCVal(logger.With("module", "privval"), address)
|
listener = privval.NewUnixListener(ln)
|
||||||
case "tcp":
|
case "tcp":
|
||||||
// TODO: persist this key so external signer
|
// TODO: persist this key so external signer
|
||||||
// can actually authenticate us
|
// can actually authenticate us
|
||||||
pvsc = privval.NewTCPVal(logger.With("module", "privval"), listenAddr, ed25519.GenPrivKey())
|
listener = privval.NewTCPListener(ln, ed25519.GenPrivKey())
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"Wrong listen address: expected either 'tcp' or 'unix' protocols, got %s",
|
"Wrong listen address: expected either 'tcp' or 'unix' protocols, got %s",
|
||||||
@ -872,10 +899,9 @@ func createAndStartPrivValidatorSocketClient(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if pvsc, ok := pvsc.(cmn.Service); ok {
|
pvsc := privval.NewSocketVal(logger.With("module", "privval"), listener)
|
||||||
if err := pvsc.Start(); err != nil {
|
if err := pvsc.Start(); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to start")
|
return nil, errors.Wrap(err, "failed to start private validator")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pvsc, nil
|
return pvsc, nil
|
||||||
|
@ -122,25 +122,25 @@ func TestNodeSetPrivValTCP(t *testing.T) {
|
|||||||
config := cfg.ResetTestRoot("node_priv_val_tcp_test")
|
config := cfg.ResetTestRoot("node_priv_val_tcp_test")
|
||||||
config.BaseConfig.PrivValidatorListenAddr = addr
|
config.BaseConfig.PrivValidatorListenAddr = addr
|
||||||
|
|
||||||
rs := privval.NewRemoteSigner(
|
dialer := privval.DialTCPFn(addr, 100*time.Millisecond, ed25519.GenPrivKey())
|
||||||
|
pvsc := privval.NewRemoteSigner(
|
||||||
log.TestingLogger(),
|
log.TestingLogger(),
|
||||||
config.ChainID(),
|
config.ChainID(),
|
||||||
addr,
|
|
||||||
types.NewMockPV(),
|
types.NewMockPV(),
|
||||||
ed25519.GenPrivKey(),
|
dialer,
|
||||||
)
|
)
|
||||||
privval.RemoteSignerConnDeadline(5 * time.Millisecond)(rs)
|
|
||||||
go func() {
|
go func() {
|
||||||
err := rs.Start()
|
err := pvsc.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
defer rs.Stop()
|
defer pvsc.Stop()
|
||||||
|
|
||||||
n, err := DefaultNewNode(config, log.TestingLogger())
|
n, err := DefaultNewNode(config, log.TestingLogger())
|
||||||
require.NoError(t, err)
|
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
|
// 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 := cfg.ResetTestRoot("node_priv_val_tcp_test")
|
||||||
config.BaseConfig.PrivValidatorListenAddr = "unix://" + tmpfile
|
config.BaseConfig.PrivValidatorListenAddr = "unix://" + tmpfile
|
||||||
|
|
||||||
rs := privval.NewIPCRemoteSigner(
|
dialer := privval.DialUnixFn(tmpfile)
|
||||||
|
pvsc := privval.NewRemoteSigner(
|
||||||
log.TestingLogger(),
|
log.TestingLogger(),
|
||||||
config.ChainID(),
|
config.ChainID(),
|
||||||
tmpfile,
|
|
||||||
types.NewMockPV(),
|
types.NewMockPV(),
|
||||||
|
dialer,
|
||||||
)
|
)
|
||||||
privval.IPCRemoteSignerConnDeadline(3 * time.Second)(rs)
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
defer close(done)
|
defer close(done)
|
||||||
n, err := DefaultNewNode(config, log.TestingLogger())
|
n, err := DefaultNewNode(config, log.TestingLogger())
|
||||||
require.NoError(t, err)
|
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)
|
require.NoError(t, err)
|
||||||
defer rs.Stop()
|
defer pvsc.Stop()
|
||||||
|
|
||||||
<-done
|
<-done
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,8 @@ The p2p package provides an abstraction around peer-to-peer communication.
|
|||||||
|
|
||||||
Docs:
|
Docs:
|
||||||
|
|
||||||
- [Connection](https://github.com/tendermint/tendermint/blob/master/docs/spec/docs/spec/p2p/connection.md) for details on how connections and multiplexing work
|
- [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/docs/spec/p2p/peer.md) for details on peer ID, handshakes, and peer exchange
|
- [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/docs/spec/p2p/node.md) for details about different types of nodes and how they should work
|
- [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/docs/spec/reactors/pex/pex.md) for details on peer discovery and exchange
|
- [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/docs/spec/p2p/config.md) for details on some config option
|
- [Config](https://github.com/tendermint/tendermint/blob/master/docs/spec/p2p/config.md) for details on some config option
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
@ -27,20 +28,36 @@ const aeadSizeOverhead = 16 // overhead of poly 1305 authentication tag
|
|||||||
const aeadKeySize = chacha20poly1305.KeySize
|
const aeadKeySize = chacha20poly1305.KeySize
|
||||||
const aeadNonceSize = chacha20poly1305.NonceSize
|
const aeadNonceSize = chacha20poly1305.NonceSize
|
||||||
|
|
||||||
// SecretConnection implements net.conn.
|
// SecretConnection implements net.Conn.
|
||||||
// It is an implementation of the STS protocol.
|
// It is an implementation of the STS protocol.
|
||||||
// Note we do not (yet) assume that a remote peer's pubkey
|
// See https://github.com/tendermint/tendermint/blob/0.1/docs/sts-final.pdf for
|
||||||
// is known ahead of time, and thus we are technically
|
// details on the protocol.
|
||||||
// still vulnerable to MITM. (TODO!)
|
//
|
||||||
// See docs/sts-final.pdf for more info
|
// 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 {
|
type SecretConnection struct {
|
||||||
conn io.ReadWriteCloser
|
|
||||||
recvBuffer []byte
|
// immutable
|
||||||
recvNonce *[aeadNonceSize]byte
|
|
||||||
sendNonce *[aeadNonceSize]byte
|
|
||||||
recvSecret *[aeadKeySize]byte
|
recvSecret *[aeadKeySize]byte
|
||||||
sendSecret *[aeadKeySize]byte
|
sendSecret *[aeadKeySize]byte
|
||||||
remPubKey crypto.PubKey
|
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
|
// MakeSecretConnection performs handshake and returns a new authenticated
|
||||||
@ -109,9 +126,12 @@ func (sc *SecretConnection) RemotePubKey() crypto.PubKey {
|
|||||||
return sc.remPubKey
|
return sc.remPubKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// Writes encrypted frames of `sealedFrameSize`
|
// Writes encrypted frames of `totalFrameSize + aeadSizeOverhead`.
|
||||||
// CONTRACT: data smaller than dataMaxSize is read atomically.
|
// CONTRACT: data smaller than dataMaxSize is written atomically.
|
||||||
func (sc *SecretConnection) Write(data []byte) (n int, err error) {
|
func (sc *SecretConnection) Write(data []byte) (n int, err error) {
|
||||||
|
sc.sendMtx.Lock()
|
||||||
|
defer sc.sendMtx.Unlock()
|
||||||
|
|
||||||
for 0 < len(data) {
|
for 0 < len(data) {
|
||||||
var frame = make([]byte, totalFrameSize)
|
var frame = make([]byte, totalFrameSize)
|
||||||
var chunk []byte
|
var chunk []byte
|
||||||
@ -130,6 +150,7 @@ func (sc *SecretConnection) Write(data []byte) (n int, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return n, errors.New("Invalid SecretConnection Key")
|
return n, errors.New("Invalid SecretConnection Key")
|
||||||
}
|
}
|
||||||
|
|
||||||
// encrypt the frame
|
// encrypt the frame
|
||||||
var sealedFrame = make([]byte, aeadSizeOverhead+totalFrameSize)
|
var sealedFrame = make([]byte, aeadSizeOverhead+totalFrameSize)
|
||||||
aead.Seal(sealedFrame[:0], sc.sendNonce[:], frame, nil)
|
aead.Seal(sealedFrame[:0], sc.sendNonce[:], frame, nil)
|
||||||
@ -147,23 +168,30 @@ func (sc *SecretConnection) Write(data []byte) (n int, err error) {
|
|||||||
|
|
||||||
// CONTRACT: data smaller than dataMaxSize is read atomically.
|
// CONTRACT: data smaller than dataMaxSize is read atomically.
|
||||||
func (sc *SecretConnection) Read(data []byte) (n int, err error) {
|
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) {
|
if 0 < len(sc.recvBuffer) {
|
||||||
n = copy(data, sc.recvBuffer)
|
n = copy(data, sc.recvBuffer)
|
||||||
sc.recvBuffer = sc.recvBuffer[n:]
|
sc.recvBuffer = sc.recvBuffer[n:]
|
||||||
return
|
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[:])
|
aead, err := chacha20poly1305.New(sc.recvSecret[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, errors.New("Invalid SecretConnection Key")
|
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)
|
var frame = make([]byte, totalFrameSize)
|
||||||
_, err = aead.Open(frame[:0], sc.recvNonce[:], sealedFrame, nil)
|
_, err = aead.Open(frame[:0], sc.recvNonce[:], sealedFrame, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -172,12 +200,13 @@ func (sc *SecretConnection) Read(data []byte) (n int, err error) {
|
|||||||
incrNonce(sc.recvNonce)
|
incrNonce(sc.recvNonce)
|
||||||
// end decryption
|
// end decryption
|
||||||
|
|
||||||
|
// copy checkLength worth into data,
|
||||||
|
// set recvBuffer to the rest.
|
||||||
var chunkLength = binary.LittleEndian.Uint32(frame) // read the first four bytes
|
var chunkLength = binary.LittleEndian.Uint32(frame) // read the first four bytes
|
||||||
if chunkLength > dataMaxSize {
|
if chunkLength > dataMaxSize {
|
||||||
return 0, errors.New("chunkLength is greater than dataMaxSize")
|
return 0, errors.New("chunkLength is greater than dataMaxSize")
|
||||||
}
|
}
|
||||||
var chunk = frame[dataLenSize : dataLenSize+chunkLength]
|
var chunk = frame[dataLenSize : dataLenSize+chunkLength]
|
||||||
|
|
||||||
n = copy(data, chunk)
|
n = copy(data, chunk)
|
||||||
sc.recvBuffer = chunk[n:]
|
sc.recvBuffer = chunk[n:]
|
||||||
return
|
return
|
||||||
|
@ -7,10 +7,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"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) {
|
func TestSecretConnectionReadWrite(t *testing.T) {
|
||||||
fooConn, barConn := makeKVStoreConnPair()
|
fooConn, barConn := makeKVStoreConnPair()
|
||||||
fooWrites, barWrites := []string{}, []string{}
|
fooWrites, barWrites := []string{}, []string{}
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxNodeInfoSize = 10240 // 10Kb
|
maxNodeInfoSize = 10240 // 10KB
|
||||||
maxNumChannels = 16 // plenty of room for upgrades, for now
|
maxNumChannels = 16 // plenty of room for upgrades, for now
|
||||||
)
|
)
|
||||||
|
|
||||||
|
238
privval/client.go
Normal file
238
privval/client.go
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
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() (closed bool, err 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 socket val connection during reset", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for a new conn
|
||||||
|
conn, err := sc.acceptConnection()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to accept a connection.
|
||||||
|
// Times out after the listener's acceptDeadline
|
||||||
|
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
|
||||||
|
}
|
466
privval/client_test.go
Normal file
466
privval/client_test.go
Normal file
@ -0,0 +1,466 @@
|
|||||||
|
package privval
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
type socketTestCase struct {
|
||||||
|
addr string
|
||||||
|
dialer Dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
func socketTestCases(t *testing.T) []socketTestCase {
|
||||||
|
tcpAddr := fmt.Sprintf("tcp://%s", testFreeTCPAddr(t))
|
||||||
|
unixFilePath, err := testUnixAddr()
|
||||||
|
require.NoError(t, err)
|
||||||
|
unixAddr := fmt.Sprintf("unix://%s", unixFilePath)
|
||||||
|
return []socketTestCase{
|
||||||
|
socketTestCase{
|
||||||
|
addr: tcpAddr,
|
||||||
|
dialer: DialTCPFn(tcpAddr, testConnDeadline, ed25519.GenPrivKey()),
|
||||||
|
},
|
||||||
|
socketTestCase{
|
||||||
|
addr: unixAddr,
|
||||||
|
dialer: DialUnixFn(unixFilePath),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSocketPVAddress(t *testing.T) {
|
||||||
|
for _, tc := range socketTestCases(t) {
|
||||||
|
// Execute the test within a closure to ensure the deferred statements
|
||||||
|
// are called between each for loop iteration, for isolated test cases.
|
||||||
|
func() {
|
||||||
|
var (
|
||||||
|
chainID = cmn.RandStr(12)
|
||||||
|
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer)
|
||||||
|
)
|
||||||
|
defer sc.Stop()
|
||||||
|
defer rs.Stop()
|
||||||
|
|
||||||
|
serverAddr := rs.privVal.GetPubKey().Address()
|
||||||
|
clientAddr := sc.GetPubKey().Address()
|
||||||
|
|
||||||
|
assert.Equal(t, serverAddr, clientAddr)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSocketPVPubKey(t *testing.T) {
|
||||||
|
for _, tc := range socketTestCases(t) {
|
||||||
|
func() {
|
||||||
|
var (
|
||||||
|
chainID = cmn.RandStr(12)
|
||||||
|
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer)
|
||||||
|
)
|
||||||
|
defer sc.Stop()
|
||||||
|
defer rs.Stop()
|
||||||
|
|
||||||
|
clientKey := sc.GetPubKey()
|
||||||
|
|
||||||
|
privvalPubKey := rs.privVal.GetPubKey()
|
||||||
|
|
||||||
|
assert.Equal(t, privvalPubKey, clientKey)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSocketPVProposal(t *testing.T) {
|
||||||
|
for _, tc := range socketTestCases(t) {
|
||||||
|
func() {
|
||||||
|
var (
|
||||||
|
chainID = cmn.RandStr(12)
|
||||||
|
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer)
|
||||||
|
|
||||||
|
ts = time.Now()
|
||||||
|
privProposal = &types.Proposal{Timestamp: ts}
|
||||||
|
clientProposal = &types.Proposal{Timestamp: ts}
|
||||||
|
)
|
||||||
|
defer sc.Stop()
|
||||||
|
defer rs.Stop()
|
||||||
|
|
||||||
|
require.NoError(t, rs.privVal.SignProposal(chainID, privProposal))
|
||||||
|
require.NoError(t, sc.SignProposal(chainID, clientProposal))
|
||||||
|
assert.Equal(t, privProposal.Signature, clientProposal.Signature)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSocketPVVote(t *testing.T) {
|
||||||
|
for _, tc := range socketTestCases(t) {
|
||||||
|
func() {
|
||||||
|
var (
|
||||||
|
chainID = cmn.RandStr(12)
|
||||||
|
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer)
|
||||||
|
|
||||||
|
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 TestSocketPVVoteResetDeadline(t *testing.T) {
|
||||||
|
for _, tc := range socketTestCases(t) {
|
||||||
|
func() {
|
||||||
|
var (
|
||||||
|
chainID = cmn.RandStr(12)
|
||||||
|
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer)
|
||||||
|
|
||||||
|
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(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(testConnDeadline2o3)
|
||||||
|
|
||||||
|
require.NoError(t, rs.privVal.SignVote(chainID, want))
|
||||||
|
require.NoError(t, sc.SignVote(chainID, have))
|
||||||
|
assert.Equal(t, want.Signature, have.Signature)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSocketPVVoteKeepalive(t *testing.T) {
|
||||||
|
for _, tc := range socketTestCases(t) {
|
||||||
|
func() {
|
||||||
|
var (
|
||||||
|
chainID = cmn.RandStr(12)
|
||||||
|
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer)
|
||||||
|
|
||||||
|
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(testConnDeadline * 2)
|
||||||
|
|
||||||
|
require.NoError(t, rs.privVal.SignVote(chainID, want))
|
||||||
|
require.NoError(t, sc.SignVote(chainID, have))
|
||||||
|
assert.Equal(t, want.Signature, have.Signature)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSocketPVDeadlineTCPOnly is not relevant to Unix domain sockets, since the
|
||||||
|
// OS knows instantaneously the state of both sides of the connection.
|
||||||
|
func TestSocketPVDeadlineTCPOnly(t *testing.T) {
|
||||||
|
var (
|
||||||
|
addr = testFreeTCPAddr(t)
|
||||||
|
listenc = make(chan struct{})
|
||||||
|
thisConnTimeout = 100 * time.Millisecond
|
||||||
|
sc = newSocketVal(log.TestingLogger(), addr, thisConnTimeout)
|
||||||
|
)
|
||||||
|
|
||||||
|
go func(sc *SocketVal) {
|
||||||
|
defer close(listenc)
|
||||||
|
|
||||||
|
assert.Equal(t, sc.Start().(cmn.Error).Data(), ErrConnTimeout)
|
||||||
|
|
||||||
|
assert.False(t, sc.IsRunning())
|
||||||
|
}(sc)
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := cmn.Connect(addr)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = p2pconn.MakeSecretConnection(
|
||||||
|
conn,
|
||||||
|
ed25519.GenPrivKey(),
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<-listenc
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoteSignVoteErrors(t *testing.T) {
|
||||||
|
for _, tc := range socketTestCases(t) {
|
||||||
|
func() {
|
||||||
|
var (
|
||||||
|
chainID = cmn.RandStr(12)
|
||||||
|
sc, rs = testSetupSocketPair(t, chainID, types.NewErroringMockPV(), tc.addr, tc.dialer)
|
||||||
|
|
||||||
|
ts = time.Now()
|
||||||
|
vType = types.PrecommitType
|
||||||
|
vote = &types.Vote{Timestamp: ts, Type: vType}
|
||||||
|
)
|
||||||
|
defer sc.Stop()
|
||||||
|
defer rs.Stop()
|
||||||
|
|
||||||
|
err := sc.SignVote("", vote)
|
||||||
|
require.Equal(t, err.(*RemoteSignerError).Description, types.ErroringMockPVErr.Error())
|
||||||
|
|
||||||
|
err = rs.privVal.SignVote(chainID, vote)
|
||||||
|
require.Error(t, err)
|
||||||
|
err = sc.SignVote(chainID, vote)
|
||||||
|
require.Error(t, err)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoteSignProposalErrors(t *testing.T) {
|
||||||
|
for _, tc := range socketTestCases(t) {
|
||||||
|
func() {
|
||||||
|
var (
|
||||||
|
chainID = cmn.RandStr(12)
|
||||||
|
sc, rs = testSetupSocketPair(t, chainID, types.NewErroringMockPV(), tc.addr, tc.dialer)
|
||||||
|
|
||||||
|
ts = time.Now()
|
||||||
|
proposal = &types.Proposal{Timestamp: ts}
|
||||||
|
)
|
||||||
|
defer sc.Stop()
|
||||||
|
defer rs.Stop()
|
||||||
|
|
||||||
|
err := sc.SignProposal("", proposal)
|
||||||
|
require.Equal(t, err.(*RemoteSignerError).Description, types.ErroringMockPVErr.Error())
|
||||||
|
|
||||||
|
err = rs.privVal.SignProposal(chainID, proposal)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
err = sc.SignProposal(chainID, proposal)
|
||||||
|
require.Error(t, err)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrUnexpectedResponse(t *testing.T) {
|
||||||
|
for _, tc := range socketTestCases(t) {
|
||||||
|
func() {
|
||||||
|
var (
|
||||||
|
logger = log.TestingLogger()
|
||||||
|
chainID = cmn.RandStr(12)
|
||||||
|
readyc = make(chan struct{})
|
||||||
|
errc = make(chan error, 1)
|
||||||
|
|
||||||
|
rs = NewRemoteSigner(
|
||||||
|
logger,
|
||||||
|
chainID,
|
||||||
|
types.NewMockPV(),
|
||||||
|
tc.dialer,
|
||||||
|
)
|
||||||
|
sc = newSocketVal(logger, tc.addr, testConnDeadline)
|
||||||
|
)
|
||||||
|
|
||||||
|
testStartSocketPV(t, readyc, sc)
|
||||||
|
defer sc.Stop()
|
||||||
|
RemoteSignerConnDeadline(time.Millisecond)(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:
|
||||||
|
go func(errc chan error) {
|
||||||
|
errc <- sc.SignProposal(chainID, &types.Proposal{})
|
||||||
|
}(errc)
|
||||||
|
// read request and write wrong response:
|
||||||
|
go testReadWriteResponse(t, &SignedVoteResponse{}, rsConn)
|
||||||
|
err = <-errc
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, err, ErrUnexpectedResponse)
|
||||||
|
|
||||||
|
// Vote:
|
||||||
|
go func(errc chan error) {
|
||||||
|
errc <- sc.SignVote(chainID, &types.Vote{})
|
||||||
|
}(errc)
|
||||||
|
// read request and write wrong response:
|
||||||
|
go testReadWriteResponse(t, &SignedProposalResponse{}, rsConn)
|
||||||
|
err = <-errc
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, err, ErrUnexpectedResponse)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRetryConnToRemoteSigner(t *testing.T) {
|
||||||
|
for _, tc := range socketTestCases(t) {
|
||||||
|
func() {
|
||||||
|
var (
|
||||||
|
logger = log.TestingLogger()
|
||||||
|
chainID = cmn.RandStr(12)
|
||||||
|
readyc = make(chan struct{})
|
||||||
|
|
||||||
|
rs = NewRemoteSigner(
|
||||||
|
logger,
|
||||||
|
chainID,
|
||||||
|
types.NewMockPV(),
|
||||||
|
tc.dialer,
|
||||||
|
)
|
||||||
|
thisConnTimeout = testConnDeadline
|
||||||
|
sc = newSocketVal(logger, tc.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(),
|
||||||
|
tc.dialer,
|
||||||
|
)
|
||||||
|
// 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 {
|
||||||
|
proto, address := cmn.ProtocolAndAddress(addr)
|
||||||
|
ln, err := net.Listen(proto, address)
|
||||||
|
logger.Info("Listening at", "proto", proto, "address", address)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var svln net.Listener
|
||||||
|
if proto == "unix" {
|
||||||
|
unixLn := NewUnixListener(ln)
|
||||||
|
UnixListenerAcceptDeadline(testAcceptDeadline)(unixLn)
|
||||||
|
UnixListenerConnDeadline(connDeadline)(unixLn)
|
||||||
|
svln = unixLn
|
||||||
|
} else {
|
||||||
|
tcpLn := NewTCPListener(ln, ed25519.GenPrivKey())
|
||||||
|
TCPListenerAcceptDeadline(testAcceptDeadline)(tcpLn)
|
||||||
|
TCPListenerConnDeadline(connDeadline)(tcpLn)
|
||||||
|
svln = tcpLn
|
||||||
|
}
|
||||||
|
return NewSocketVal(logger, svln)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSetupSocketPair(
|
||||||
|
t *testing.T,
|
||||||
|
chainID string,
|
||||||
|
privValidator types.PrivValidator,
|
||||||
|
addr string,
|
||||||
|
dialer Dialer,
|
||||||
|
) (*SocketVal, *RemoteSigner) {
|
||||||
|
var (
|
||||||
|
logger = log.TestingLogger()
|
||||||
|
privVal = privValidator
|
||||||
|
readyc = make(chan struct{})
|
||||||
|
rs = NewRemoteSigner(
|
||||||
|
logger,
|
||||||
|
chainID,
|
||||||
|
privVal,
|
||||||
|
dialer,
|
||||||
|
)
|
||||||
|
|
||||||
|
thisConnTimeout = testConnDeadline
|
||||||
|
sc = newSocketVal(logger, addr, thisConnTimeout)
|
||||||
|
)
|
||||||
|
|
||||||
|
SocketValHeartbeat(testHeartbeatTimeout)(sc)
|
||||||
|
RemoteSignerConnDeadline(testConnDeadline)(rs)
|
||||||
|
RemoteSignerConnRetries(1e6)(rs)
|
||||||
|
|
||||||
|
testStartSocketPV(t, readyc, sc)
|
||||||
|
|
||||||
|
require.NoError(t, rs.Start())
|
||||||
|
assert.True(t, rs.IsRunning())
|
||||||
|
|
||||||
|
<-readyc
|
||||||
|
|
||||||
|
return sc, rs
|
||||||
|
}
|
||||||
|
|
||||||
|
func testReadWriteResponse(t *testing.T, resp RemoteSignerMsg, rsConn net.Conn) {
|
||||||
|
_, err := readMsg(rsConn)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = writeMsg(rsConn, resp)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testStartSocketPV(t *testing.T, readyc chan struct{}, sc *SocketVal) {
|
||||||
|
go func(sc *SocketVal) {
|
||||||
|
require.NoError(t, sc.Start())
|
||||||
|
assert.True(t, sc.IsRunning())
|
||||||
|
|
||||||
|
readyc <- struct{}{}
|
||||||
|
}(sc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// testFreeTCPAddr claims a free port so we don't block on listener being ready.
|
||||||
|
func testFreeTCPAddr(t *testing.T) string {
|
||||||
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
return fmt.Sprintf("127.0.0.1:%d", ln.Addr().(*net.TCPAddr).Port)
|
||||||
|
}
|
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"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/crypto"
|
"github.com/tendermint/tendermint/crypto"
|
||||||
@ -23,6 +22,7 @@ const (
|
|||||||
stepPrecommit int8 = 3
|
stepPrecommit int8 = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// A vote is either stepPrevote or stepPrecommit.
|
||||||
func voteToStep(vote *types.Vote) int8 {
|
func voteToStep(vote *types.Vote) int8 {
|
||||||
switch vote.Type {
|
switch vote.Type {
|
||||||
case types.PrevoteType:
|
case types.PrevoteType:
|
||||||
@ -30,105 +30,95 @@ func voteToStep(vote *types.Vote) int8 {
|
|||||||
case types.PrecommitType:
|
case types.PrecommitType:
|
||||||
return stepPrecommit
|
return stepPrecommit
|
||||||
default:
|
default:
|
||||||
cmn.PanicSanity("Unknown vote type")
|
panic("Unknown vote type")
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilePV implements PrivValidator using data persisted to disk
|
//-------------------------------------------------------------------------------
|
||||||
// to prevent double signing.
|
|
||||||
// NOTE: the directory containing the pv.filePath must already exist.
|
// FilePVKey stores the immutable part of PrivValidator.
|
||||||
// It includes the LastSignature and LastSignBytes so we don't lose the signature
|
type FilePVKey struct {
|
||||||
// if the process crashes after signing but before the resulting consensus message is processed.
|
|
||||||
type FilePV struct {
|
|
||||||
Address types.Address `json:"address"`
|
Address types.Address `json:"address"`
|
||||||
PubKey crypto.PubKey `json:"pub_key"`
|
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"`
|
PrivKey crypto.PrivKey `json:"priv_key"`
|
||||||
|
|
||||||
// For persistence.
|
|
||||||
// Overloaded for testing.
|
|
||||||
filePath string
|
filePath string
|
||||||
mtx sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAddress returns the address of the validator.
|
// Save persists the FilePVKey to its filePath.
|
||||||
// Implements PrivValidator.
|
func (pvKey FilePVKey) Save() {
|
||||||
func (pv *FilePV) GetAddress() types.Address {
|
outFile := pvKey.filePath
|
||||||
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
|
|
||||||
if outFile == "" {
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -138,23 +128,115 @@ func (pv *FilePV) save() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset resets all fields in the FilePV.
|
//-------------------------------------------------------------------------------
|
||||||
// NOTE: Unsafe!
|
|
||||||
func (pv *FilePV) Reset() {
|
// FilePV implements PrivValidator using data persisted to disk
|
||||||
var sig []byte
|
// to prevent double signing.
|
||||||
pv.LastHeight = 0
|
// NOTE: the directories containing pv.Key.filePath and pv.LastSignState.filePath must already exist.
|
||||||
pv.LastRound = 0
|
// It includes the LastSignature and LastSignBytes so we don't lose the signature
|
||||||
pv.LastStep = 0
|
// if the process crashes after signing but before the resulting consensus message is processed.
|
||||||
pv.LastSignature = sig
|
type FilePV struct {
|
||||||
pv.LastSignBytes = nil
|
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()
|
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
|
// SignVote signs a canonical representation of the vote, along with the
|
||||||
// chainID. Implements PrivValidator.
|
// chainID. Implements PrivValidator.
|
||||||
func (pv *FilePV) SignVote(chainID string, vote *types.Vote) error {
|
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 {
|
if err := pv.signVote(chainID, vote); err != nil {
|
||||||
return fmt.Errorf("Error signing vote: %v", err)
|
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
|
// SignProposal signs a canonical representation of the proposal, along with
|
||||||
// the chainID. Implements PrivValidator.
|
// the chainID. Implements PrivValidator.
|
||||||
func (pv *FilePV) SignProposal(chainID string, proposal *types.Proposal) error {
|
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 {
|
if err := pv.signProposal(chainID, proposal); err != nil {
|
||||||
return fmt.Errorf("Error signing proposal: %v", err)
|
return fmt.Errorf("Error signing proposal: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns error if HRS regression or no LastSignBytes. returns true if HRS is unchanged
|
// Save persists the FilePV to disk.
|
||||||
func (pv *FilePV) checkHRS(height int64, round int, step int8) (bool, error) {
|
func (pv *FilePV) Save() {
|
||||||
if pv.LastHeight > height {
|
pv.Key.Save()
|
||||||
return false, errors.New("Height regression")
|
pv.LastSignState.Save()
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.
|
// 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
|
// 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).
|
// 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 {
|
func (pv *FilePV) signVote(chainID string, vote *types.Vote) error {
|
||||||
height, round, step := vote.Height, vote.Round, voteToStep(vote)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signBytes := vote.SignBytes(chainID)
|
||||||
|
|
||||||
// We might crash before writing to the wal,
|
// We might crash before writing to the wal,
|
||||||
// causing us to try to re-sign for the same HRS.
|
// causing us to try to re-sign for the same HRS.
|
||||||
// If signbytes are the same, use the last signature.
|
// If signbytes are the same, use the last signature.
|
||||||
// If they only differ by timestamp, use last timestamp and signature
|
// If they only differ by timestamp, use last timestamp and signature
|
||||||
// Otherwise, return error
|
// Otherwise, return error
|
||||||
if sameHRS {
|
if sameHRS {
|
||||||
if bytes.Equal(signBytes, pv.LastSignBytes) {
|
if bytes.Equal(signBytes, lss.SignBytes) {
|
||||||
vote.Signature = pv.LastSignature
|
vote.Signature = lss.Signature
|
||||||
} else if timestamp, ok := checkVotesOnlyDifferByTimestamp(pv.LastSignBytes, signBytes); ok {
|
} else if timestamp, ok := checkVotesOnlyDifferByTimestamp(lss.SignBytes, signBytes); ok {
|
||||||
vote.Timestamp = timestamp
|
vote.Timestamp = timestamp
|
||||||
vote.Signature = pv.LastSignature
|
vote.Signature = lss.Signature
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("Conflicting data")
|
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
|
// It passed the checks. Sign the vote
|
||||||
sig, err := pv.PrivKey.Sign(signBytes)
|
sig, err := pv.Key.PrivKey.Sign(signBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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).
|
// 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 {
|
func (pv *FilePV) signProposal(chainID string, proposal *types.Proposal) error {
|
||||||
height, round, step := proposal.Height, proposal.Round, stepPropose
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signBytes := proposal.SignBytes(chainID)
|
||||||
|
|
||||||
// We might crash before writing to the wal,
|
// We might crash before writing to the wal,
|
||||||
// causing us to try to re-sign for the same HRS.
|
// causing us to try to re-sign for the same HRS.
|
||||||
// If signbytes are the same, use the last signature.
|
// If signbytes are the same, use the last signature.
|
||||||
// If they only differ by timestamp, use last timestamp and signature
|
// If they only differ by timestamp, use last timestamp and signature
|
||||||
// Otherwise, return error
|
// Otherwise, return error
|
||||||
if sameHRS {
|
if sameHRS {
|
||||||
if bytes.Equal(signBytes, pv.LastSignBytes) {
|
if bytes.Equal(signBytes, lss.SignBytes) {
|
||||||
proposal.Signature = pv.LastSignature
|
proposal.Signature = lss.Signature
|
||||||
} else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(pv.LastSignBytes, signBytes); ok {
|
} else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(lss.SignBytes, signBytes); ok {
|
||||||
proposal.Timestamp = timestamp
|
proposal.Timestamp = timestamp
|
||||||
proposal.Signature = pv.LastSignature
|
proposal.Signature = lss.Signature
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("Conflicting data")
|
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
|
// It passed the checks. Sign the proposal
|
||||||
sig, err := pv.PrivKey.Sign(signBytes)
|
sig, err := pv.Key.PrivKey.Sign(signBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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,
|
func (pv *FilePV) saveSigned(height int64, round int, step int8,
|
||||||
signBytes []byte, sig []byte) {
|
signBytes []byte, sig []byte) {
|
||||||
|
|
||||||
pv.LastHeight = height
|
pv.LastSignState.Height = height
|
||||||
pv.LastRound = round
|
pv.LastSignState.Round = round
|
||||||
pv.LastStep = step
|
pv.LastSignState.Step = step
|
||||||
pv.LastSignature = sig
|
pv.LastSignState.Signature = sig
|
||||||
pv.LastSignBytes = signBytes
|
pv.LastSignState.SignBytes = signBytes
|
||||||
pv.save()
|
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 the timestamp from the lastSignBytes.
|
||||||
// returns true if the only difference in the votes is their timestamp.
|
// returns true if the only difference in the votes is their timestamp.
|
@ -18,36 +18,100 @@ import (
|
|||||||
func TestGenLoadValidator(t *testing.T) {
|
func TestGenLoadValidator(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
tempFile, err := ioutil.TempFile("", "priv_validator_")
|
tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_")
|
||||||
require.Nil(t, err)
|
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)
|
height := int64(100)
|
||||||
privVal.LastHeight = height
|
privVal.LastSignState.Height = height
|
||||||
privVal.Save()
|
privVal.Save()
|
||||||
addr := privVal.GetAddress()
|
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(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) {
|
func TestLoadOrGenValidator(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
tempFile, err := ioutil.TempFile("", "priv_validator_")
|
tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
tempFilePath := tempFile.Name()
|
tempStateFile, err := ioutil.TempFile("", "priv_validator_state_")
|
||||||
if err := os.Remove(tempFilePath); err != nil {
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
tempKeyFilePath := tempKeyFile.Name()
|
||||||
|
if err := os.Remove(tempKeyFilePath); err != nil {
|
||||||
t.Error(err)
|
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()
|
addr := privVal.GetAddress()
|
||||||
privVal = LoadOrGenFilePV(tempFilePath)
|
privVal = LoadOrGenFilePV(tempKeyFilePath, tempStateFilePath)
|
||||||
assert.Equal(addr, privVal.GetAddress(), "expected privval addr to be the same")
|
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)
|
assert, require := assert.New(t), require.New(t)
|
||||||
|
|
||||||
// create some fixed values
|
// create some fixed values
|
||||||
@ -67,22 +131,19 @@ func TestUnmarshalValidator(t *testing.T) {
|
|||||||
"type": "tendermint/PubKeyEd25519",
|
"type": "tendermint/PubKeyEd25519",
|
||||||
"value": "%s"
|
"value": "%s"
|
||||||
},
|
},
|
||||||
"last_height": "0",
|
|
||||||
"last_round": "0",
|
|
||||||
"last_step": 0,
|
|
||||||
"priv_key": {
|
"priv_key": {
|
||||||
"type": "tendermint/PrivKeyEd25519",
|
"type": "tendermint/PrivKeyEd25519",
|
||||||
"value": "%s"
|
"value": "%s"
|
||||||
}
|
}
|
||||||
}`, addr, pubB64, privB64)
|
}`, addr, pubB64, privB64)
|
||||||
|
|
||||||
val := FilePV{}
|
val := FilePVKey{}
|
||||||
err := cdc.UnmarshalJSON([]byte(serialized), &val)
|
err := cdc.UnmarshalJSON([]byte(serialized), &val)
|
||||||
require.Nil(err, "%+v", err)
|
require.Nil(err, "%+v", err)
|
||||||
|
|
||||||
// make sure the values match
|
// make sure the values match
|
||||||
assert.EqualValues(addr, val.GetAddress())
|
assert.EqualValues(addr, val.Address)
|
||||||
assert.EqualValues(pubKey, val.GetPubKey())
|
assert.EqualValues(pubKey, val.PubKey)
|
||||||
assert.EqualValues(privKey, val.PrivKey)
|
assert.EqualValues(privKey, val.PrivKey)
|
||||||
|
|
||||||
// export it and make sure it is the same
|
// export it and make sure it is the same
|
||||||
@ -94,9 +155,12 @@ func TestUnmarshalValidator(t *testing.T) {
|
|||||||
func TestSignVote(t *testing.T) {
|
func TestSignVote(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
tempFile, err := ioutil.TempFile("", "priv_validator_")
|
tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_")
|
||||||
require.Nil(t, err)
|
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{}}
|
block1 := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}}
|
||||||
block2 := types.BlockID{[]byte{3, 2, 1}, types.PartSetHeader{}}
|
block2 := types.BlockID{[]byte{3, 2, 1}, types.PartSetHeader{}}
|
||||||
@ -104,7 +168,7 @@ func TestSignVote(t *testing.T) {
|
|||||||
voteType := byte(types.PrevoteType)
|
voteType := byte(types.PrevoteType)
|
||||||
|
|
||||||
// sign a vote for first time
|
// 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)
|
err = privVal.SignVote("mychainid", vote)
|
||||||
assert.NoError(err, "expected no error signing vote")
|
assert.NoError(err, "expected no error signing vote")
|
||||||
|
|
||||||
@ -114,10 +178,10 @@ func TestSignVote(t *testing.T) {
|
|||||||
|
|
||||||
// now try some bad votes
|
// now try some bad votes
|
||||||
cases := []*types.Vote{
|
cases := []*types.Vote{
|
||||||
newVote(privVal.Address, 0, height, round-1, voteType, block1), // round regression
|
newVote(privVal.Key.Address, 0, height, round-1, voteType, block1), // round regression
|
||||||
newVote(privVal.Address, 0, height-1, round, voteType, block1), // height regression
|
newVote(privVal.Key.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.Key.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, voteType, block2), // different block
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
@ -136,9 +200,12 @@ func TestSignVote(t *testing.T) {
|
|||||||
func TestSignProposal(t *testing.T) {
|
func TestSignProposal(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
tempFile, err := ioutil.TempFile("", "priv_validator_")
|
tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_")
|
||||||
require.Nil(t, err)
|
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}}}
|
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}}}
|
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) {
|
func TestDifferByTimestamp(t *testing.T) {
|
||||||
tempFile, err := ioutil.TempFile("", "priv_validator_")
|
tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_")
|
||||||
require.Nil(t, err)
|
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}}}
|
block1 := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{5, []byte{1, 2, 3}}}
|
||||||
height, round := int64(10), 1
|
height, round := int64(10), 1
|
||||||
@ -208,7 +278,7 @@ func TestDifferByTimestamp(t *testing.T) {
|
|||||||
{
|
{
|
||||||
voteType := byte(types.PrevoteType)
|
voteType := byte(types.PrevoteType)
|
||||||
blockID := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}}
|
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)
|
err := privVal.SignVote("mychainid", vote)
|
||||||
assert.NoError(t, err, "expected no error signing 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"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/tendermint/go-amino"
|
"github.com/tendermint/go-amino"
|
||||||
"github.com/tendermint/tendermint/crypto"
|
"github.com/tendermint/tendermint/crypto"
|
||||||
@ -12,68 +13,73 @@ import (
|
|||||||
"github.com/tendermint/tendermint/types"
|
"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.
|
// from an external process.
|
||||||
type RemoteSignerClient struct {
|
type RemoteSignerClient struct {
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
lock sync.Mutex
|
|
||||||
|
// memoized
|
||||||
|
consensusPubKey crypto.PubKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that RemoteSignerClient implements PrivValidator.
|
// Check that RemoteSignerClient implements PrivValidator.
|
||||||
var _ types.PrivValidator = (*RemoteSignerClient)(nil)
|
var _ types.PrivValidator = (*RemoteSignerClient)(nil)
|
||||||
|
|
||||||
// NewRemoteSignerClient returns an instance of RemoteSignerClient.
|
// NewRemoteSignerClient returns an instance of RemoteSignerClient.
|
||||||
func NewRemoteSignerClient(
|
func NewRemoteSignerClient(conn net.Conn) (*RemoteSignerClient, error) {
|
||||||
conn net.Conn,
|
|
||||||
) *RemoteSignerClient {
|
// retrieve and memoize the consensus public key once.
|
||||||
sc := &RemoteSignerClient{
|
pubKey, err := getPubKey(conn)
|
||||||
conn: 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.
|
// Close calls Close on the underlying net.Conn.
|
||||||
func (sc *RemoteSignerClient) GetAddress() types.Address {
|
func (sc *RemoteSignerClient) Close() error {
|
||||||
pubKey, err := sc.getPubKey()
|
return sc.conn.Close()
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pubKey.Address()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPubKey implements PrivValidator.
|
// GetPubKey implements PrivValidator.
|
||||||
func (sc *RemoteSignerClient) GetPubKey() crypto.PubKey {
|
func (sc *RemoteSignerClient) GetPubKey() crypto.PubKey {
|
||||||
pubKey, err := sc.getPubKey()
|
return sc.consensusPubKey
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pubKey
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *RemoteSignerClient) getPubKey() (crypto.PubKey, error) {
|
// not thread-safe (only called on startup).
|
||||||
sc.lock.Lock()
|
func getPubKey(conn net.Conn) (crypto.PubKey, error) {
|
||||||
defer sc.lock.Unlock()
|
err := writeMsg(conn, &PubKeyRequest{})
|
||||||
|
|
||||||
err := writeMsg(sc.conn, &PubKeyMsg{})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := readMsg(sc.conn)
|
res, err := readMsg(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// SignVote implements PrivValidator.
|
||||||
func (sc *RemoteSignerClient) SignVote(chainID string, vote *types.Vote) error {
|
func (sc *RemoteSignerClient) SignVote(chainID string, vote *types.Vote) error {
|
||||||
sc.lock.Lock()
|
|
||||||
defer sc.lock.Unlock()
|
|
||||||
|
|
||||||
err := writeMsg(sc.conn, &SignVoteRequest{Vote: vote})
|
err := writeMsg(sc.conn, &SignVoteRequest{Vote: vote})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -101,9 +107,6 @@ func (sc *RemoteSignerClient) SignProposal(
|
|||||||
chainID string,
|
chainID string,
|
||||||
proposal *types.Proposal,
|
proposal *types.Proposal,
|
||||||
) error {
|
) error {
|
||||||
sc.lock.Lock()
|
|
||||||
defer sc.lock.Unlock()
|
|
||||||
|
|
||||||
err := writeMsg(sc.conn, &SignProposalRequest{Proposal: proposal})
|
err := writeMsg(sc.conn, &SignProposalRequest{Proposal: proposal})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -127,9 +130,6 @@ func (sc *RemoteSignerClient) SignProposal(
|
|||||||
|
|
||||||
// Ping is used to check connection health.
|
// Ping is used to check connection health.
|
||||||
func (sc *RemoteSignerClient) Ping() error {
|
func (sc *RemoteSignerClient) Ping() error {
|
||||||
sc.lock.Lock()
|
|
||||||
defer sc.lock.Unlock()
|
|
||||||
|
|
||||||
err := writeMsg(sc.conn, &PingRequest{})
|
err := writeMsg(sc.conn, &PingRequest{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -152,7 +152,8 @@ type RemoteSignerMsg interface{}
|
|||||||
|
|
||||||
func RegisterRemoteSignerMsg(cdc *amino.Codec) {
|
func RegisterRemoteSignerMsg(cdc *amino.Codec) {
|
||||||
cdc.RegisterInterface((*RemoteSignerMsg)(nil), nil)
|
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(&SignVoteRequest{}, "tendermint/remotesigner/SignVoteRequest", nil)
|
||||||
cdc.RegisterConcrete(&SignedVoteResponse{}, "tendermint/remotesigner/SignedVoteResponse", nil)
|
cdc.RegisterConcrete(&SignedVoteResponse{}, "tendermint/remotesigner/SignedVoteResponse", nil)
|
||||||
cdc.RegisterConcrete(&SignProposalRequest{}, "tendermint/remotesigner/SignProposalRequest", nil)
|
cdc.RegisterConcrete(&SignProposalRequest{}, "tendermint/remotesigner/SignProposalRequest", nil)
|
||||||
@ -161,9 +162,13 @@ func RegisterRemoteSignerMsg(cdc *amino.Codec) {
|
|||||||
cdc.RegisterConcrete(&PingResponse{}, "tendermint/remotesigner/PingResponse", nil)
|
cdc.RegisterConcrete(&PingResponse{}, "tendermint/remotesigner/PingResponse", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PubKeyMsg is a PrivValidatorSocket message containing the public key.
|
// PubKeyRequest requests the consensus public key from the remote signer.
|
||||||
type PubKeyMsg struct {
|
type PubKeyRequest struct{}
|
||||||
|
|
||||||
|
// PubKeyResponse is a PrivValidatorSocket message containing the public key.
|
||||||
|
type PubKeyResponse struct {
|
||||||
PubKey crypto.PubKey
|
PubKey crypto.PubKey
|
||||||
|
Error *RemoteSignerError
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignVoteRequest is a PrivValidatorSocket message containing a vote.
|
// SignVoteRequest is a PrivValidatorSocket message containing a vote.
|
||||||
@ -227,10 +232,10 @@ func handleRequest(req RemoteSignerMsg, chainID string, privVal types.PrivValida
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
switch r := req.(type) {
|
switch r := req.(type) {
|
||||||
case *PubKeyMsg:
|
case *PubKeyRequest:
|
||||||
var p crypto.PubKey
|
var p crypto.PubKey
|
||||||
p = privVal.GetPubKey()
|
p = privVal.GetPubKey()
|
||||||
res = &PubKeyMsg{p}
|
res = &PubKeyResponse{p, nil}
|
||||||
case *SignVoteRequest:
|
case *SignVoteRequest:
|
||||||
err = privVal.SignVote(chainID, r.Vote)
|
err = privVal.SignVote(chainID, r.Vote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
68
privval/remote_signer_test.go
Normal file
68
privval/remote_signer_test.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package privval
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||||
|
cmn "github.com/tendermint/tendermint/libs/common"
|
||||||
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
"github.com/tendermint/tendermint/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestRemoteSignerRetryTCPOnly will test connection retry attempts over TCP. We
|
||||||
|
// don't need this for Unix sockets because the OS instantly knows the state of
|
||||||
|
// both ends of the socket connection. This basically causes the
|
||||||
|
// RemoteSigner.dialer() call inside RemoteSigner.connect() to return
|
||||||
|
// successfully immediately, putting an instant stop to any retry attempts.
|
||||||
|
func TestRemoteSignerRetryTCPOnly(t *testing.T) {
|
||||||
|
var (
|
||||||
|
attemptc = make(chan int)
|
||||||
|
retries = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
go func(ln net.Listener, attemptc chan<- int) {
|
||||||
|
attempts := 0
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := ln.Accept()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = conn.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
attempts++
|
||||||
|
|
||||||
|
if attempts == retries {
|
||||||
|
attemptc <- attempts
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(ln, attemptc)
|
||||||
|
|
||||||
|
rs := NewRemoteSigner(
|
||||||
|
log.TestingLogger(),
|
||||||
|
cmn.RandStr(12),
|
||||||
|
types.NewMockPV(),
|
||||||
|
DialTCPFn(ln.Addr().String(), testConnDeadline, ed25519.GenPrivKey()),
|
||||||
|
)
|
||||||
|
defer rs.Stop()
|
||||||
|
|
||||||
|
RemoteSignerConnDeadline(time.Millisecond)(rs)
|
||||||
|
RemoteSignerConnRetries(retries)(rs)
|
||||||
|
|
||||||
|
assert.Equal(t, rs.Start(), ErrDialRetryMax)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case attempts := <-attemptc:
|
||||||
|
assert.Equal(t, retries, attempts)
|
||||||
|
case <-time.After(100 * time.Millisecond):
|
||||||
|
t.Error("expected remote to observe connection attempts")
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||||
cmn "github.com/tendermint/tendermint/libs/common"
|
cmn "github.com/tendermint/tendermint/libs/common"
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
@ -12,6 +13,11 @@ import (
|
|||||||
"github.com/tendermint/tendermint/types"
|
"github.com/tendermint/tendermint/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Socket errors.
|
||||||
|
var (
|
||||||
|
ErrDialRetryMax = errors.New("dialed maximum retries")
|
||||||
|
)
|
||||||
|
|
||||||
// RemoteSignerOption sets an optional parameter on the RemoteSigner.
|
// RemoteSignerOption sets an optional parameter on the RemoteSigner.
|
||||||
type RemoteSignerOption func(*RemoteSigner)
|
type RemoteSignerOption func(*RemoteSigner)
|
||||||
|
|
||||||
@ -26,38 +32,64 @@ func RemoteSignerConnRetries(retries int) RemoteSignerOption {
|
|||||||
return func(ss *RemoteSigner) { ss.connRetries = retries }
|
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 {
|
type RemoteSigner struct {
|
||||||
cmn.BaseService
|
cmn.BaseService
|
||||||
|
|
||||||
addr string
|
|
||||||
chainID string
|
chainID string
|
||||||
connDeadline time.Duration
|
connDeadline time.Duration
|
||||||
connRetries int
|
connRetries int
|
||||||
privKey ed25519.PrivKeyEd25519
|
|
||||||
privVal types.PrivValidator
|
privVal types.PrivValidator
|
||||||
|
|
||||||
|
dialer Dialer
|
||||||
conn net.Conn
|
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(
|
func NewRemoteSigner(
|
||||||
logger log.Logger,
|
logger log.Logger,
|
||||||
chainID, socketAddr string,
|
chainID string,
|
||||||
privVal types.PrivValidator,
|
privVal types.PrivValidator,
|
||||||
privKey ed25519.PrivKeyEd25519,
|
dialer Dialer,
|
||||||
) *RemoteSigner {
|
) *RemoteSigner {
|
||||||
rs := &RemoteSigner{
|
rs := &RemoteSigner{
|
||||||
addr: socketAddr,
|
|
||||||
chainID: chainID,
|
chainID: chainID,
|
||||||
connDeadline: time.Second * defaultConnDeadlineSeconds,
|
connDeadline: time.Second * defaultConnDeadlineSeconds,
|
||||||
connRetries: defaultDialRetries,
|
connRetries: defaultDialRetries,
|
||||||
privKey: privKey,
|
|
||||||
privVal: privVal,
|
privVal: privVal,
|
||||||
|
dialer: dialer,
|
||||||
}
|
}
|
||||||
|
|
||||||
rs.BaseService = *cmn.NewBaseService(logger, "RemoteSigner", rs)
|
rs.BaseService = *cmn.NewBaseService(logger, "RemoteSigner", rs)
|
||||||
|
|
||||||
return rs
|
return rs
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,6 +100,7 @@ func (rs *RemoteSigner) OnStart() error {
|
|||||||
rs.Logger.Error("OnStart", "err", err)
|
rs.Logger.Error("OnStart", "err", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
rs.conn = conn
|
||||||
|
|
||||||
go rs.handleConnection(conn)
|
go rs.handleConnection(conn)
|
||||||
|
|
||||||
@ -91,36 +124,11 @@ func (rs *RemoteSigner) connect() (net.Conn, error) {
|
|||||||
if retries != rs.connRetries {
|
if retries != rs.connRetries {
|
||||||
time.Sleep(rs.connDeadline)
|
time.Sleep(rs.connDeadline)
|
||||||
}
|
}
|
||||||
|
conn, err := rs.dialer()
|
||||||
conn, err := cmn.Connect(rs.addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rs.Logger.Error(
|
rs.Logger.Error("dialing", "err", err)
|
||||||
"connect",
|
|
||||||
"addr", rs.addr,
|
|
||||||
"err", err,
|
|
||||||
)
|
|
||||||
|
|
||||||
continue
|
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
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +147,7 @@ func (rs *RemoteSigner) handleConnection(conn net.Conn) {
|
|||||||
req, err := readMsg(conn)
|
req, err := readMsg(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != io.EOF {
|
if err != io.EOF {
|
||||||
rs.Logger.Error("handleConnection", "err", err)
|
rs.Logger.Error("handleConnection readMsg", "err", err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -148,12 +156,12 @@ func (rs *RemoteSigner) handleConnection(conn net.Conn) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// only log the error; we'll reply with an error in res
|
// 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)
|
err = writeMsg(conn, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rs.Logger.Error("handleConnection", "err", err)
|
rs.Logger.Error("handleConnection writeMsg", "err", err)
|
||||||
return
|
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 timeoutConn.
|
||||||
|
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)
|
||||||
|
}
|
133
privval/socket_test.go
Normal file
133
privval/socket_test.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
package privval
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||||
|
)
|
||||||
|
|
||||||
|
//-------------------------------------------
|
||||||
|
// helper funcs
|
||||||
|
|
||||||
|
func newPrivKey() ed25519.PrivKeyEd25519 {
|
||||||
|
return ed25519.GenPrivKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------
|
||||||
|
// tests
|
||||||
|
|
||||||
|
type listenerTestCase struct {
|
||||||
|
description string // For test reporting purposes.
|
||||||
|
listener net.Listener
|
||||||
|
dialer Dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
// testUnixAddr will attempt to obtain a platform-independent temporary file
|
||||||
|
// name for a Unix socket
|
||||||
|
func testUnixAddr() (string, error) {
|
||||||
|
f, err := ioutil.TempFile("", "tendermint-privval-test-*")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
addr := f.Name()
|
||||||
|
f.Close()
|
||||||
|
os.Remove(addr)
|
||||||
|
return addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcpListenerTestCase(t *testing.T, acceptDeadline, connectDeadline time.Duration) listenerTestCase {
|
||||||
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tcpLn := NewTCPListener(ln, newPrivKey())
|
||||||
|
TCPListenerAcceptDeadline(acceptDeadline)(tcpLn)
|
||||||
|
TCPListenerConnDeadline(connectDeadline)(tcpLn)
|
||||||
|
return listenerTestCase{
|
||||||
|
description: "TCP",
|
||||||
|
listener: tcpLn,
|
||||||
|
dialer: DialTCPFn(ln.Addr().String(), testConnDeadline, newPrivKey()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unixListenerTestCase(t *testing.T, acceptDeadline, connectDeadline time.Duration) listenerTestCase {
|
||||||
|
addr, err := testUnixAddr()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ln, err := net.Listen("unix", addr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
unixLn := NewUnixListener(ln)
|
||||||
|
UnixListenerAcceptDeadline(acceptDeadline)(unixLn)
|
||||||
|
UnixListenerConnDeadline(connectDeadline)(unixLn)
|
||||||
|
return listenerTestCase{
|
||||||
|
description: "Unix",
|
||||||
|
listener: unixLn,
|
||||||
|
dialer: DialUnixFn(addr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func listenerTestCases(t *testing.T, acceptDeadline, connectDeadline time.Duration) []listenerTestCase {
|
||||||
|
return []listenerTestCase{
|
||||||
|
tcpListenerTestCase(t, acceptDeadline, connectDeadline),
|
||||||
|
unixListenerTestCase(t, acceptDeadline, connectDeadline),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListenerAcceptDeadlines(t *testing.T) {
|
||||||
|
for _, tc := range listenerTestCases(t, time.Millisecond, time.Second) {
|
||||||
|
_, err := tc.listener.Accept()
|
||||||
|
opErr, ok := err.(*net.OpError)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("for %s listener, have %v, want *net.OpError", tc.description, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if have, want := opErr.Op, "accept"; have != want {
|
||||||
|
t.Errorf("for %s listener, have %v, want %v", tc.description, have, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListenerConnectDeadlines(t *testing.T) {
|
||||||
|
for _, tc := range listenerTestCases(t, time.Second, time.Millisecond) {
|
||||||
|
readyc := make(chan struct{})
|
||||||
|
donec := make(chan struct{})
|
||||||
|
go func(ln net.Listener) {
|
||||||
|
defer close(donec)
|
||||||
|
|
||||||
|
c, err := ln.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
<-readyc
|
||||||
|
|
||||||
|
time.Sleep(2 * time.Millisecond)
|
||||||
|
|
||||||
|
msg := make([]byte, 200)
|
||||||
|
_, err = c.Read(msg)
|
||||||
|
opErr, ok := err.(*net.OpError)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("for %s listener, have %v, want *net.OpError", tc.description, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if have, want := opErr.Op, "read"; have != want {
|
||||||
|
t.Errorf("for %s listener, have %v, want %v", tc.description, have, want)
|
||||||
|
}
|
||||||
|
}(tc.listener)
|
||||||
|
|
||||||
|
_, err := tc.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)
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
package privval
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTCPTimeoutListenerAcceptDeadline(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)
|
|
||||||
|
|
||||||
_, err = ln.Accept()
|
|
||||||
opErr, ok := err.(*net.OpError)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("have %v, want *net.OpError", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if have, want := opErr.Op, "accept"; have != want {
|
|
||||||
t.Errorf("have %v, want %v", have, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTCPTimeoutListenerConnDeadline(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)
|
|
||||||
|
|
||||||
donec := make(chan struct{})
|
|
||||||
go func(ln net.Listener) {
|
|
||||||
defer close(donec)
|
|
||||||
|
|
||||||
c, err := ln.Accept()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(2 * time.Millisecond)
|
|
||||||
|
|
||||||
msg := make([]byte, 200)
|
|
||||||
_, err = c.Read(msg)
|
|
||||||
opErr, ok := err.(*net.OpError)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("have %v, want *net.OpError", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if have, want := opErr.Op, "read"; have != want {
|
|
||||||
t.Errorf("have %v, want %v", have, want)
|
|
||||||
}
|
|
||||||
}(ln)
|
|
||||||
|
|
||||||
_, err = net.Dial("tcp", ln.Addr().String())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
<-donec
|
|
||||||
}
|
|
@ -1,407 +0,0 @@
|
|||||||
package privval
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"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"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSocketPVAddress(t *testing.T) {
|
|
||||||
var (
|
|
||||||
chainID = cmn.RandStr(12)
|
|
||||||
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV())
|
|
||||||
)
|
|
||||||
defer sc.Stop()
|
|
||||||
defer rs.Stop()
|
|
||||||
|
|
||||||
serverAddr := rs.privVal.GetAddress()
|
|
||||||
|
|
||||||
clientAddr := sc.GetAddress()
|
|
||||||
|
|
||||||
assert.Equal(t, serverAddr, clientAddr)
|
|
||||||
|
|
||||||
// TODO(xla): Remove when PrivValidator2 replaced PrivValidator.
|
|
||||||
assert.Equal(t, serverAddr, sc.GetAddress())
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSocketPVPubKey(t *testing.T) {
|
|
||||||
var (
|
|
||||||
chainID = cmn.RandStr(12)
|
|
||||||
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV())
|
|
||||||
)
|
|
||||||
defer sc.Stop()
|
|
||||||
defer rs.Stop()
|
|
||||||
|
|
||||||
clientKey, err := sc.getPubKey()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
privKey := rs.privVal.GetPubKey()
|
|
||||||
|
|
||||||
assert.Equal(t, privKey, clientKey)
|
|
||||||
|
|
||||||
// TODO(xla): Remove when PrivValidator2 replaced PrivValidator.
|
|
||||||
assert.Equal(t, privKey, sc.GetPubKey())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSocketPVProposal(t *testing.T) {
|
|
||||||
var (
|
|
||||||
chainID = cmn.RandStr(12)
|
|
||||||
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV())
|
|
||||||
|
|
||||||
ts = time.Now()
|
|
||||||
privProposal = &types.Proposal{Timestamp: ts}
|
|
||||||
clientProposal = &types.Proposal{Timestamp: ts}
|
|
||||||
)
|
|
||||||
defer sc.Stop()
|
|
||||||
defer rs.Stop()
|
|
||||||
|
|
||||||
require.NoError(t, rs.privVal.SignProposal(chainID, privProposal))
|
|
||||||
require.NoError(t, sc.SignProposal(chainID, clientProposal))
|
|
||||||
assert.Equal(t, privProposal.Signature, clientProposal.Signature)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSocketPVVote(t *testing.T) {
|
|
||||||
var (
|
|
||||||
chainID = cmn.RandStr(12)
|
|
||||||
sc, rs = testSetupSocketPair(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 TestSocketPVVoteResetDeadline(t *testing.T) {
|
|
||||||
var (
|
|
||||||
chainID = cmn.RandStr(12)
|
|
||||||
sc, rs = testSetupSocketPair(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 TestSocketPVVoteKeepalive(t *testing.T) {
|
|
||||||
var (
|
|
||||||
chainID = cmn.RandStr(12)
|
|
||||||
sc, rs = testSetupSocketPair(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 TestSocketPVDeadline(t *testing.T) {
|
|
||||||
var (
|
|
||||||
addr = testFreeAddr(t)
|
|
||||||
listenc = make(chan struct{})
|
|
||||||
sc = NewTCPVal(
|
|
||||||
log.TestingLogger(),
|
|
||||||
addr,
|
|
||||||
ed25519.GenPrivKey(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
TCPValConnTimeout(100 * time.Millisecond)(sc)
|
|
||||||
|
|
||||||
go func(sc *TCPVal) {
|
|
||||||
defer close(listenc)
|
|
||||||
|
|
||||||
require.NoError(t, sc.Start())
|
|
||||||
|
|
||||||
assert.True(t, sc.IsRunning())
|
|
||||||
}(sc)
|
|
||||||
|
|
||||||
for {
|
|
||||||
conn, err := cmn.Connect(addr)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = p2pconn.MakeSecretConnection(
|
|
||||||
conn,
|
|
||||||
ed25519.GenPrivKey(),
|
|
||||||
)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<-listenc
|
|
||||||
|
|
||||||
_, err := sc.getPubKey()
|
|
||||||
assert.Equal(t, err.(cmn.Error).Data(), ErrConnTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRemoteSignerRetry(t *testing.T) {
|
|
||||||
var (
|
|
||||||
attemptc = make(chan int)
|
|
||||||
retries = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
go func(ln net.Listener, attemptc chan<- int) {
|
|
||||||
attempts := 0
|
|
||||||
|
|
||||||
for {
|
|
||||||
conn, err := ln.Accept()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = conn.Close()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
attempts++
|
|
||||||
|
|
||||||
if attempts == retries {
|
|
||||||
attemptc <- attempts
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(ln, attemptc)
|
|
||||||
|
|
||||||
rs := NewRemoteSigner(
|
|
||||||
log.TestingLogger(),
|
|
||||||
cmn.RandStr(12),
|
|
||||||
ln.Addr().String(),
|
|
||||||
types.NewMockPV(),
|
|
||||||
ed25519.GenPrivKey(),
|
|
||||||
)
|
|
||||||
defer rs.Stop()
|
|
||||||
|
|
||||||
RemoteSignerConnDeadline(time.Millisecond)(rs)
|
|
||||||
RemoteSignerConnRetries(retries)(rs)
|
|
||||||
|
|
||||||
assert.Equal(t, rs.Start(), ErrDialRetryMax)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case attempts := <-attemptc:
|
|
||||||
assert.Equal(t, retries, attempts)
|
|
||||||
case <-time.After(100 * time.Millisecond):
|
|
||||||
t.Error("expected remote to observe connection attempts")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRemoteSignVoteErrors(t *testing.T) {
|
|
||||||
var (
|
|
||||||
chainID = cmn.RandStr(12)
|
|
||||||
sc, rs = testSetupSocketPair(t, chainID, types.NewErroringMockPV())
|
|
||||||
|
|
||||||
ts = time.Now()
|
|
||||||
vType = types.PrecommitType
|
|
||||||
vote = &types.Vote{Timestamp: ts, Type: vType}
|
|
||||||
)
|
|
||||||
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 = rs.privVal.SignVote(chainID, vote)
|
|
||||||
require.Error(t, err)
|
|
||||||
err = sc.SignVote(chainID, vote)
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRemoteSignProposalErrors(t *testing.T) {
|
|
||||||
var (
|
|
||||||
chainID = cmn.RandStr(12)
|
|
||||||
sc, rs = testSetupSocketPair(t, chainID, types.NewErroringMockPV())
|
|
||||||
|
|
||||||
ts = time.Now()
|
|
||||||
proposal = &types.Proposal{Timestamp: ts}
|
|
||||||
)
|
|
||||||
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 = rs.privVal.SignProposal(chainID, proposal)
|
|
||||||
require.Error(t, err)
|
|
||||||
|
|
||||||
err = sc.SignProposal(chainID, proposal)
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestErrUnexpectedResponse(t *testing.T) {
|
|
||||||
var (
|
|
||||||
addr = testFreeAddr(t)
|
|
||||||
logger = log.TestingLogger()
|
|
||||||
chainID = cmn.RandStr(12)
|
|
||||||
readyc = make(chan struct{})
|
|
||||||
errc = make(chan error, 1)
|
|
||||||
|
|
||||||
rs = NewRemoteSigner(
|
|
||||||
logger,
|
|
||||||
chainID,
|
|
||||||
addr,
|
|
||||||
types.NewMockPV(),
|
|
||||||
ed25519.GenPrivKey(),
|
|
||||||
)
|
|
||||||
sc = NewTCPVal(
|
|
||||||
logger,
|
|
||||||
addr,
|
|
||||||
ed25519.GenPrivKey(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
testStartSocketPV(t, readyc, sc)
|
|
||||||
defer sc.Stop()
|
|
||||||
RemoteSignerConnDeadline(time.Millisecond)(rs)
|
|
||||||
RemoteSignerConnRetries(1e6)(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)
|
|
||||||
<-readyc
|
|
||||||
|
|
||||||
// Proposal:
|
|
||||||
go func(errc chan error) {
|
|
||||||
errc <- sc.SignProposal(chainID, &types.Proposal{})
|
|
||||||
}(errc)
|
|
||||||
// read request and write wrong response:
|
|
||||||
go testReadWriteResponse(t, &SignedVoteResponse{}, rsConn)
|
|
||||||
err = <-errc
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Equal(t, err, ErrUnexpectedResponse)
|
|
||||||
|
|
||||||
// Vote:
|
|
||||||
go func(errc chan error) {
|
|
||||||
errc <- sc.SignVote(chainID, &types.Vote{})
|
|
||||||
}(errc)
|
|
||||||
// read request and write wrong response:
|
|
||||||
go testReadWriteResponse(t, &SignedProposalResponse{}, rsConn)
|
|
||||||
err = <-errc
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Equal(t, err, ErrUnexpectedResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSetupSocketPair(
|
|
||||||
t *testing.T,
|
|
||||||
chainID string,
|
|
||||||
privValidator types.PrivValidator,
|
|
||||||
) (*TCPVal, *RemoteSigner) {
|
|
||||||
var (
|
|
||||||
addr = testFreeAddr(t)
|
|
||||||
logger = log.TestingLogger()
|
|
||||||
privVal = privValidator
|
|
||||||
readyc = make(chan struct{})
|
|
||||||
rs = NewRemoteSigner(
|
|
||||||
logger,
|
|
||||||
chainID,
|
|
||||||
addr,
|
|
||||||
privVal,
|
|
||||||
ed25519.GenPrivKey(),
|
|
||||||
)
|
|
||||||
sc = NewTCPVal(
|
|
||||||
logger,
|
|
||||||
addr,
|
|
||||||
ed25519.GenPrivKey(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
TCPValConnTimeout(5 * time.Millisecond)(sc)
|
|
||||||
TCPValHeartbeat(2 * time.Millisecond)(sc)
|
|
||||||
RemoteSignerConnDeadline(5 * time.Millisecond)(rs)
|
|
||||||
RemoteSignerConnRetries(1e6)(rs)
|
|
||||||
|
|
||||||
testStartSocketPV(t, readyc, sc)
|
|
||||||
|
|
||||||
require.NoError(t, rs.Start())
|
|
||||||
assert.True(t, rs.IsRunning())
|
|
||||||
|
|
||||||
<-readyc
|
|
||||||
|
|
||||||
return sc, rs
|
|
||||||
}
|
|
||||||
|
|
||||||
func testReadWriteResponse(t *testing.T, resp RemoteSignerMsg, rsConn net.Conn) {
|
|
||||||
_, err := readMsg(rsConn)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = writeMsg(rsConn, resp)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testStartSocketPV(t *testing.T, readyc chan struct{}, sc *TCPVal) {
|
|
||||||
go func(sc *TCPVal) {
|
|
||||||
require.NoError(t, sc.Start())
|
|
||||||
assert.True(t, sc.IsRunning())
|
|
||||||
|
|
||||||
readyc <- struct{}{}
|
|
||||||
}(sc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// testFreeAddr claims a free port so we don't block on listener being ready.
|
|
||||||
func testFreeAddr(t *testing.T) string {
|
|
||||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer ln.Close()
|
|
||||||
|
|
||||||
return fmt.Sprintf("127.0.0.1:%d", ln.Addr().(*net.TCPAddr).Port)
|
|
||||||
}
|
|
@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
abcicli "github.com/tendermint/tendermint/abci/client"
|
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/example/kvstore"
|
||||||
"github.com/tendermint/tendermint/abci/types"
|
"github.com/tendermint/tendermint/abci/types"
|
||||||
)
|
)
|
||||||
@ -64,15 +65,15 @@ func (r *remoteClientCreator) NewABCIClient() (abcicli.Client, error) {
|
|||||||
|
|
||||||
func DefaultClientCreator(addr, transport, dbDir string) ClientCreator {
|
func DefaultClientCreator(addr, transport, dbDir string) ClientCreator {
|
||||||
switch addr {
|
switch addr {
|
||||||
|
case "counter":
|
||||||
|
return NewLocalClientCreator(counter.NewCounterApplication(false))
|
||||||
|
case "counter_serial":
|
||||||
|
return NewLocalClientCreator(counter.NewCounterApplication(true))
|
||||||
case "kvstore":
|
case "kvstore":
|
||||||
fallthrough
|
|
||||||
case "dummy":
|
|
||||||
return NewLocalClientCreator(kvstore.NewKVStoreApplication())
|
return NewLocalClientCreator(kvstore.NewKVStoreApplication())
|
||||||
case "persistent_kvstore":
|
case "persistent_kvstore":
|
||||||
fallthrough
|
|
||||||
case "persistent_dummy":
|
|
||||||
return NewLocalClientCreator(kvstore.NewPersistentKVStoreApplication(dbDir))
|
return NewLocalClientCreator(kvstore.NewPersistentKVStoreApplication(dbDir))
|
||||||
case "nilapp":
|
case "noop":
|
||||||
return NewLocalClientCreator(types.NewBaseApplication())
|
return NewLocalClientCreator(types.NewBaseApplication())
|
||||||
default:
|
default:
|
||||||
mustConnect := false // loop retrying
|
mustConnect := false // loop retrying
|
||||||
|
@ -428,5 +428,10 @@ func TestTxSearch(t *testing.T) {
|
|||||||
if len(result.Txs) == 0 {
|
if len(result.Txs) == 0 {
|
||||||
t.Fatal("expected a lot of transactions")
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,7 @@ func NetInfo() (*ctypes.ResultNetInfo, error) {
|
|||||||
NodeInfo: nodeInfo,
|
NodeInfo: nodeInfo,
|
||||||
IsOutbound: peer.IsOutbound(),
|
IsOutbound: peer.IsOutbound(),
|
||||||
ConnectionStatus: peer.Status(),
|
ConnectionStatus: peer.Status(),
|
||||||
|
RemoteIP: peer.RemoteIP(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// TODO: Should we include PersistentPeers and Seeds in here?
|
// TODO: Should we include PersistentPeers and Seeds in here?
|
||||||
|
@ -154,3 +154,12 @@ func validatePerPage(perPage int) int {
|
|||||||
}
|
}
|
||||||
return perPage
|
return perPage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateSkipCount(page, perPage int) int {
|
||||||
|
skipCount := (page - 1) * perPage
|
||||||
|
if skipCount < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return skipCount
|
||||||
|
}
|
||||||
|
@ -201,10 +201,11 @@ func TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSear
|
|||||||
totalCount := len(results)
|
totalCount := len(results)
|
||||||
perPage = validatePerPage(perPage)
|
perPage = validatePerPage(perPage)
|
||||||
page = validatePage(page, perPage, totalCount)
|
page = validatePage(page, perPage, totalCount)
|
||||||
skipCount := (page - 1) * perPage
|
skipCount := validateSkipCount(page, perPage)
|
||||||
|
|
||||||
apiResults := make([]*ctypes.ResultTx, cmn.MinInt(perPage, totalCount-skipCount))
|
apiResults := make([]*ctypes.ResultTx, cmn.MinInt(perPage, totalCount-skipCount))
|
||||||
var proof types.TxProof
|
var proof types.TxProof
|
||||||
|
// if there's no tx in the results array, we don't need to loop through the apiResults array
|
||||||
for i := 0; i < len(apiResults); i++ {
|
for i := 0; i < len(apiResults); i++ {
|
||||||
r := results[skipCount+i]
|
r := results[skipCount+i]
|
||||||
height := r.Height
|
height := r.Height
|
||||||
|
@ -2,6 +2,7 @@ package core_types
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
@ -110,6 +111,7 @@ type Peer struct {
|
|||||||
NodeInfo p2p.DefaultNodeInfo `json:"node_info"`
|
NodeInfo p2p.DefaultNodeInfo `json:"node_info"`
|
||||||
IsOutbound bool `json:"is_outbound"`
|
IsOutbound bool `json:"is_outbound"`
|
||||||
ConnectionStatus p2p.ConnectionStatus `json:"connection_status"`
|
ConnectionStatus p2p.ConnectionStatus `json:"connection_status"`
|
||||||
|
RemoteIP net.IP `json:"remote_ip"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validators for a height
|
// Validators for a height
|
||||||
|
@ -119,8 +119,9 @@ func NewTendermint(app abci.Application) *nm.Node {
|
|||||||
config := GetConfig()
|
config := GetConfig()
|
||||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||||
logger = log.NewFilter(logger, log.AllowError())
|
logger = log.NewFilter(logger, log.AllowError())
|
||||||
pvFile := config.PrivValidatorFile()
|
pvKeyFile := config.PrivValidatorKeyFile()
|
||||||
pv := privval.LoadOrGenFilePV(pvFile)
|
pvKeyStateFile := config.PrivValidatorStateFile()
|
||||||
|
pv := privval.LoadOrGenFilePV(pvKeyFile, pvKeyStateFile)
|
||||||
papp := proxy.NewLocalClientCreator(app)
|
papp := proxy.NewLocalClientCreator(app)
|
||||||
nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
|
nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -6,7 +6,7 @@ set -e
|
|||||||
|
|
||||||
# Get the version from the environment, or try to figure it out.
|
# Get the version from the environment, or try to figure it out.
|
||||||
if [ -z $VERSION ]; then
|
if [ -z $VERSION ]; then
|
||||||
VERSION=$(awk -F\" '/Version =/ { print $2; exit }' < version/version.go)
|
VERSION=$(awk -F\" 'TMCoreSemVer =/ { print $2; exit }' < version/version.go)
|
||||||
fi
|
fi
|
||||||
if [ -z "$VERSION" ]; then
|
if [ -z "$VERSION" ]; then
|
||||||
echo "Please specify a version."
|
echo "Please specify a version."
|
||||||
|
@ -1,19 +1,11 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# XXX: this script is intended to be run from
|
|
||||||
# a fresh Digital Ocean droplet with Ubuntu
|
|
||||||
|
|
||||||
# upon its completion, you must either reset
|
|
||||||
# your terminal or run `source ~/.profile`
|
|
||||||
|
|
||||||
# as written, this script will install
|
|
||||||
# tendermint core from master branch
|
|
||||||
REPO=github.com/tendermint/tendermint
|
REPO=github.com/tendermint/tendermint
|
||||||
|
|
||||||
# change this to a specific release or branch
|
# change this to a specific release or branch
|
||||||
BRANCH=master
|
BRANCH=master
|
||||||
|
|
||||||
GO_VERSION=1.11.2
|
GO_VERSION=1.11.4
|
||||||
|
|
||||||
sudo apt-get update -y
|
sudo apt-get update -y
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
set BRANCH=master
|
set BRANCH=master
|
||||||
set REPO=github.com/tendermint/tendermint
|
set REPO=github.com/tendermint/tendermint
|
||||||
|
|
||||||
set GO_VERSION=1.11.2
|
set GO_VERSION=1.11.4
|
||||||
|
|
||||||
sudo pkg update
|
sudo pkg update
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ REPO=github.com/tendermint/tendermint
|
|||||||
# change this to a specific release or branch
|
# change this to a specific release or branch
|
||||||
BRANCH=master
|
BRANCH=master
|
||||||
|
|
||||||
GO_VERSION=1.11.2
|
GO_VERSION=1.11.4
|
||||||
|
|
||||||
sudo apt-get update -y
|
sudo apt-get update -y
|
||||||
sudo apt-get install -y make
|
sudo apt-get install -y make
|
||||||
|
41
scripts/privValUpgrade.go
Normal file
41
scripts/privValUpgrade.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
"github.com/tendermint/tendermint/privval"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
args := os.Args[1:]
|
||||||
|
if len(args) != 3 {
|
||||||
|
fmt.Println("Expected three args: <old path> <new key path> <new state path>")
|
||||||
|
fmt.Println("Eg. ~/.tendermint/config/priv_validator.json ~/.tendermint/config/priv_validator_key.json ~/.tendermint/data/priv_validator_state.json")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
err := loadAndUpgrade(args[0], args[1], args[2])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadAndUpgrade(oldPVPath, newPVKeyPath, newPVStatePath string) error {
|
||||||
|
oldPV, err := privval.LoadOldFilePV(oldPVPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error reading OldPrivValidator from %v: %v\n", oldPVPath, err)
|
||||||
|
}
|
||||||
|
logger.Info("Upgrading PrivValidator file",
|
||||||
|
"old", oldPVPath,
|
||||||
|
"newKey", newPVKeyPath,
|
||||||
|
"newState", newPVStatePath,
|
||||||
|
)
|
||||||
|
oldPV.Upgrade(newPVKeyPath, newPVStatePath)
|
||||||
|
return nil
|
||||||
|
}
|
121
scripts/privValUpgrade_test.go
Normal file
121
scripts/privValUpgrade_test.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"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)
|
||||||
|
emptyOldFile, err := ioutil.TempFile("", "priv_validator_empty*.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer os.Remove(emptyOldFile.Name())
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
oldPVPath string
|
||||||
|
newPVKeyPath string
|
||||||
|
newPVStatePath string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
wantPanic bool
|
||||||
|
}{
|
||||||
|
{"successful upgrade",
|
||||||
|
args{oldPVPath: oldFilePath, newPVKeyPath: newKeyFile.Name(), newPVStatePath: newStateFile.Name()},
|
||||||
|
false, false,
|
||||||
|
},
|
||||||
|
{"unsuccessful upgrade: empty old privval file",
|
||||||
|
args{oldPVPath: emptyOldFile.Name(), newPVKeyPath: newKeyFile.Name(), newPVStatePath: newStateFile.Name()},
|
||||||
|
true, false,
|
||||||
|
},
|
||||||
|
{"unsuccessful upgrade: invalid new paths (1/3)",
|
||||||
|
args{oldPVPath: oldFilePath, newPVKeyPath: "", newPVStatePath: newStateFile.Name()},
|
||||||
|
false, true,
|
||||||
|
},
|
||||||
|
{"unsuccessful upgrade: invalid new paths (2/3)",
|
||||||
|
args{oldPVPath: oldFilePath, newPVKeyPath: newKeyFile.Name(), newPVStatePath: ""},
|
||||||
|
false, true,
|
||||||
|
},
|
||||||
|
{"unsuccessful upgrade: invalid new paths (3/3)",
|
||||||
|
args{oldPVPath: oldFilePath, newPVKeyPath: "", newPVStatePath: ""},
|
||||||
|
false, true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// need to re-write the file everytime because upgrading renames it
|
||||||
|
err := ioutil.WriteFile(oldFilePath, []byte(oldPrivvalContent), 0600)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if tt.wantPanic {
|
||||||
|
require.Panics(t, func() { loadAndUpgrade(tt.args.oldPVPath, tt.args.newPVKeyPath, tt.args.newPVStatePath) })
|
||||||
|
} else {
|
||||||
|
err = loadAndUpgrade(tt.args.oldPVPath, tt.args.newPVKeyPath, tt.args.newPVStatePath)
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
fmt.Println("ERR", err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
upgradedPV := privval.LoadFilePV(tt.args.newPVKeyPath, tt.args.newPVStatePath)
|
||||||
|
oldPV, err := privval.LoadOldFilePV(tt.args.oldPVPath + ".bak")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, oldPV.Address, upgradedPV.Key.Address)
|
||||||
|
assert.Equal(t, oldPV.Address, upgradedPV.GetAddress())
|
||||||
|
assert.Equal(t, oldPV.PubKey, upgradedPV.Key.PubKey)
|
||||||
|
assert.Equal(t, oldPV.PubKey, upgradedPV.GetPubKey())
|
||||||
|
assert.Equal(t, oldPV.PrivKey, upgradedPV.Key.PrivKey)
|
||||||
|
|
||||||
|
assert.Equal(t, oldPV.LastHeight, upgradedPV.LastSignState.Height)
|
||||||
|
assert.Equal(t, oldPV.LastRound, upgradedPV.LastSignState.Round)
|
||||||
|
assert.Equal(t, oldPV.LastSignature, upgradedPV.LastSignState.Signature)
|
||||||
|
assert.Equal(t, oldPV.LastSignBytes, upgradedPV.LastSignState.SignBytes)
|
||||||
|
assert.Equal(t, oldPV.LastStep, upgradedPV.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()
|
||||||
|
}
|
@ -6,7 +6,7 @@ DIST_DIR=./build/dist
|
|||||||
|
|
||||||
# Get the version from the environment, or try to figure it out.
|
# Get the version from the environment, or try to figure it out.
|
||||||
if [ -z $VERSION ]; then
|
if [ -z $VERSION ]; then
|
||||||
VERSION=$(awk -F\" '/Version =/ { print $2; exit }' < version/version.go)
|
VERSION=$(awk -F\" 'TMCoreSemVer =/ { print $2; exit }' < version/version.go)
|
||||||
fi
|
fi
|
||||||
if [ -z "$VERSION" ]; then
|
if [ -z "$VERSION" ]; then
|
||||||
echo "Please specify a version."
|
echo "Please specify a version."
|
||||||
|
@ -3,7 +3,7 @@ set -e
|
|||||||
|
|
||||||
# Get the version from the environment, or try to figure it out.
|
# Get the version from the environment, or try to figure it out.
|
||||||
if [ -z $VERSION ]; then
|
if [ -z $VERSION ]; then
|
||||||
VERSION=$(awk -F\" '/Version =/ { print $2; exit }' < version/version.go)
|
VERSION=$(awk -F\" 'TMCoreSemVer =/ { print $2; exit }' < version/version.go)
|
||||||
fi
|
fi
|
||||||
if [ -z "$VERSION" ]; then
|
if [ -z "$VERSION" ]; then
|
||||||
echo "Please specify a version."
|
echo "Please specify a version."
|
||||||
|
@ -1,182 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/tendermint/go-amino"
|
|
||||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
|
||||||
cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino"
|
|
||||||
|
|
||||||
cmn "github.com/tendermint/tendermint/libs/common"
|
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/p2p"
|
|
||||||
"github.com/tendermint/tendermint/privval"
|
|
||||||
"github.com/tendermint/tendermint/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GenesisValidator struct {
|
|
||||||
PubKey Data `json:"pub_key"`
|
|
||||||
Power int64 `json:"power"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Genesis struct {
|
|
||||||
GenesisTime time.Time `json:"genesis_time"`
|
|
||||||
ChainID string `json:"chain_id"`
|
|
||||||
ConsensusParams *types.ConsensusParams `json:"consensus_params,omitempty"`
|
|
||||||
Validators []GenesisValidator `json:"validators"`
|
|
||||||
AppHash cmn.HexBytes `json:"app_hash"`
|
|
||||||
AppState json.RawMessage `json:"app_state,omitempty"`
|
|
||||||
AppOptions json.RawMessage `json:"app_options,omitempty"` // DEPRECATED
|
|
||||||
}
|
|
||||||
|
|
||||||
type NodeKey struct {
|
|
||||||
PrivKey Data `json:"priv_key"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PrivVal struct {
|
|
||||||
Address cmn.HexBytes `json:"address"`
|
|
||||||
LastHeight int64 `json:"last_height"`
|
|
||||||
LastRound int `json:"last_round"`
|
|
||||||
LastStep int8 `json:"last_step"`
|
|
||||||
PubKey Data `json:"pub_key"`
|
|
||||||
PrivKey Data `json:"priv_key"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Data struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Data cmn.HexBytes `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertNodeKey(cdc *amino.Codec, jsonBytes []byte) ([]byte, error) {
|
|
||||||
var nodeKey NodeKey
|
|
||||||
err := json.Unmarshal(jsonBytes, &nodeKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var privKey ed25519.PrivKeyEd25519
|
|
||||||
copy(privKey[:], nodeKey.PrivKey.Data)
|
|
||||||
|
|
||||||
nodeKeyNew := p2p.NodeKey{privKey}
|
|
||||||
|
|
||||||
bz, err := cdc.MarshalJSON(nodeKeyNew)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return bz, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertPrivVal(cdc *amino.Codec, jsonBytes []byte) ([]byte, error) {
|
|
||||||
var privVal PrivVal
|
|
||||||
err := json.Unmarshal(jsonBytes, &privVal)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var privKey ed25519.PrivKeyEd25519
|
|
||||||
copy(privKey[:], privVal.PrivKey.Data)
|
|
||||||
|
|
||||||
var pubKey ed25519.PubKeyEd25519
|
|
||||||
copy(pubKey[:], privVal.PubKey.Data)
|
|
||||||
|
|
||||||
privValNew := privval.FilePV{
|
|
||||||
Address: pubKey.Address(),
|
|
||||||
PubKey: pubKey,
|
|
||||||
LastHeight: privVal.LastHeight,
|
|
||||||
LastRound: privVal.LastRound,
|
|
||||||
LastStep: privVal.LastStep,
|
|
||||||
PrivKey: privKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
bz, err := cdc.MarshalJSON(privValNew)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return bz, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertGenesis(cdc *amino.Codec, jsonBytes []byte) ([]byte, error) {
|
|
||||||
var genesis Genesis
|
|
||||||
err := json.Unmarshal(jsonBytes, &genesis)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
genesisNew := types.GenesisDoc{
|
|
||||||
GenesisTime: genesis.GenesisTime,
|
|
||||||
ChainID: genesis.ChainID,
|
|
||||||
ConsensusParams: genesis.ConsensusParams,
|
|
||||||
// Validators
|
|
||||||
AppHash: genesis.AppHash,
|
|
||||||
AppState: genesis.AppState,
|
|
||||||
}
|
|
||||||
|
|
||||||
if genesis.AppOptions != nil {
|
|
||||||
genesisNew.AppState = genesis.AppOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range genesis.Validators {
|
|
||||||
var pubKey ed25519.PubKeyEd25519
|
|
||||||
copy(pubKey[:], v.PubKey.Data)
|
|
||||||
genesisNew.Validators = append(
|
|
||||||
genesisNew.Validators,
|
|
||||||
types.GenesisValidator{
|
|
||||||
PubKey: pubKey,
|
|
||||||
Power: v.Power,
|
|
||||||
Name: v.Name,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bz, err := cdc.MarshalJSON(genesisNew)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return bz, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cdc := amino.NewCodec()
|
|
||||||
cryptoAmino.RegisterAmino(cdc)
|
|
||||||
|
|
||||||
args := os.Args[1:]
|
|
||||||
if len(args) != 1 {
|
|
||||||
fmt.Println("Please specify a file to convert")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
filePath := args[0]
|
|
||||||
fileName := filepath.Base(filePath)
|
|
||||||
|
|
||||||
fileBytes, err := ioutil.ReadFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var bz []byte
|
|
||||||
|
|
||||||
switch fileName {
|
|
||||||
case "node_key.json":
|
|
||||||
bz, err = convertNodeKey(cdc, fileBytes)
|
|
||||||
case "priv_validator.json":
|
|
||||||
bz, err = convertPrivVal(cdc, fileBytes)
|
|
||||||
case "genesis.json":
|
|
||||||
bz, err = convertGenesis(cdc, fileBytes)
|
|
||||||
default:
|
|
||||||
fmt.Println("Expected file name to be in (node_key.json, priv_validator.json, genesis.json)")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fmt.Println(string(bz))
|
|
||||||
|
|
||||||
}
|
|
@ -17,8 +17,9 @@ type voteData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func makeVote(val PrivValidator, chainID string, valIndex int, height int64, round, step int, blockID BlockID) *Vote {
|
func makeVote(val PrivValidator, chainID string, valIndex int, height int64, round, step int, blockID BlockID) *Vote {
|
||||||
|
addr := val.GetPubKey().Address()
|
||||||
v := &Vote{
|
v := &Vote{
|
||||||
ValidatorAddress: val.GetAddress(),
|
ValidatorAddress: addr,
|
||||||
ValidatorIndex: valIndex,
|
ValidatorIndex: valIndex,
|
||||||
Height: height,
|
Height: height,
|
||||||
Round: round,
|
Round: round,
|
||||||
|
@ -34,7 +34,7 @@ type EvidenceParams struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidatorParams restrict the public key types validators can use.
|
// ValidatorParams restrict the public key types validators can use.
|
||||||
// NOTE: uses ABCI pubkey naming, not Amino routes.
|
// NOTE: uses ABCI pubkey naming, not Amino names.
|
||||||
type ValidatorParams struct {
|
type ValidatorParams struct {
|
||||||
PubKeyTypes []string `json:"pub_key_types"`
|
PubKeyTypes []string `json:"pub_key_types"`
|
||||||
}
|
}
|
||||||
@ -107,7 +107,7 @@ func (params *ConsensusParams) Validate() error {
|
|||||||
// Check if keyType is a known ABCIPubKeyType
|
// Check if keyType is a known ABCIPubKeyType
|
||||||
for i := 0; i < len(params.Validator.PubKeyTypes); i++ {
|
for i := 0; i < len(params.Validator.PubKeyTypes); i++ {
|
||||||
keyType := params.Validator.PubKeyTypes[i]
|
keyType := params.Validator.PubKeyTypes[i]
|
||||||
if _, ok := ABCIPubKeyTypesToAminoRoutes[keyType]; !ok {
|
if _, ok := ABCIPubKeyTypesToAminoNames[keyType]; !ok {
|
||||||
return cmn.NewError("params.Validator.PubKeyTypes[%d], %s, is an unknown pubkey type",
|
return cmn.NewError("params.Validator.PubKeyTypes[%d], %s, is an unknown pubkey type",
|
||||||
i, keyType)
|
i, keyType)
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import (
|
|||||||
// PrivValidator defines the functionality of a local Tendermint validator
|
// PrivValidator defines the functionality of a local Tendermint validator
|
||||||
// that signs votes and proposals, and never double signs.
|
// that signs votes and proposals, and never double signs.
|
||||||
type PrivValidator interface {
|
type PrivValidator interface {
|
||||||
GetAddress() Address // redundant since .PubKey().Address()
|
|
||||||
GetPubKey() crypto.PubKey
|
GetPubKey() crypto.PubKey
|
||||||
|
|
||||||
SignVote(chainID string, vote *Vote) error
|
SignVote(chainID string, vote *Vote) error
|
||||||
@ -29,7 +28,7 @@ func (pvs PrivValidatorsByAddress) Len() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pvs PrivValidatorsByAddress) Less(i, j int) bool {
|
func (pvs PrivValidatorsByAddress) Less(i, j int) bool {
|
||||||
return bytes.Compare(pvs[i].GetAddress(), pvs[j].GetAddress()) == -1
|
return bytes.Compare(pvs[i].GetPubKey().Address(), pvs[j].GetPubKey().Address()) == -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pvs PrivValidatorsByAddress) Swap(i, j int) {
|
func (pvs PrivValidatorsByAddress) Swap(i, j int) {
|
||||||
@ -51,11 +50,6 @@ func NewMockPV() *MockPV {
|
|||||||
return &MockPV{ed25519.GenPrivKey()}
|
return &MockPV{ed25519.GenPrivKey()}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements PrivValidator.
|
|
||||||
func (pv *MockPV) GetAddress() Address {
|
|
||||||
return pv.privKey.PubKey().Address()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implements PrivValidator.
|
// Implements PrivValidator.
|
||||||
func (pv *MockPV) GetPubKey() crypto.PubKey {
|
func (pv *MockPV) GetPubKey() crypto.PubKey {
|
||||||
return pv.privKey.PubKey()
|
return pv.privKey.PubKey()
|
||||||
@ -85,7 +79,8 @@ func (pv *MockPV) SignProposal(chainID string, proposal *Proposal) error {
|
|||||||
|
|
||||||
// String returns a string representation of the MockPV.
|
// String returns a string representation of the MockPV.
|
||||||
func (pv *MockPV) String() string {
|
func (pv *MockPV) String() string {
|
||||||
return fmt.Sprintf("MockPV{%v}", pv.GetAddress())
|
addr := pv.GetPubKey().Address()
|
||||||
|
return fmt.Sprintf("MockPV{%v}", addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: Implement.
|
// XXX: Implement.
|
||||||
|
@ -25,9 +25,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// TODO: Make non-global by allowing for registration of more pubkey types
|
// TODO: Make non-global by allowing for registration of more pubkey types
|
||||||
var ABCIPubKeyTypesToAminoRoutes = map[string]string{
|
var ABCIPubKeyTypesToAminoNames = map[string]string{
|
||||||
ABCIPubKeyTypeEd25519: ed25519.PubKeyAminoRoute,
|
ABCIPubKeyTypeEd25519: ed25519.PubKeyAminoName,
|
||||||
ABCIPubKeyTypeSecp256k1: secp256k1.PubKeyAminoRoute,
|
ABCIPubKeyTypeSecp256k1: secp256k1.PubKeyAminoName,
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------------------------------------------------------
|
//-------------------------------------------------------
|
||||||
|
@ -142,14 +142,15 @@ func TestABCIEvidence(t *testing.T) {
|
|||||||
blockID := makeBlockID([]byte("blockhash"), 1000, []byte("partshash"))
|
blockID := makeBlockID([]byte("blockhash"), 1000, []byte("partshash"))
|
||||||
blockID2 := makeBlockID([]byte("blockhash2"), 1000, []byte("partshash"))
|
blockID2 := makeBlockID([]byte("blockhash2"), 1000, []byte("partshash"))
|
||||||
const chainID = "mychain"
|
const chainID = "mychain"
|
||||||
|
pubKey := val.GetPubKey()
|
||||||
ev := &DuplicateVoteEvidence{
|
ev := &DuplicateVoteEvidence{
|
||||||
PubKey: val.GetPubKey(),
|
PubKey: pubKey,
|
||||||
VoteA: makeVote(val, chainID, 0, 10, 2, 1, blockID),
|
VoteA: makeVote(val, chainID, 0, 10, 2, 1, blockID),
|
||||||
VoteB: makeVote(val, chainID, 0, 10, 2, 1, blockID2),
|
VoteB: makeVote(val, chainID, 0, 10, 2, 1, blockID2),
|
||||||
}
|
}
|
||||||
abciEv := TM2PB.Evidence(
|
abciEv := TM2PB.Evidence(
|
||||||
ev,
|
ev,
|
||||||
NewValidatorSet([]*Validator{NewValidator(val.GetPubKey(), 10)}),
|
NewValidatorSet([]*Validator{NewValidator(pubKey, 10)}),
|
||||||
time.Now(),
|
time.Now(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -10,9 +10,9 @@ func MakeCommit(blockID BlockID, height int64, round int,
|
|||||||
|
|
||||||
// all sign
|
// all sign
|
||||||
for i := 0; i < len(validators); i++ {
|
for i := 0; i < len(validators); i++ {
|
||||||
|
addr := validators[i].GetPubKey().Address()
|
||||||
vote := &Vote{
|
vote := &Vote{
|
||||||
ValidatorAddress: validators[i].GetAddress(),
|
ValidatorAddress: addr,
|
||||||
ValidatorIndex: i,
|
ValidatorIndex: i,
|
||||||
Height: height,
|
Height: height,
|
||||||
Round: round,
|
Round: round,
|
||||||
|
@ -103,9 +103,9 @@ func TestComputeTxsOverhead(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{Txs{[]byte{6, 6, 6, 6, 6, 6}}, 2},
|
{Txs{[]byte{6, 6, 6, 6, 6, 6}}, 2},
|
||||||
// one 21 Mb transaction:
|
// one 21 Mb transaction:
|
||||||
{Txs{make([]byte, 22020096, 22020096)}, 5},
|
{Txs{make([]byte, 22020096)}, 5},
|
||||||
// two 21Mb/2 sized transactions:
|
// two 21Mb/2 sized transactions:
|
||||||
{Txs{make([]byte, 11010048, 11010048), make([]byte, 11010048, 11010048)}, 10},
|
{Txs{make([]byte, 11010048), make([]byte, 11010048)}, 10},
|
||||||
{Txs{[]byte{1, 2, 3}, []byte{1, 2, 3}, []byte{4, 5, 6}}, 6},
|
{Txs{[]byte{1, 2, 3}, []byte{1, 2, 3}, []byte{4, 5, 6}}, 6},
|
||||||
{Txs{[]byte{100, 5, 64}, []byte{42, 116, 118}, []byte{6, 6, 6}, []byte{6, 6, 6}}, 8},
|
{Txs{[]byte{100, 5, 64}, []byte{42, 116, 118}, []byte{6, 6, 6}, []byte{6, 6, 6}}, 8},
|
||||||
}
|
}
|
||||||
|
@ -101,6 +101,7 @@ func RandValidator(randPower bool, minPower int64) (*Validator, PrivValidator) {
|
|||||||
if randPower {
|
if randPower {
|
||||||
votePower += int64(cmn.RandUint32())
|
votePower += int64(cmn.RandUint32())
|
||||||
}
|
}
|
||||||
val := NewValidator(privVal.GetPubKey(), votePower)
|
pubKey := privVal.GetPubKey()
|
||||||
|
val := NewValidator(pubKey, votePower)
|
||||||
return val, privVal
|
return val, privVal
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,8 @@ func TestAddVote(t *testing.T) {
|
|||||||
|
|
||||||
// t.Logf(">> %v", voteSet)
|
// t.Logf(">> %v", voteSet)
|
||||||
|
|
||||||
if voteSet.GetByAddress(val0.GetAddress()) != nil {
|
val0Addr := val0.GetPubKey().Address()
|
||||||
|
if voteSet.GetByAddress(val0Addr) != nil {
|
||||||
t.Errorf("Expected GetByAddress(val0.Address) to be nil")
|
t.Errorf("Expected GetByAddress(val0.Address) to be nil")
|
||||||
}
|
}
|
||||||
if voteSet.BitArray().GetIndex(0) {
|
if voteSet.BitArray().GetIndex(0) {
|
||||||
@ -78,7 +79,7 @@ func TestAddVote(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vote := &Vote{
|
vote := &Vote{
|
||||||
ValidatorAddress: val0.GetAddress(),
|
ValidatorAddress: val0Addr,
|
||||||
ValidatorIndex: 0, // since privValidators are in order
|
ValidatorIndex: 0, // since privValidators are in order
|
||||||
Height: height,
|
Height: height,
|
||||||
Round: round,
|
Round: round,
|
||||||
@ -91,7 +92,7 @@ func TestAddVote(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if voteSet.GetByAddress(val0.GetAddress()) == nil {
|
if voteSet.GetByAddress(val0Addr) == nil {
|
||||||
t.Errorf("Expected GetByAddress(val0.Address) to be present")
|
t.Errorf("Expected GetByAddress(val0.Address) to be present")
|
||||||
}
|
}
|
||||||
if !voteSet.BitArray().GetIndex(0) {
|
if !voteSet.BitArray().GetIndex(0) {
|
||||||
@ -118,7 +119,8 @@ func Test2_3Majority(t *testing.T) {
|
|||||||
}
|
}
|
||||||
// 6 out of 10 voted for nil.
|
// 6 out of 10 voted for nil.
|
||||||
for i := 0; i < 6; i++ {
|
for i := 0; i < 6; i++ {
|
||||||
vote := withValidator(voteProto, privValidators[i].GetAddress(), i)
|
addr := privValidators[i].GetPubKey().Address()
|
||||||
|
vote := withValidator(voteProto, addr, i)
|
||||||
_, err := signAddVote(privValidators[i], vote, voteSet)
|
_, err := signAddVote(privValidators[i], vote, voteSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -131,7 +133,8 @@ func Test2_3Majority(t *testing.T) {
|
|||||||
|
|
||||||
// 7th validator voted for some blockhash
|
// 7th validator voted for some blockhash
|
||||||
{
|
{
|
||||||
vote := withValidator(voteProto, privValidators[6].GetAddress(), 6)
|
addr := privValidators[6].GetPubKey().Address()
|
||||||
|
vote := withValidator(voteProto, addr, 6)
|
||||||
_, err := signAddVote(privValidators[6], withBlockHash(vote, cmn.RandBytes(32)), voteSet)
|
_, err := signAddVote(privValidators[6], withBlockHash(vote, cmn.RandBytes(32)), voteSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -144,7 +147,8 @@ func Test2_3Majority(t *testing.T) {
|
|||||||
|
|
||||||
// 8th validator voted for nil.
|
// 8th validator voted for nil.
|
||||||
{
|
{
|
||||||
vote := withValidator(voteProto, privValidators[7].GetAddress(), 7)
|
addr := privValidators[7].GetPubKey().Address()
|
||||||
|
vote := withValidator(voteProto, addr, 7)
|
||||||
_, err := signAddVote(privValidators[7], vote, voteSet)
|
_, err := signAddVote(privValidators[7], vote, voteSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -176,7 +180,8 @@ func Test2_3MajorityRedux(t *testing.T) {
|
|||||||
|
|
||||||
// 66 out of 100 voted for nil.
|
// 66 out of 100 voted for nil.
|
||||||
for i := 0; i < 66; i++ {
|
for i := 0; i < 66; i++ {
|
||||||
vote := withValidator(voteProto, privValidators[i].GetAddress(), i)
|
addr := privValidators[i].GetPubKey().Address()
|
||||||
|
vote := withValidator(voteProto, addr, i)
|
||||||
_, err := signAddVote(privValidators[i], vote, voteSet)
|
_, err := signAddVote(privValidators[i], vote, voteSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -189,7 +194,8 @@ func Test2_3MajorityRedux(t *testing.T) {
|
|||||||
|
|
||||||
// 67th validator voted for nil
|
// 67th validator voted for nil
|
||||||
{
|
{
|
||||||
vote := withValidator(voteProto, privValidators[66].GetAddress(), 66)
|
adrr := privValidators[66].GetPubKey().Address()
|
||||||
|
vote := withValidator(voteProto, adrr, 66)
|
||||||
_, err := signAddVote(privValidators[66], withBlockHash(vote, nil), voteSet)
|
_, err := signAddVote(privValidators[66], withBlockHash(vote, nil), voteSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -202,7 +208,8 @@ func Test2_3MajorityRedux(t *testing.T) {
|
|||||||
|
|
||||||
// 68th validator voted for a different BlockParts PartSetHeader
|
// 68th validator voted for a different BlockParts PartSetHeader
|
||||||
{
|
{
|
||||||
vote := withValidator(voteProto, privValidators[67].GetAddress(), 67)
|
addr := privValidators[67].GetPubKey().Address()
|
||||||
|
vote := withValidator(voteProto, addr, 67)
|
||||||
blockPartsHeader := PartSetHeader{blockPartsTotal, crypto.CRandBytes(32)}
|
blockPartsHeader := PartSetHeader{blockPartsTotal, crypto.CRandBytes(32)}
|
||||||
_, err := signAddVote(privValidators[67], withBlockPartsHeader(vote, blockPartsHeader), voteSet)
|
_, err := signAddVote(privValidators[67], withBlockPartsHeader(vote, blockPartsHeader), voteSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -216,7 +223,8 @@ func Test2_3MajorityRedux(t *testing.T) {
|
|||||||
|
|
||||||
// 69th validator voted for different BlockParts Total
|
// 69th validator voted for different BlockParts Total
|
||||||
{
|
{
|
||||||
vote := withValidator(voteProto, privValidators[68].GetAddress(), 68)
|
addr := privValidators[68].GetPubKey().Address()
|
||||||
|
vote := withValidator(voteProto, addr, 68)
|
||||||
blockPartsHeader := PartSetHeader{blockPartsTotal + 1, blockPartsHeader.Hash}
|
blockPartsHeader := PartSetHeader{blockPartsTotal + 1, blockPartsHeader.Hash}
|
||||||
_, err := signAddVote(privValidators[68], withBlockPartsHeader(vote, blockPartsHeader), voteSet)
|
_, err := signAddVote(privValidators[68], withBlockPartsHeader(vote, blockPartsHeader), voteSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -230,7 +238,8 @@ func Test2_3MajorityRedux(t *testing.T) {
|
|||||||
|
|
||||||
// 70th validator voted for different BlockHash
|
// 70th validator voted for different BlockHash
|
||||||
{
|
{
|
||||||
vote := withValidator(voteProto, privValidators[69].GetAddress(), 69)
|
addr := privValidators[69].GetPubKey().Address()
|
||||||
|
vote := withValidator(voteProto, addr, 69)
|
||||||
_, err := signAddVote(privValidators[69], withBlockHash(vote, cmn.RandBytes(32)), voteSet)
|
_, err := signAddVote(privValidators[69], withBlockHash(vote, cmn.RandBytes(32)), voteSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -243,7 +252,8 @@ func Test2_3MajorityRedux(t *testing.T) {
|
|||||||
|
|
||||||
// 71st validator voted for the right BlockHash & BlockPartsHeader
|
// 71st validator voted for the right BlockHash & BlockPartsHeader
|
||||||
{
|
{
|
||||||
vote := withValidator(voteProto, privValidators[70].GetAddress(), 70)
|
addr := privValidators[70].GetPubKey().Address()
|
||||||
|
vote := withValidator(voteProto, addr, 70)
|
||||||
_, err := signAddVote(privValidators[70], vote, voteSet)
|
_, err := signAddVote(privValidators[70], vote, voteSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -271,7 +281,8 @@ func TestBadVotes(t *testing.T) {
|
|||||||
|
|
||||||
// val0 votes for nil.
|
// val0 votes for nil.
|
||||||
{
|
{
|
||||||
vote := withValidator(voteProto, privValidators[0].GetAddress(), 0)
|
addr := privValidators[0].GetPubKey().Address()
|
||||||
|
vote := withValidator(voteProto, addr, 0)
|
||||||
added, err := signAddVote(privValidators[0], vote, voteSet)
|
added, err := signAddVote(privValidators[0], vote, voteSet)
|
||||||
if !added || err != nil {
|
if !added || err != nil {
|
||||||
t.Errorf("Expected VoteSet.Add to succeed")
|
t.Errorf("Expected VoteSet.Add to succeed")
|
||||||
@ -280,7 +291,8 @@ func TestBadVotes(t *testing.T) {
|
|||||||
|
|
||||||
// val0 votes again for some block.
|
// val0 votes again for some block.
|
||||||
{
|
{
|
||||||
vote := withValidator(voteProto, privValidators[0].GetAddress(), 0)
|
addr := privValidators[0].GetPubKey().Address()
|
||||||
|
vote := withValidator(voteProto, addr, 0)
|
||||||
added, err := signAddVote(privValidators[0], withBlockHash(vote, cmn.RandBytes(32)), voteSet)
|
added, err := signAddVote(privValidators[0], withBlockHash(vote, cmn.RandBytes(32)), voteSet)
|
||||||
if added || err == nil {
|
if added || err == nil {
|
||||||
t.Errorf("Expected VoteSet.Add to fail, conflicting vote.")
|
t.Errorf("Expected VoteSet.Add to fail, conflicting vote.")
|
||||||
@ -289,7 +301,8 @@ func TestBadVotes(t *testing.T) {
|
|||||||
|
|
||||||
// val1 votes on another height
|
// val1 votes on another height
|
||||||
{
|
{
|
||||||
vote := withValidator(voteProto, privValidators[1].GetAddress(), 1)
|
addr := privValidators[1].GetPubKey().Address()
|
||||||
|
vote := withValidator(voteProto, addr, 1)
|
||||||
added, err := signAddVote(privValidators[1], withHeight(vote, height+1), voteSet)
|
added, err := signAddVote(privValidators[1], withHeight(vote, height+1), voteSet)
|
||||||
if added || err == nil {
|
if added || err == nil {
|
||||||
t.Errorf("Expected VoteSet.Add to fail, wrong height")
|
t.Errorf("Expected VoteSet.Add to fail, wrong height")
|
||||||
@ -298,7 +311,8 @@ func TestBadVotes(t *testing.T) {
|
|||||||
|
|
||||||
// val2 votes on another round
|
// val2 votes on another round
|
||||||
{
|
{
|
||||||
vote := withValidator(voteProto, privValidators[2].GetAddress(), 2)
|
addr := privValidators[2].GetPubKey().Address()
|
||||||
|
vote := withValidator(voteProto, addr, 2)
|
||||||
added, err := signAddVote(privValidators[2], withRound(vote, round+1), voteSet)
|
added, err := signAddVote(privValidators[2], withRound(vote, round+1), voteSet)
|
||||||
if added || err == nil {
|
if added || err == nil {
|
||||||
t.Errorf("Expected VoteSet.Add to fail, wrong round")
|
t.Errorf("Expected VoteSet.Add to fail, wrong round")
|
||||||
@ -307,7 +321,8 @@ func TestBadVotes(t *testing.T) {
|
|||||||
|
|
||||||
// val3 votes of another type.
|
// val3 votes of another type.
|
||||||
{
|
{
|
||||||
vote := withValidator(voteProto, privValidators[3].GetAddress(), 3)
|
addr := privValidators[3].GetPubKey().Address()
|
||||||
|
vote := withValidator(voteProto, addr, 3)
|
||||||
added, err := signAddVote(privValidators[3], withType(vote, byte(PrecommitType)), voteSet)
|
added, err := signAddVote(privValidators[3], withType(vote, byte(PrecommitType)), voteSet)
|
||||||
if added || err == nil {
|
if added || err == nil {
|
||||||
t.Errorf("Expected VoteSet.Add to fail, wrong type")
|
t.Errorf("Expected VoteSet.Add to fail, wrong type")
|
||||||
@ -331,9 +346,10 @@ func TestConflicts(t *testing.T) {
|
|||||||
BlockID: BlockID{nil, PartSetHeader{}},
|
BlockID: BlockID{nil, PartSetHeader{}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val0Addr := privValidators[0].GetPubKey().Address()
|
||||||
// val0 votes for nil.
|
// val0 votes for nil.
|
||||||
{
|
{
|
||||||
vote := withValidator(voteProto, privValidators[0].GetAddress(), 0)
|
vote := withValidator(voteProto, val0Addr, 0)
|
||||||
added, err := signAddVote(privValidators[0], vote, voteSet)
|
added, err := signAddVote(privValidators[0], vote, voteSet)
|
||||||
if !added || err != nil {
|
if !added || err != nil {
|
||||||
t.Errorf("Expected VoteSet.Add to succeed")
|
t.Errorf("Expected VoteSet.Add to succeed")
|
||||||
@ -342,7 +358,7 @@ func TestConflicts(t *testing.T) {
|
|||||||
|
|
||||||
// val0 votes again for blockHash1.
|
// val0 votes again for blockHash1.
|
||||||
{
|
{
|
||||||
vote := withValidator(voteProto, privValidators[0].GetAddress(), 0)
|
vote := withValidator(voteProto, val0Addr, 0)
|
||||||
added, err := signAddVote(privValidators[0], withBlockHash(vote, blockHash1), voteSet)
|
added, err := signAddVote(privValidators[0], withBlockHash(vote, blockHash1), voteSet)
|
||||||
if added {
|
if added {
|
||||||
t.Errorf("Expected VoteSet.Add to fail, conflicting vote.")
|
t.Errorf("Expected VoteSet.Add to fail, conflicting vote.")
|
||||||
@ -357,7 +373,7 @@ func TestConflicts(t *testing.T) {
|
|||||||
|
|
||||||
// val0 votes again for blockHash1.
|
// val0 votes again for blockHash1.
|
||||||
{
|
{
|
||||||
vote := withValidator(voteProto, privValidators[0].GetAddress(), 0)
|
vote := withValidator(voteProto, val0Addr, 0)
|
||||||
added, err := signAddVote(privValidators[0], withBlockHash(vote, blockHash1), voteSet)
|
added, err := signAddVote(privValidators[0], withBlockHash(vote, blockHash1), voteSet)
|
||||||
if !added {
|
if !added {
|
||||||
t.Errorf("Expected VoteSet.Add to succeed, called SetPeerMaj23().")
|
t.Errorf("Expected VoteSet.Add to succeed, called SetPeerMaj23().")
|
||||||
@ -372,7 +388,7 @@ func TestConflicts(t *testing.T) {
|
|||||||
|
|
||||||
// val0 votes again for blockHash1.
|
// val0 votes again for blockHash1.
|
||||||
{
|
{
|
||||||
vote := withValidator(voteProto, privValidators[0].GetAddress(), 0)
|
vote := withValidator(voteProto, val0Addr, 0)
|
||||||
added, err := signAddVote(privValidators[0], withBlockHash(vote, blockHash2), voteSet)
|
added, err := signAddVote(privValidators[0], withBlockHash(vote, blockHash2), voteSet)
|
||||||
if added {
|
if added {
|
||||||
t.Errorf("Expected VoteSet.Add to fail, duplicate SetPeerMaj23() from peerA")
|
t.Errorf("Expected VoteSet.Add to fail, duplicate SetPeerMaj23() from peerA")
|
||||||
@ -384,7 +400,8 @@ func TestConflicts(t *testing.T) {
|
|||||||
|
|
||||||
// val1 votes for blockHash1.
|
// val1 votes for blockHash1.
|
||||||
{
|
{
|
||||||
vote := withValidator(voteProto, privValidators[1].GetAddress(), 1)
|
addr := privValidators[1].GetPubKey().Address()
|
||||||
|
vote := withValidator(voteProto, addr, 1)
|
||||||
added, err := signAddVote(privValidators[1], withBlockHash(vote, blockHash1), voteSet)
|
added, err := signAddVote(privValidators[1], withBlockHash(vote, blockHash1), voteSet)
|
||||||
if !added || err != nil {
|
if !added || err != nil {
|
||||||
t.Errorf("Expected VoteSet.Add to succeed")
|
t.Errorf("Expected VoteSet.Add to succeed")
|
||||||
@ -401,7 +418,8 @@ func TestConflicts(t *testing.T) {
|
|||||||
|
|
||||||
// val2 votes for blockHash2.
|
// val2 votes for blockHash2.
|
||||||
{
|
{
|
||||||
vote := withValidator(voteProto, privValidators[2].GetAddress(), 2)
|
addr := privValidators[2].GetPubKey().Address()
|
||||||
|
vote := withValidator(voteProto, addr, 2)
|
||||||
added, err := signAddVote(privValidators[2], withBlockHash(vote, blockHash2), voteSet)
|
added, err := signAddVote(privValidators[2], withBlockHash(vote, blockHash2), voteSet)
|
||||||
if !added || err != nil {
|
if !added || err != nil {
|
||||||
t.Errorf("Expected VoteSet.Add to succeed")
|
t.Errorf("Expected VoteSet.Add to succeed")
|
||||||
@ -421,7 +439,8 @@ func TestConflicts(t *testing.T) {
|
|||||||
|
|
||||||
// val2 votes for blockHash1.
|
// val2 votes for blockHash1.
|
||||||
{
|
{
|
||||||
vote := withValidator(voteProto, privValidators[2].GetAddress(), 2)
|
addr := privValidators[2].GetPubKey().Address()
|
||||||
|
vote := withValidator(voteProto, addr, 2)
|
||||||
added, err := signAddVote(privValidators[2], withBlockHash(vote, blockHash1), voteSet)
|
added, err := signAddVote(privValidators[2], withBlockHash(vote, blockHash1), voteSet)
|
||||||
if !added {
|
if !added {
|
||||||
t.Errorf("Expected VoteSet.Add to succeed")
|
t.Errorf("Expected VoteSet.Add to succeed")
|
||||||
@ -462,7 +481,8 @@ func TestMakeCommit(t *testing.T) {
|
|||||||
|
|
||||||
// 6 out of 10 voted for some block.
|
// 6 out of 10 voted for some block.
|
||||||
for i := 0; i < 6; i++ {
|
for i := 0; i < 6; i++ {
|
||||||
vote := withValidator(voteProto, privValidators[i].GetAddress(), i)
|
addr := privValidators[i].GetPubKey().Address()
|
||||||
|
vote := withValidator(voteProto, addr, i)
|
||||||
_, err := signAddVote(privValidators[i], vote, voteSet)
|
_, err := signAddVote(privValidators[i], vote, voteSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -474,7 +494,8 @@ func TestMakeCommit(t *testing.T) {
|
|||||||
|
|
||||||
// 7th voted for some other block.
|
// 7th voted for some other block.
|
||||||
{
|
{
|
||||||
vote := withValidator(voteProto, privValidators[6].GetAddress(), 6)
|
addr := privValidators[6].GetPubKey().Address()
|
||||||
|
vote := withValidator(voteProto, addr, 6)
|
||||||
vote = withBlockHash(vote, cmn.RandBytes(32))
|
vote = withBlockHash(vote, cmn.RandBytes(32))
|
||||||
vote = withBlockPartsHeader(vote, PartSetHeader{123, cmn.RandBytes(32)})
|
vote = withBlockPartsHeader(vote, PartSetHeader{123, cmn.RandBytes(32)})
|
||||||
|
|
||||||
@ -486,7 +507,8 @@ func TestMakeCommit(t *testing.T) {
|
|||||||
|
|
||||||
// The 8th voted like everyone else.
|
// The 8th voted like everyone else.
|
||||||
{
|
{
|
||||||
vote := withValidator(voteProto, privValidators[7].GetAddress(), 7)
|
addr := privValidators[7].GetPubKey().Address()
|
||||||
|
vote := withValidator(voteProto, addr, 7)
|
||||||
_, err := signAddVote(privValidators[7], vote, voteSet)
|
_, err := signAddVote(privValidators[7], vote, voteSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
|
@ -18,7 +18,7 @@ const (
|
|||||||
// TMCoreSemVer is the current version of Tendermint Core.
|
// TMCoreSemVer is the current version of Tendermint Core.
|
||||||
// It's the Semantic Version of the software.
|
// It's the Semantic Version of the software.
|
||||||
// Must be a string because scripts like dist.sh read this file.
|
// Must be a string because scripts like dist.sh read this file.
|
||||||
TMCoreSemVer = "0.27.4"
|
TMCoreSemVer = "0.28.0"
|
||||||
|
|
||||||
// ABCISemVer is the semantic version of the ABCI library
|
// ABCISemVer is the semantic version of the ABCI library
|
||||||
ABCISemVer = "0.15.0"
|
ABCISemVer = "0.15.0"
|
||||||
|
Reference in New Issue
Block a user