Make types use Amino; Refactor PrivValidator* to FilePV/SocketPV
This commit is contained in:
parent
901b456151
commit
34974e3932
|
@ -1,128 +1,29 @@
|
||||||
# ADR 008: PrivValidator
|
# ADR 008: SocketPV
|
||||||
|
|
||||||
## Context
|
Tendermint node's should support only two in-process PrivValidator
|
||||||
|
implementations:
|
||||||
|
|
||||||
The current PrivValidator is monolithic and isn't easily reuseable by alternative signers.
|
- FilePV uses an unencrypted private key in a "priv_validator.json" file - no
|
||||||
|
configuration required (just `tendermint init`).
|
||||||
|
- SocketPV uses a socket to send signing requests to another process - user is
|
||||||
|
responsible for starting that process themselves.
|
||||||
|
|
||||||
For instance, see https://github.com/tendermint/tendermint/issues/673
|
The SocketPV address can be provided via flags at the command line - doing so
|
||||||
|
will cause Tendermint to ignore any "priv_validator.json" file and to listen on
|
||||||
|
the given address for incoming connections from an external priv_validator
|
||||||
|
process. It will halt any operation until at least one external process
|
||||||
|
succesfully connected.
|
||||||
|
|
||||||
The goal is to have a clean PrivValidator interface like:
|
The external priv_validator process will dial the address to connect to
|
||||||
|
Tendermint, and then Tendermint will send requests on the ensuing connection to
|
||||||
|
sign votes and proposals. Thus the external process initiates the connection,
|
||||||
|
but the Tendermint process makes all requests. In a later stage we're going to
|
||||||
|
support multiple validators for fault tolerance. To prevent double signing they
|
||||||
|
need to be synced, which is deferred to an external solution (see #1185).
|
||||||
|
|
||||||
```
|
In addition, Tendermint will provide implementations that can be run in that
|
||||||
type PrivValidator interface {
|
external process. These include:
|
||||||
Address() data.Bytes
|
|
||||||
PubKey() crypto.PubKey
|
|
||||||
|
|
||||||
SignVote(chainID string, vote *types.Vote) error
|
|
||||||
SignProposal(chainID string, proposal *types.Proposal) error
|
|
||||||
SignHeartbeat(chainID string, heartbeat *types.Heartbeat) error
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
It should also be easy to re-use the LastSignedInfo logic to avoid double signing.
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
|
|
||||||
Tendermint node's should support only two in-process PrivValidator implementations:
|
|
||||||
|
|
||||||
- PrivValidatorUnencrypted uses an unencrypted private key in a "priv_validator.json" file - no configuration required (just `tendermint init`).
|
|
||||||
- PrivValidatorSocket uses a socket to send signing requests to another process - user is responsible for starting that process themselves.
|
|
||||||
|
|
||||||
The PrivValidatorSocket address can be provided via flags at the command line -
|
|
||||||
doing so will cause Tendermint to ignore any "priv_validator.json" file and to listen
|
|
||||||
on the given address for incoming connections from an external priv_validator process.
|
|
||||||
It will halt any operation until at least one external process succesfully
|
|
||||||
connected.
|
|
||||||
|
|
||||||
The external priv_validator process will dial the address to connect to Tendermint,
|
|
||||||
and then Tendermint will send requests on the ensuing connection to sign votes and proposals.
|
|
||||||
Thus the external process initiates the connection, but the Tendermint process makes all requests.
|
|
||||||
In a later stage we're going to support multiple validators for fault
|
|
||||||
tolerance. To prevent double signing they need to be synced, which is deferred
|
|
||||||
to an external solution (see #1185).
|
|
||||||
|
|
||||||
In addition, Tendermint will provide implementations that can be run in that external process.
|
|
||||||
These include:
|
|
||||||
|
|
||||||
- PrivValidatorEncrypted uses an encrypted private key persisted to disk - user must enter password to decrypt key when process is started.
|
|
||||||
- PrivValidatorLedger uses a Ledger Nano S to handle all signing.
|
|
||||||
|
|
||||||
What follows are descriptions of useful types
|
|
||||||
|
|
||||||
### Signer
|
|
||||||
|
|
||||||
```
|
|
||||||
type Signer interface {
|
|
||||||
Sign(msg []byte) (crypto.Signature, error)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Signer signs a message. It can also return an error.
|
|
||||||
|
|
||||||
### ValidatorID
|
|
||||||
|
|
||||||
|
|
||||||
ValidatorID is just the Address and PubKey
|
|
||||||
|
|
||||||
```
|
|
||||||
type ValidatorID struct {
|
|
||||||
Address data.Bytes `json:"address"`
|
|
||||||
PubKey crypto.PubKey `json:"pub_key"`
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### LastSignedInfo
|
|
||||||
|
|
||||||
LastSignedInfo tracks the last thing we signed:
|
|
||||||
|
|
||||||
```
|
|
||||||
type LastSignedInfo struct {
|
|
||||||
Height int64 `json:"height"`
|
|
||||||
Round int `json:"round"`
|
|
||||||
Step int8 `json:"step"`
|
|
||||||
Signature crypto.Signature `json:"signature,omitempty"` // so we dont lose signatures
|
|
||||||
SignBytes data.Bytes `json:"signbytes,omitempty"` // so we dont lose signatures
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
It exposes methods for signing votes and proposals using a `Signer`.
|
|
||||||
|
|
||||||
This allows it to easily be reused by developers implemented their own PrivValidator.
|
|
||||||
|
|
||||||
### PrivValidatorUnencrypted
|
|
||||||
|
|
||||||
```
|
|
||||||
type PrivValidatorUnencrypted struct {
|
|
||||||
ID types.ValidatorID `json:"id"`
|
|
||||||
PrivKey PrivKey `json:"priv_key"`
|
|
||||||
LastSignedInfo *LastSignedInfo `json:"last_signed_info"`
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Has the same structure as currently, but broken up into sub structs.
|
|
||||||
|
|
||||||
Note the LastSignedInfo is mutated in place every time we sign.
|
|
||||||
|
|
||||||
### PrivValidatorJSON
|
|
||||||
|
|
||||||
The "priv_validator.json" file supports only the PrivValidatorUnencrypted type.
|
|
||||||
|
|
||||||
It unmarshals into PrivValidatorJSON, which is used as the default PrivValidator type.
|
|
||||||
It wraps the PrivValidatorUnencrypted and persists it to disk after every signature.
|
|
||||||
|
|
||||||
## Status
|
|
||||||
|
|
||||||
Accepted.
|
|
||||||
|
|
||||||
## Consequences
|
|
||||||
|
|
||||||
### Positive
|
|
||||||
|
|
||||||
- Cleaner separation of components enabling re-use.
|
|
||||||
|
|
||||||
### Negative
|
|
||||||
|
|
||||||
- More files - led to creation of new directory.
|
|
||||||
|
|
||||||
### Neutral
|
|
||||||
|
|
||||||
|
- FilePV will encrypt the private key, and the user must enter password to
|
||||||
|
decrypt key when process is started.
|
||||||
|
- LedgerPV uses a Ledger Nano S to handle all signing.
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
wire "github.com/tendermint/tendermint/wire"
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
"github.com/tendermint/tmlibs/merkle"
|
"github.com/tendermint/tmlibs/merkle"
|
||||||
"golang.org/x/crypto/ripemd160"
|
"golang.org/x/crypto/ripemd160"
|
||||||
|
@ -95,7 +94,7 @@ func (b *Block) Hash() cmn.HexBytes {
|
||||||
// MakePartSet returns a PartSet containing parts of a serialized block.
|
// MakePartSet returns a PartSet containing parts of a serialized block.
|
||||||
// This is the form in which the block is gossipped to peers.
|
// This is the form in which the block is gossipped to peers.
|
||||||
func (b *Block) MakePartSet(partSize int) *PartSet {
|
func (b *Block) MakePartSet(partSize int) *PartSet {
|
||||||
bz, err := wire.MarshalBinary(b)
|
bz, err := cdc.MarshalBinary(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -183,19 +182,19 @@ func (h *Header) Hash() cmn.HexBytes {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return merkle.SimpleHashFromMap(map[string]merkle.Hasher{
|
return merkle.SimpleHashFromMap(map[string]merkle.Hasher{
|
||||||
"ChainID": wireHasher(h.ChainID),
|
"ChainID": aminoHasher(h.ChainID),
|
||||||
"Height": wireHasher(h.Height),
|
"Height": aminoHasher(h.Height),
|
||||||
"Time": wireHasher(h.Time),
|
"Time": aminoHasher(h.Time),
|
||||||
"NumTxs": wireHasher(h.NumTxs),
|
"NumTxs": aminoHasher(h.NumTxs),
|
||||||
"TotalTxs": wireHasher(h.TotalTxs),
|
"TotalTxs": aminoHasher(h.TotalTxs),
|
||||||
"LastBlockID": wireHasher(h.LastBlockID),
|
"LastBlockID": aminoHasher(h.LastBlockID),
|
||||||
"LastCommit": wireHasher(h.LastCommitHash),
|
"LastCommit": aminoHasher(h.LastCommitHash),
|
||||||
"Data": wireHasher(h.DataHash),
|
"Data": aminoHasher(h.DataHash),
|
||||||
"Validators": wireHasher(h.ValidatorsHash),
|
"Validators": aminoHasher(h.ValidatorsHash),
|
||||||
"App": wireHasher(h.AppHash),
|
"App": aminoHasher(h.AppHash),
|
||||||
"Consensus": wireHasher(h.ConsensusHash),
|
"Consensus": aminoHasher(h.ConsensusHash),
|
||||||
"Results": wireHasher(h.LastResultsHash),
|
"Results": aminoHasher(h.LastResultsHash),
|
||||||
"Evidence": wireHasher(h.EvidenceHash),
|
"Evidence": aminoHasher(h.EvidenceHash),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,7 +363,7 @@ func (commit *Commit) Hash() cmn.HexBytes {
|
||||||
if commit.hash == nil {
|
if commit.hash == nil {
|
||||||
bs := make([]merkle.Hasher, len(commit.Precommits))
|
bs := make([]merkle.Hasher, len(commit.Precommits))
|
||||||
for i, precommit := range commit.Precommits {
|
for i, precommit := range commit.Precommits {
|
||||||
bs[i] = wireHasher(precommit)
|
bs[i] = aminoHasher(precommit)
|
||||||
}
|
}
|
||||||
commit.hash = merkle.SimpleHashFromHashers(bs)
|
commit.hash = merkle.SimpleHashFromHashers(bs)
|
||||||
}
|
}
|
||||||
|
@ -499,7 +498,7 @@ func (blockID BlockID) Equals(other BlockID) bool {
|
||||||
|
|
||||||
// Key returns a machine-readable string representation of the BlockID
|
// Key returns a machine-readable string representation of the BlockID
|
||||||
func (blockID BlockID) Key() string {
|
func (blockID BlockID) Key() string {
|
||||||
bz, err := wire.MarshalBinary(blockID.PartsHeader)
|
bz, err := cdc.MarshalBinary(blockID.PartsHeader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -519,7 +518,7 @@ type hasher struct {
|
||||||
|
|
||||||
func (h hasher) Hash() []byte {
|
func (h hasher) Hash() []byte {
|
||||||
hasher := ripemd160.New()
|
hasher := ripemd160.New()
|
||||||
bz, err := wire.MarshalBinary(h.item)
|
bz, err := cdc.MarshalBinaryBare(h.item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -531,11 +530,11 @@ func (h hasher) Hash() []byte {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func tmHash(item interface{}) []byte {
|
func aminoHash(item interface{}) []byte {
|
||||||
h := hasher{item}
|
h := hasher{item}
|
||||||
return h.Hash()
|
return h.Hash()
|
||||||
}
|
}
|
||||||
|
|
||||||
func wireHasher(item interface{}) merkle.Hasher {
|
func aminoHasher(item interface{}) merkle.Hasher {
|
||||||
return hasher{item}
|
return hasher{item}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,7 @@ func TestValidateBlock(t *testing.T) {
|
||||||
lastID := makeBlockIDRandom()
|
lastID := makeBlockIDRandom()
|
||||||
h := int64(3)
|
h := int64(3)
|
||||||
|
|
||||||
voteSet, _, vals := randVoteSet(h-1, 1, VoteTypePrecommit,
|
voteSet, _, vals := randVoteSet(h-1, 1, VoteTypePrecommit, 10, 1)
|
||||||
10, 1)
|
|
||||||
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals)
|
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
|
|
@ -3,14 +3,14 @@ package types
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
wire "github.com/tendermint/tendermint/wire"
|
"github.com/tendermint/go-amino"
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// canonical json is wire's json for structs with fields in alphabetical order
|
// Canonical json is amino's json for structs with fields in alphabetical order
|
||||||
|
|
||||||
// TimeFormat is used for generating the sigs
|
// TimeFormat is used for generating the sigs
|
||||||
const TimeFormat = wire.RFC3339Millis
|
const TimeFormat = amino.RFC3339Millis
|
||||||
|
|
||||||
type CanonicalJSONBlockID struct {
|
type CanonicalJSONBlockID struct {
|
||||||
Hash cmn.HexBytes `json:"hash,omitempty"`
|
Hash cmn.HexBytes `json:"hash,omitempty"`
|
||||||
|
@ -18,11 +18,13 @@ type CanonicalJSONBlockID struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type CanonicalJSONPartSetHeader struct {
|
type CanonicalJSONPartSetHeader struct {
|
||||||
Hash cmn.HexBytes `json:"hash"`
|
Hash cmn.HexBytes `json:"hash,omitempty"`
|
||||||
Total int `json:"total"`
|
Total int `json:"total,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CanonicalJSONProposal struct {
|
type CanonicalJSONProposal struct {
|
||||||
|
ChainID string `json:"@chain_id"`
|
||||||
|
Type string `json:"@type"`
|
||||||
BlockPartsHeader CanonicalJSONPartSetHeader `json:"block_parts_header"`
|
BlockPartsHeader CanonicalJSONPartSetHeader `json:"block_parts_header"`
|
||||||
Height int64 `json:"height"`
|
Height int64 `json:"height"`
|
||||||
POLBlockID CanonicalJSONBlockID `json:"pol_block_id"`
|
POLBlockID CanonicalJSONBlockID `json:"pol_block_id"`
|
||||||
|
@ -32,14 +34,18 @@ type CanonicalJSONProposal struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type CanonicalJSONVote struct {
|
type CanonicalJSONVote struct {
|
||||||
|
ChainID string `json:"@chain_id"`
|
||||||
|
Type string `json:"@type"`
|
||||||
BlockID CanonicalJSONBlockID `json:"block_id"`
|
BlockID CanonicalJSONBlockID `json:"block_id"`
|
||||||
Height int64 `json:"height"`
|
Height int64 `json:"height"`
|
||||||
Round int `json:"round"`
|
Round int `json:"round"`
|
||||||
Timestamp string `json:"timestamp"`
|
Timestamp string `json:"timestamp"`
|
||||||
Type byte `json:"type"`
|
VoteType byte `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CanonicalJSONHeartbeat struct {
|
type CanonicalJSONHeartbeat struct {
|
||||||
|
ChainID string `json:"@chain_id"`
|
||||||
|
Type string `json:"@type"`
|
||||||
Height int64 `json:"height"`
|
Height int64 `json:"height"`
|
||||||
Round int `json:"round"`
|
Round int `json:"round"`
|
||||||
Sequence int `json:"sequence"`
|
Sequence int `json:"sequence"`
|
||||||
|
@ -47,24 +53,6 @@ type CanonicalJSONHeartbeat struct {
|
||||||
ValidatorIndex int `json:"validator_index"`
|
ValidatorIndex int `json:"validator_index"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------
|
|
||||||
// Messages including a "chain id" can only be applied to one chain, hence "Once"
|
|
||||||
|
|
||||||
type CanonicalJSONOnceProposal struct {
|
|
||||||
ChainID string `json:"chain_id"`
|
|
||||||
Proposal CanonicalJSONProposal `json:"proposal"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CanonicalJSONOnceVote struct {
|
|
||||||
ChainID string `json:"chain_id"`
|
|
||||||
Vote CanonicalJSONVote `json:"vote"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CanonicalJSONOnceHeartbeat struct {
|
|
||||||
ChainID string `json:"chain_id"`
|
|
||||||
Heartbeat CanonicalJSONHeartbeat `json:"heartbeat"`
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------
|
//-----------------------------------
|
||||||
// Canonicalize the structs
|
// Canonicalize the structs
|
||||||
|
|
||||||
|
@ -82,8 +70,10 @@ func CanonicalPartSetHeader(psh PartSetHeader) CanonicalJSONPartSetHeader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func CanonicalProposal(proposal *Proposal) CanonicalJSONProposal {
|
func CanonicalProposal(chainID string, proposal *Proposal) CanonicalJSONProposal {
|
||||||
return CanonicalJSONProposal{
|
return CanonicalJSONProposal{
|
||||||
|
ChainID: chainID,
|
||||||
|
Type: "proposal",
|
||||||
BlockPartsHeader: CanonicalPartSetHeader(proposal.BlockPartsHeader),
|
BlockPartsHeader: CanonicalPartSetHeader(proposal.BlockPartsHeader),
|
||||||
Height: proposal.Height,
|
Height: proposal.Height,
|
||||||
Timestamp: CanonicalTime(proposal.Timestamp),
|
Timestamp: CanonicalTime(proposal.Timestamp),
|
||||||
|
@ -93,28 +83,32 @@ func CanonicalProposal(proposal *Proposal) CanonicalJSONProposal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func CanonicalVote(vote *Vote) CanonicalJSONVote {
|
func CanonicalVote(chainID string, vote *Vote) CanonicalJSONVote {
|
||||||
return CanonicalJSONVote{
|
return CanonicalJSONVote{
|
||||||
|
ChainID: chainID,
|
||||||
|
Type: "vote",
|
||||||
BlockID: CanonicalBlockID(vote.BlockID),
|
BlockID: CanonicalBlockID(vote.BlockID),
|
||||||
Height: vote.Height,
|
Height: vote.Height,
|
||||||
Round: vote.Round,
|
Round: vote.Round,
|
||||||
Timestamp: CanonicalTime(vote.Timestamp),
|
Timestamp: CanonicalTime(vote.Timestamp),
|
||||||
Type: vote.Type,
|
VoteType: vote.Type,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func CanonicalHeartbeat(heartbeat *Heartbeat) CanonicalJSONHeartbeat {
|
func CanonicalHeartbeat(chainID string, heartbeat *Heartbeat) CanonicalJSONHeartbeat {
|
||||||
return CanonicalJSONHeartbeat{
|
return CanonicalJSONHeartbeat{
|
||||||
heartbeat.Height,
|
ChainID: chainID,
|
||||||
heartbeat.Round,
|
Type: "heartbeat",
|
||||||
heartbeat.Sequence,
|
Height: heartbeat.Height,
|
||||||
heartbeat.ValidatorAddress,
|
Round: heartbeat.Round,
|
||||||
heartbeat.ValidatorIndex,
|
Sequence: heartbeat.Sequence,
|
||||||
|
ValidatorAddress: heartbeat.ValidatorAddress,
|
||||||
|
ValidatorIndex: heartbeat.ValidatorIndex,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func CanonicalTime(t time.Time) string {
|
func CanonicalTime(t time.Time) string {
|
||||||
// note that sending time over wire resets it to
|
// Note that sending time over amino resets it to
|
||||||
// local time, we need to force UTC here, so the
|
// local time, we need to force UTC here, so the
|
||||||
// signatures match
|
// signatures match
|
||||||
return t.UTC().Format(TimeFormat)
|
return t.UTC().Format(TimeFormat)
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/tendermint/go-amino"
|
||||||
"github.com/tendermint/go-crypto"
|
"github.com/tendermint/go-crypto"
|
||||||
wire "github.com/tendermint/tendermint/wire"
|
|
||||||
"github.com/tendermint/tmlibs/merkle"
|
"github.com/tendermint/tmlibs/merkle"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -38,56 +38,11 @@ type Evidence interface {
|
||||||
String() string
|
String() string
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------------------------------------------
|
func RegisterEvidence(cdc *amino.Codec) {
|
||||||
|
cdc.RegisterInterface((*Evidence)(nil), nil)
|
||||||
// EvidenceList is a list of Evidence. Evidences is not a word.
|
cdc.RegisterConcrete(&DuplicateVoteEvidence{}, "tendermint/Evidence", nil)
|
||||||
type EvidenceList []Evidence
|
|
||||||
|
|
||||||
// Hash returns the simple merkle root hash of the EvidenceList.
|
|
||||||
func (evl EvidenceList) Hash() []byte {
|
|
||||||
// Recursive impl.
|
|
||||||
// Copied from tmlibs/merkle to avoid allocations
|
|
||||||
switch len(evl) {
|
|
||||||
case 0:
|
|
||||||
return nil
|
|
||||||
case 1:
|
|
||||||
return evl[0].Hash()
|
|
||||||
default:
|
|
||||||
left := EvidenceList(evl[:(len(evl)+1)/2]).Hash()
|
|
||||||
right := EvidenceList(evl[(len(evl)+1)/2:]).Hash()
|
|
||||||
return merkle.SimpleHashFromTwoHashes(left, right)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (evl EvidenceList) String() string {
|
|
||||||
s := ""
|
|
||||||
for _, e := range evl {
|
|
||||||
s += fmt.Sprintf("%s\t\t", e)
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Has returns true if the evidence is in the EvidenceList.
|
|
||||||
func (evl EvidenceList) Has(evidence Evidence) bool {
|
|
||||||
for _, ev := range evl {
|
|
||||||
if ev.Equal(evidence) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
//-------------------------------------------
|
|
||||||
|
|
||||||
const (
|
|
||||||
evidenceTypeDuplicateVote = byte(0x01)
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = wire.RegisterInterface(
|
|
||||||
struct{ Evidence }{},
|
|
||||||
wire.ConcreteType{&DuplicateVoteEvidence{}, evidenceTypeDuplicateVote},
|
|
||||||
)
|
|
||||||
|
|
||||||
//-------------------------------------------
|
//-------------------------------------------
|
||||||
|
|
||||||
// DuplicateVoteEvidence contains evidence a validator signed two conflicting votes.
|
// DuplicateVoteEvidence contains evidence a validator signed two conflicting votes.
|
||||||
|
@ -120,7 +75,7 @@ func (dve *DuplicateVoteEvidence) Index() int {
|
||||||
|
|
||||||
// Hash returns the hash of the evidence.
|
// Hash returns the hash of the evidence.
|
||||||
func (dve *DuplicateVoteEvidence) Hash() []byte {
|
func (dve *DuplicateVoteEvidence) Hash() []byte {
|
||||||
return wireHasher(dve).Hash()
|
return aminoHasher(dve).Hash()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify returns an error if the two votes aren't conflicting.
|
// Verify returns an error if the two votes aren't conflicting.
|
||||||
|
@ -165,8 +120,8 @@ func (dve *DuplicateVoteEvidence) Equal(ev Evidence) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// just check their hashes
|
// just check their hashes
|
||||||
dveHash := wireHasher(dve).Hash()
|
dveHash := aminoHasher(dve).Hash()
|
||||||
evHash := wireHasher(ev).Hash()
|
evHash := aminoHasher(ev).Hash()
|
||||||
return bytes.Equal(dveHash, evHash)
|
return bytes.Equal(dveHash, evHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,3 +171,42 @@ func (e MockBadEvidence) Equal(ev Evidence) bool {
|
||||||
func (e MockBadEvidence) String() string {
|
func (e MockBadEvidence) String() string {
|
||||||
return fmt.Sprintf("BadEvidence: %d/%s/%d", e.Height_, e.Address_, e.Index_)
|
return fmt.Sprintf("BadEvidence: %d/%s/%d", e.Height_, e.Address_, e.Index_)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------
|
||||||
|
|
||||||
|
// EvidenceList is a list of Evidence. Evidences is not a word.
|
||||||
|
type EvidenceList []Evidence
|
||||||
|
|
||||||
|
// Hash returns the simple merkle root hash of the EvidenceList.
|
||||||
|
func (evl EvidenceList) Hash() []byte {
|
||||||
|
// Recursive impl.
|
||||||
|
// Copied from tmlibs/merkle to avoid allocations
|
||||||
|
switch len(evl) {
|
||||||
|
case 0:
|
||||||
|
return nil
|
||||||
|
case 1:
|
||||||
|
return evl[0].Hash()
|
||||||
|
default:
|
||||||
|
left := EvidenceList(evl[:(len(evl)+1)/2]).Hash()
|
||||||
|
right := EvidenceList(evl[(len(evl)+1)/2:]).Hash()
|
||||||
|
return merkle.SimpleHashFromTwoHashes(left, right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (evl EvidenceList) String() string {
|
||||||
|
s := ""
|
||||||
|
for _, e := range evl {
|
||||||
|
s += fmt.Sprintf("%s\t\t", e)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has returns true if the evidence is in the EvidenceList.
|
||||||
|
func (evl EvidenceList) Has(evidence Evidence) bool {
|
||||||
|
for _, ev := range evl {
|
||||||
|
if ev.Equal(evidence) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type voteData struct {
|
type voteData struct {
|
||||||
|
@ -13,25 +12,25 @@ type voteData struct {
|
||||||
valid bool
|
valid bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeVote(val *PrivValidatorFS, 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 {
|
||||||
v := &Vote{
|
v := &Vote{
|
||||||
ValidatorAddress: val.PubKey.Address(),
|
ValidatorAddress: val.GetAddress(),
|
||||||
ValidatorIndex: valIndex,
|
ValidatorIndex: valIndex,
|
||||||
Height: height,
|
Height: height,
|
||||||
Round: round,
|
Round: round,
|
||||||
Type: byte(step),
|
Type: byte(step),
|
||||||
BlockID: blockID,
|
BlockID: blockID,
|
||||||
}
|
}
|
||||||
sig := val.PrivKey.Sign(v.SignBytes(chainID))
|
err := val.SignVote(chainID, v)
|
||||||
v.Signature = sig
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
return v
|
return v
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEvidence(t *testing.T) {
|
func TestEvidence(t *testing.T) {
|
||||||
_, tmpFilePath := cmn.Tempfile("priv_validator_")
|
val := NewMockPV()
|
||||||
val := GenPrivValidatorFS(tmpFilePath)
|
val2 := NewMockPV()
|
||||||
val2 := GenPrivValidatorFS(tmpFilePath)
|
|
||||||
blockID := makeBlockID("blockhash", 1000, "partshash")
|
blockID := makeBlockID("blockhash", 1000, "partshash")
|
||||||
blockID2 := makeBlockID("blockhash2", 1000, "partshash")
|
blockID2 := makeBlockID("blockhash2", 1000, "partshash")
|
||||||
blockID3 := makeBlockID("blockhash", 10000, "partshash")
|
blockID3 := makeBlockID("blockhash", 10000, "partshash")
|
||||||
|
@ -41,7 +40,10 @@ func TestEvidence(t *testing.T) {
|
||||||
|
|
||||||
vote1 := makeVote(val, chainID, 0, 10, 2, 1, blockID)
|
vote1 := makeVote(val, chainID, 0, 10, 2, 1, blockID)
|
||||||
badVote := makeVote(val, chainID, 0, 10, 2, 1, blockID)
|
badVote := makeVote(val, chainID, 0, 10, 2, 1, blockID)
|
||||||
badVote.Signature = val2.PrivKey.Sign(badVote.SignBytes(chainID))
|
err := val2.SignVote(chainID, badVote)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
cases := []voteData{
|
cases := []voteData{
|
||||||
{vote1, makeVote(val, chainID, 0, 10, 2, 1, blockID2), true}, // different block ids
|
{vote1, makeVote(val, chainID, 0, 10, 2, 1, blockID2), true}, // different block ids
|
||||||
|
@ -59,7 +61,7 @@ func TestEvidence(t *testing.T) {
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
ev := &DuplicateVoteEvidence{
|
ev := &DuplicateVoteEvidence{
|
||||||
PubKey: val.PubKey,
|
PubKey: val.GetPubKey(),
|
||||||
VoteA: c.vote1,
|
VoteA: c.vote1,
|
||||||
VoteB: c.vote2,
|
VoteB: c.vote2,
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/tendermint/go-crypto"
|
||||||
|
|
||||||
crypto "github.com/tendermint/go-crypto"
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -33,7 +31,7 @@ type GenesisDoc struct {
|
||||||
|
|
||||||
// SaveAs is a utility method for saving GenensisDoc as a JSON file.
|
// SaveAs is a utility method for saving GenensisDoc as a JSON file.
|
||||||
func (genDoc *GenesisDoc) SaveAs(file string) error {
|
func (genDoc *GenesisDoc) SaveAs(file string) error {
|
||||||
genDocBytes, err := json.Marshal(genDoc)
|
genDocBytes, err := cdc.MarshalJSON(genDoc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -55,7 +53,7 @@ func (genDoc *GenesisDoc) ValidatorHash() []byte {
|
||||||
func (genDoc *GenesisDoc) ValidateAndComplete() error {
|
func (genDoc *GenesisDoc) ValidateAndComplete() error {
|
||||||
|
|
||||||
if genDoc.ChainID == "" {
|
if genDoc.ChainID == "" {
|
||||||
return errors.Errorf("Genesis doc must include non-empty chain_id")
|
return cmn.NewError("Genesis doc must include non-empty chain_id")
|
||||||
}
|
}
|
||||||
|
|
||||||
if genDoc.ConsensusParams == nil {
|
if genDoc.ConsensusParams == nil {
|
||||||
|
@ -67,12 +65,12 @@ func (genDoc *GenesisDoc) ValidateAndComplete() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(genDoc.Validators) == 0 {
|
if len(genDoc.Validators) == 0 {
|
||||||
return errors.Errorf("The genesis file must have at least one validator")
|
return cmn.NewError("The genesis file must have at least one validator")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range genDoc.Validators {
|
for _, v := range genDoc.Validators {
|
||||||
if v.Power == 0 {
|
if v.Power == 0 {
|
||||||
return errors.Errorf("The genesis file cannot contain validators with no voting power: %v", v)
|
return cmn.NewError("The genesis file cannot contain validators with no voting power: %v", v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +87,7 @@ func (genDoc *GenesisDoc) ValidateAndComplete() error {
|
||||||
// GenesisDocFromJSON unmarshalls JSON data into a GenesisDoc.
|
// GenesisDocFromJSON unmarshalls JSON data into a GenesisDoc.
|
||||||
func GenesisDocFromJSON(jsonBlob []byte) (*GenesisDoc, error) {
|
func GenesisDocFromJSON(jsonBlob []byte) (*GenesisDoc, error) {
|
||||||
genDoc := GenesisDoc{}
|
genDoc := GenesisDoc{}
|
||||||
err := json.Unmarshal(jsonBlob, &genDoc)
|
err := cdc.UnmarshalJSON(jsonBlob, &genDoc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -105,11 +103,11 @@ func GenesisDocFromJSON(jsonBlob []byte) (*GenesisDoc, error) {
|
||||||
func GenesisDocFromFile(genDocFile string) (*GenesisDoc, error) {
|
func GenesisDocFromFile(genDocFile string) (*GenesisDoc, error) {
|
||||||
jsonBlob, err := ioutil.ReadFile(genDocFile)
|
jsonBlob, err := ioutil.ReadFile(genDocFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "Couldn't read GenesisDoc file")
|
return nil, cmn.ErrorWrap(err, "Couldn't read GenesisDoc file")
|
||||||
}
|
}
|
||||||
genDoc, err := GenesisDocFromJSON(jsonBlob)
|
genDoc, err := GenesisDocFromJSON(jsonBlob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, cmn.Fmt("Error reading GenesisDoc at %v", genDocFile))
|
return nil, cmn.ErrorWrap(err, cmn.Fmt("Error reading GenesisDoc at %v", genDocFile))
|
||||||
}
|
}
|
||||||
return genDoc, nil
|
return genDoc, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,34 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tendermint/go-crypto"
|
||||||
crypto "github.com/tendermint/go-crypto"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGenesis(t *testing.T) {
|
func TestGenesisBad(t *testing.T) {
|
||||||
// test some bad ones from raw json
|
// test some bad ones from raw json
|
||||||
testCases := [][]byte{
|
testCases := [][]byte{
|
||||||
[]byte{}, // empty
|
[]byte{}, // empty
|
||||||
[]byte{1, 1, 1, 1, 1}, // junk
|
[]byte{1, 1, 1, 1, 1}, // junk
|
||||||
[]byte(`{}`), // empty
|
[]byte(`{}`), // empty
|
||||||
[]byte(`{"chain_id": "mychain"}`), // missing validators
|
[]byte(`{"chain_id":"mychain"}`), // missing validators
|
||||||
[]byte(`{"chain_id": "mychain", "validators": []`), // missing validators
|
[]byte(`{"chain_id":"mychain","validators":[]}`), // missing validators
|
||||||
[]byte(`{"chain_id": "mychain", "validators": [{}]`), // missing validators
|
[]byte(`{"chain_id":"mychain","validators":[{}]}`), // missing validators
|
||||||
[]byte(`{"validators":[{"pub_key":
|
[]byte(`{"chain_id":"mychain","validators":null}`), // missing validators
|
||||||
{"type":"ed25519","data":"961EAB8752E51A03618502F55C2B6E09C38C65635C64CCF3173ED452CF86C957"},
|
[]byte(`{"chain_id":"mychain"}`), // missing validators
|
||||||
"power":10,"name":""}]}`), // missing chain_id
|
[]byte(`{"validators":[{"pub_key":{"type":"AC26791624DE60","value":"AT/+aaL1eB0477Mud9JMm8Sh8BIvOYlPGC9KkIUmFaE="},"power":10,"name":""}]}`), // missing chain_id
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
_, err := GenesisDocFromJSON(testCase)
|
_, err := GenesisDocFromJSON(testCase)
|
||||||
assert.Error(t, err, "expected error for empty genDoc json")
|
assert.Error(t, err, "expected error for empty genDoc json")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenesisGood(t *testing.T) {
|
||||||
// test a good one by raw json
|
// test a good one by raw json
|
||||||
genDocBytes := []byte(`{"genesis_time":"0001-01-01T00:00:00Z","chain_id":"test-chain-QDKdJr","consensus_params":null,"validators":[{"pub_key":{"type":"ed25519","data":"961EAB8752E51A03618502F55C2B6E09C38C65635C64CCF3173ED452CF86C957"},"power":10,"name":""}],"app_hash":"","app_state":{"account_owner": "Bob"}}`)
|
genDocBytes := []byte(`{"genesis_time":"0001-01-01T00:00:00Z","chain_id":"test-chain-QDKdJr","consensus_params":null,"validators":[{"pub_key":{"type":"AC26791624DE60","value":"AT/+aaL1eB0477Mud9JMm8Sh8BIvOYlPGC9KkIUmFaE="},"power":10,"name":""}],"app_hash":"","app_state":{"account_owner": "Bob"}}`)
|
||||||
_, err := GenesisDocFromJSON(genDocBytes)
|
_, err := GenesisDocFromJSON(genDocBytes)
|
||||||
assert.NoError(t, err, "expected no error for good genDoc json")
|
assert.NoError(t, err, "expected no error for good genDoc json")
|
||||||
|
|
||||||
|
@ -38,7 +37,7 @@ func TestGenesis(t *testing.T) {
|
||||||
ChainID: "abc",
|
ChainID: "abc",
|
||||||
Validators: []GenesisValidator{{crypto.GenPrivKeyEd25519().PubKey(), 10, "myval"}},
|
Validators: []GenesisValidator{{crypto.GenPrivKeyEd25519().PubKey(), 10, "myval"}},
|
||||||
}
|
}
|
||||||
genDocBytes, err = json.Marshal(baseGenDoc)
|
genDocBytes, err = cdc.MarshalJSON(baseGenDoc)
|
||||||
assert.NoError(t, err, "error marshalling genDoc")
|
assert.NoError(t, err, "error marshalling genDoc")
|
||||||
|
|
||||||
// test base gendoc and check consensus params were filled
|
// test base gendoc and check consensus params were filled
|
||||||
|
@ -47,14 +46,14 @@ func TestGenesis(t *testing.T) {
|
||||||
assert.NotNil(t, genDoc.ConsensusParams, "expected consensus params to be filled in")
|
assert.NotNil(t, genDoc.ConsensusParams, "expected consensus params to be filled in")
|
||||||
|
|
||||||
// create json with consensus params filled
|
// create json with consensus params filled
|
||||||
genDocBytes, err = json.Marshal(genDoc)
|
genDocBytes, err = cdc.MarshalJSON(genDoc)
|
||||||
assert.NoError(t, err, "error marshalling genDoc")
|
assert.NoError(t, err, "error marshalling genDoc")
|
||||||
genDoc, err = GenesisDocFromJSON(genDocBytes)
|
genDoc, err = GenesisDocFromJSON(genDocBytes)
|
||||||
assert.NoError(t, err, "expected no error for valid genDoc json")
|
assert.NoError(t, err, "expected no error for valid genDoc json")
|
||||||
|
|
||||||
// test with invalid consensus params
|
// test with invalid consensus params
|
||||||
genDoc.ConsensusParams.BlockSize.MaxBytes = 0
|
genDoc.ConsensusParams.BlockSize.MaxBytes = 0
|
||||||
genDocBytes, err = json.Marshal(genDoc)
|
genDocBytes, err = cdc.MarshalJSON(genDoc)
|
||||||
assert.NoError(t, err, "error marshalling genDoc")
|
assert.NoError(t, err, "error marshalling genDoc")
|
||||||
genDoc, err = GenesisDocFromJSON(genDocBytes)
|
genDoc, err = GenesisDocFromJSON(genDocBytes)
|
||||||
assert.Error(t, err, "expected error for genDoc json with block size of 0")
|
assert.Error(t, err, "expected error for genDoc json with block size of 0")
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/tendermint/go-crypto"
|
"github.com/tendermint/go-crypto"
|
||||||
"github.com/tendermint/tendermint/wire"
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,7 +13,7 @@ import (
|
||||||
// json field tags because we always want the JSON
|
// json field tags because we always want the JSON
|
||||||
// representation to be in its canonical form.
|
// representation to be in its canonical form.
|
||||||
type Heartbeat struct {
|
type Heartbeat struct {
|
||||||
ValidatorAddress Address `json:"validator_address"`
|
ValidatorAddress Address `json:"validator_address"`
|
||||||
ValidatorIndex int `json:"validator_index"`
|
ValidatorIndex int `json:"validator_index"`
|
||||||
Height int64 `json:"height"`
|
Height int64 `json:"height"`
|
||||||
Round int `json:"round"`
|
Round int `json:"round"`
|
||||||
|
@ -25,10 +24,7 @@ type Heartbeat struct {
|
||||||
// SignBytes returns the Heartbeat bytes for signing.
|
// SignBytes returns the Heartbeat bytes for signing.
|
||||||
// It panics if the Heartbeat is nil.
|
// It panics if the Heartbeat is nil.
|
||||||
func (heartbeat *Heartbeat) SignBytes(chainID string) []byte {
|
func (heartbeat *Heartbeat) SignBytes(chainID string) []byte {
|
||||||
bz, err := wire.MarshalJSON(CanonicalJSONOnceHeartbeat{
|
bz, err := cdc.MarshalJSON(CanonicalHeartbeat(chainID, heartbeat))
|
||||||
chainID,
|
|
||||||
CanonicalHeartbeat(heartbeat),
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,22 +25,23 @@ func TestHeartbeatString(t *testing.T) {
|
||||||
require.Contains(t, nilHb.String(), "nil", "expecting a string and no panic")
|
require.Contains(t, nilHb.String(), "nil", "expecting a string and no panic")
|
||||||
|
|
||||||
hb := &Heartbeat{ValidatorIndex: 1, Height: 11, Round: 2}
|
hb := &Heartbeat{ValidatorIndex: 1, Height: 11, Round: 2}
|
||||||
require.Equal(t, hb.String(), "Heartbeat{1:000000000000 11/02 (0) {<nil>}}")
|
require.Equal(t, hb.String(), "Heartbeat{1:000000000000 11/02 (0) <nil>}")
|
||||||
|
|
||||||
var key crypto.PrivKeyEd25519
|
var key crypto.PrivKeyEd25519
|
||||||
hb.Signature = key.Sign([]byte("Tendermint"))
|
hb.Signature = key.Sign([]byte("Tendermint"))
|
||||||
require.Equal(t, hb.String(), "Heartbeat{1:000000000000 11/02 (0) {/FF41E371B9BF.../}}")
|
require.Equal(t, hb.String(), "Heartbeat{1:000000000000 11/02 (0) /FF41E371B9BF.../}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHeartbeatWriteSignBytes(t *testing.T) {
|
func TestHeartbeatWriteSignBytes(t *testing.T) {
|
||||||
|
|
||||||
hb := &Heartbeat{ValidatorIndex: 1, Height: 10, Round: 1}
|
hb := &Heartbeat{ValidatorIndex: 1, Height: 10, Round: 1}
|
||||||
bz := hb.SignBytes("0xdeadbeef")
|
bz := hb.SignBytes("0xdeadbeef")
|
||||||
require.Equal(t, string(bz), `{"chain_id":"0xdeadbeef","heartbeat":{"height":10,"round":1,"sequence":0,"validator_address":"","validator_index":1}}`)
|
// XXX HMMMMMMM
|
||||||
|
require.Equal(t, string(bz), `{"@chain_id":"0xdeadbeef","@type":"heartbeat","height":10,"round":1,"sequence":0,"validator_address":"","validator_index":1}`)
|
||||||
|
|
||||||
plainHb := &Heartbeat{}
|
plainHb := &Heartbeat{}
|
||||||
bz = plainHb.SignBytes("0xdeadbeef")
|
bz = plainHb.SignBytes("0xdeadbeef")
|
||||||
require.Equal(t, string(bz), `{"chain_id":"0xdeadbeef","heartbeat":{"height":0,"round":0,"sequence":0,"validator_address":"","validator_index":0}}`)
|
require.Equal(t, string(bz), `{"@chain_id":"0xdeadbeef","@type":"heartbeat","height":0,"round":0,"sequence":0,"validator_address":"","validator_index":0}`)
|
||||||
|
|
||||||
require.Panics(t, func() {
|
require.Panics(t, func() {
|
||||||
var nilHb *Heartbeat
|
var nilHb *Heartbeat
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
abci "github.com/tendermint/abci/types"
|
abci "github.com/tendermint/abci/types"
|
||||||
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
"github.com/tendermint/tmlibs/merkle"
|
"github.com/tendermint/tmlibs/merkle"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -89,15 +88,15 @@ func DefaultEvidenceParams() EvidenceParams {
|
||||||
func (params *ConsensusParams) Validate() error {
|
func (params *ConsensusParams) Validate() error {
|
||||||
// ensure some values are greater than 0
|
// ensure some values are greater than 0
|
||||||
if params.BlockSize.MaxBytes <= 0 {
|
if params.BlockSize.MaxBytes <= 0 {
|
||||||
return errors.Errorf("BlockSize.MaxBytes must be greater than 0. Got %d", params.BlockSize.MaxBytes)
|
return cmn.NewError("BlockSize.MaxBytes must be greater than 0. Got %d", params.BlockSize.MaxBytes)
|
||||||
}
|
}
|
||||||
if params.BlockGossip.BlockPartSizeBytes <= 0 {
|
if params.BlockGossip.BlockPartSizeBytes <= 0 {
|
||||||
return errors.Errorf("BlockGossip.BlockPartSizeBytes must be greater than 0. Got %d", params.BlockGossip.BlockPartSizeBytes)
|
return cmn.NewError("BlockGossip.BlockPartSizeBytes must be greater than 0. Got %d", params.BlockGossip.BlockPartSizeBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure blocks aren't too big
|
// ensure blocks aren't too big
|
||||||
if params.BlockSize.MaxBytes > maxBlockSizeBytes {
|
if params.BlockSize.MaxBytes > maxBlockSizeBytes {
|
||||||
return errors.Errorf("BlockSize.MaxBytes is too big. %d > %d",
|
return cmn.NewError("BlockSize.MaxBytes is too big. %d > %d",
|
||||||
params.BlockSize.MaxBytes, maxBlockSizeBytes)
|
params.BlockSize.MaxBytes, maxBlockSizeBytes)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -107,12 +106,12 @@ func (params *ConsensusParams) Validate() error {
|
||||||
// in the block header
|
// in the block header
|
||||||
func (params *ConsensusParams) Hash() []byte {
|
func (params *ConsensusParams) Hash() []byte {
|
||||||
return merkle.SimpleHashFromMap(map[string]merkle.Hasher{
|
return merkle.SimpleHashFromMap(map[string]merkle.Hasher{
|
||||||
"block_gossip_part_size_bytes": wireHasher(params.BlockGossip.BlockPartSizeBytes),
|
"block_gossip_part_size_bytes": aminoHasher(params.BlockGossip.BlockPartSizeBytes),
|
||||||
"block_size_max_bytes": wireHasher(params.BlockSize.MaxBytes),
|
"block_size_max_bytes": aminoHasher(params.BlockSize.MaxBytes),
|
||||||
"block_size_max_gas": wireHasher(params.BlockSize.MaxGas),
|
"block_size_max_gas": aminoHasher(params.BlockSize.MaxGas),
|
||||||
"block_size_max_txs": wireHasher(params.BlockSize.MaxTxs),
|
"block_size_max_txs": aminoHasher(params.BlockSize.MaxTxs),
|
||||||
"tx_size_max_bytes": wireHasher(params.TxSize.MaxBytes),
|
"tx_size_max_bytes": aminoHasher(params.TxSize.MaxBytes),
|
||||||
"tx_size_max_gas": wireHasher(params.TxSize.MaxGas),
|
"tx_size_max_gas": aminoHasher(params.TxSize.MaxGas),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,88 +2,11 @@ package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
crypto "github.com/tendermint/go-crypto"
|
"github.com/tendermint/go-crypto"
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: type ?
|
|
||||||
const (
|
|
||||||
stepNone int8 = 0 // Used to distinguish the initial state
|
|
||||||
stepPropose int8 = 1
|
|
||||||
stepPrevote int8 = 2
|
|
||||||
stepPrecommit int8 = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
func voteToStep(vote *Vote) int8 {
|
|
||||||
switch vote.Type {
|
|
||||||
case VoteTypePrevote:
|
|
||||||
return stepPrevote
|
|
||||||
case VoteTypePrecommit:
|
|
||||||
return stepPrecommit
|
|
||||||
default:
|
|
||||||
cmn.PanicSanity("Unknown vote type")
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//--------------------------------------------------------------
|
|
||||||
// PrivValidator is being upgraded! See types/priv_validator/
|
|
||||||
|
|
||||||
// ValidatorID contains the identity of the validator.
|
|
||||||
type ValidatorID struct {
|
|
||||||
Address cmn.HexBytes `json:"address"`
|
|
||||||
PubKey crypto.PubKey `json:"pub_key"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrivValidator defines the functionality of a local Tendermint validator
|
|
||||||
// that signs votes, proposals, and heartbeats, and never double signs.
|
|
||||||
type PrivValidator2 interface {
|
|
||||||
Address() (Address, error) // redundant since .PubKey().Address()
|
|
||||||
PubKey() (crypto.PubKey, error)
|
|
||||||
|
|
||||||
SignVote(chainID string, vote *Vote) error
|
|
||||||
SignProposal(chainID string, proposal *Proposal) error
|
|
||||||
SignHeartbeat(chainID string, heartbeat *Heartbeat) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestSigner interface {
|
|
||||||
Address() cmn.HexBytes
|
|
||||||
PubKey() crypto.PubKey
|
|
||||||
Sign([]byte) (crypto.Signature, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GenSigner() TestSigner {
|
|
||||||
return &DefaultTestSigner{
|
|
||||||
crypto.GenPrivKeyEd25519().Wrap(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultTestSigner struct {
|
|
||||||
crypto.PrivKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ds *DefaultTestSigner) Address() cmn.HexBytes {
|
|
||||||
return ds.PubKey().Address()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ds *DefaultTestSigner) PubKey() crypto.PubKey {
|
|
||||||
return ds.PrivKey.PubKey()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ds *DefaultTestSigner) Sign(msg []byte) (crypto.Signature, error) {
|
|
||||||
return ds.PrivKey.Sign(msg), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//--------------------------------------------------------------
|
|
||||||
// TODO: Deprecate!
|
|
||||||
|
|
||||||
// PrivValidator defines the functionality of a local Tendermint validator
|
// PrivValidator defines the functionality of a local Tendermint validator
|
||||||
// that signs votes, proposals, and heartbeats, and never double signs.
|
// that signs votes, proposals, and heartbeats, and never double signs.
|
||||||
type PrivValidator interface {
|
type PrivValidator interface {
|
||||||
|
@ -95,313 +18,10 @@ type PrivValidator interface {
|
||||||
SignHeartbeat(chainID string, heartbeat *Heartbeat) error
|
SignHeartbeat(chainID string, heartbeat *Heartbeat) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrivValidatorFS implements PrivValidator using data persisted to disk
|
//----------------------------------------
|
||||||
// to prevent double signing. The Signer itself can be mutated to use
|
// Misc.
|
||||||
// something besides the default, for instance a hardware signer.
|
|
||||||
// NOTE: the directory containing the privVal.filePath must already exist.
|
|
||||||
type PrivValidatorFS struct {
|
|
||||||
Address 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 crypto.Signature `json:"last_signature,omitempty"` // so we dont lose signatures
|
|
||||||
LastSignBytes cmn.HexBytes `json:"last_signbytes,omitempty"` // so we dont lose signatures
|
|
||||||
|
|
||||||
// PrivKey should be empty if a Signer other than the default is being used.
|
type PrivValidatorsByAddress []PrivValidator
|
||||||
PrivKey crypto.PrivKey `json:"priv_key"`
|
|
||||||
Signer `json:"-"`
|
|
||||||
|
|
||||||
// For persistence.
|
|
||||||
// Overloaded for testing.
|
|
||||||
filePath string
|
|
||||||
mtx sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signer is an interface that defines how to sign messages.
|
|
||||||
// It is the caller's duty to verify the msg before calling Sign,
|
|
||||||
// eg. to avoid double signing.
|
|
||||||
// Currently, the only callers are SignVote, SignProposal, and SignHeartbeat.
|
|
||||||
type Signer interface {
|
|
||||||
Sign(msg []byte) (crypto.Signature, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultSigner implements Signer.
|
|
||||||
// It uses a standard, unencrypted crypto.PrivKey.
|
|
||||||
type DefaultSigner struct {
|
|
||||||
PrivKey crypto.PrivKey `json:"priv_key"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDefaultSigner returns an instance of DefaultSigner.
|
|
||||||
func NewDefaultSigner(priv crypto.PrivKey) *DefaultSigner {
|
|
||||||
return &DefaultSigner{
|
|
||||||
PrivKey: priv,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign implements Signer. It signs the byte slice with a private key.
|
|
||||||
func (ds *DefaultSigner) Sign(msg []byte) (crypto.Signature, error) {
|
|
||||||
return ds.PrivKey.Sign(msg), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAddress returns the address of the validator.
|
|
||||||
// Implements PrivValidator.
|
|
||||||
func (pv *PrivValidatorFS) GetAddress() Address {
|
|
||||||
return pv.Address
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPubKey returns the public key of the validator.
|
|
||||||
// Implements PrivValidator.
|
|
||||||
func (pv *PrivValidatorFS) GetPubKey() crypto.PubKey {
|
|
||||||
return pv.PubKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenPrivValidatorFS generates a new validator with randomly generated private key
|
|
||||||
// and sets the filePath, but does not call Save().
|
|
||||||
func GenPrivValidatorFS(filePath string) *PrivValidatorFS {
|
|
||||||
privKey := crypto.GenPrivKeyEd25519().Wrap()
|
|
||||||
return &PrivValidatorFS{
|
|
||||||
Address: privKey.PubKey().Address(),
|
|
||||||
PubKey: privKey.PubKey(),
|
|
||||||
PrivKey: privKey,
|
|
||||||
LastStep: stepNone,
|
|
||||||
Signer: NewDefaultSigner(privKey),
|
|
||||||
filePath: filePath,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadPrivValidatorFS loads a PrivValidatorFS from the filePath.
|
|
||||||
func LoadPrivValidatorFS(filePath string) *PrivValidatorFS {
|
|
||||||
return LoadPrivValidatorFSWithSigner(filePath, func(privVal PrivValidator) Signer {
|
|
||||||
return NewDefaultSigner(privVal.(*PrivValidatorFS).PrivKey)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadOrGenPrivValidatorFS loads a PrivValidatorFS from the given filePath
|
|
||||||
// or else generates a new one and saves it to the filePath.
|
|
||||||
func LoadOrGenPrivValidatorFS(filePath string) *PrivValidatorFS {
|
|
||||||
var privVal *PrivValidatorFS
|
|
||||||
if cmn.FileExists(filePath) {
|
|
||||||
privVal = LoadPrivValidatorFS(filePath)
|
|
||||||
} else {
|
|
||||||
privVal = GenPrivValidatorFS(filePath)
|
|
||||||
privVal.Save()
|
|
||||||
}
|
|
||||||
return privVal
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadPrivValidatorWithSigner loads a PrivValidatorFS with a custom
|
|
||||||
// signer object. The PrivValidatorFS handles double signing prevention by persisting
|
|
||||||
// data to the filePath, while the Signer handles the signing.
|
|
||||||
// If the filePath does not exist, the PrivValidatorFS must be created manually and saved.
|
|
||||||
func LoadPrivValidatorFSWithSigner(filePath string, signerFunc func(PrivValidator) Signer) *PrivValidatorFS {
|
|
||||||
privValJSONBytes, err := ioutil.ReadFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
cmn.Exit(err.Error())
|
|
||||||
}
|
|
||||||
privVal := &PrivValidatorFS{}
|
|
||||||
err = json.Unmarshal(privValJSONBytes, &privVal)
|
|
||||||
if err != nil {
|
|
||||||
cmn.Exit(cmn.Fmt("Error reading PrivValidator from %v: %v\n", filePath, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
privVal.filePath = filePath
|
|
||||||
privVal.Signer = signerFunc(privVal)
|
|
||||||
return privVal
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save persists the PrivValidatorFS to disk.
|
|
||||||
func (privVal *PrivValidatorFS) Save() {
|
|
||||||
privVal.mtx.Lock()
|
|
||||||
defer privVal.mtx.Unlock()
|
|
||||||
privVal.save()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (privVal *PrivValidatorFS) save() {
|
|
||||||
outFile := privVal.filePath
|
|
||||||
if outFile == "" {
|
|
||||||
panic("Cannot save PrivValidator: filePath not set")
|
|
||||||
}
|
|
||||||
jsonBytes, err := json.Marshal(privVal)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset resets all fields in the PrivValidatorFS.
|
|
||||||
// NOTE: Unsafe!
|
|
||||||
func (privVal *PrivValidatorFS) Reset() {
|
|
||||||
var sig crypto.Signature
|
|
||||||
privVal.LastHeight = 0
|
|
||||||
privVal.LastRound = 0
|
|
||||||
privVal.LastStep = 0
|
|
||||||
privVal.LastSignature = sig
|
|
||||||
privVal.LastSignBytes = nil
|
|
||||||
privVal.Save()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignVote signs a canonical representation of the vote, along with the
|
|
||||||
// chainID. Implements PrivValidator.
|
|
||||||
func (privVal *PrivValidatorFS) SignVote(chainID string, vote *Vote) error {
|
|
||||||
privVal.mtx.Lock()
|
|
||||||
defer privVal.mtx.Unlock()
|
|
||||||
if err := privVal.signVote(chainID, vote); err != nil {
|
|
||||||
return errors.New(cmn.Fmt("Error signing vote: %v", err))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignProposal signs a canonical representation of the proposal, along with
|
|
||||||
// the chainID. Implements PrivValidator.
|
|
||||||
func (privVal *PrivValidatorFS) SignProposal(chainID string, proposal *Proposal) error {
|
|
||||||
privVal.mtx.Lock()
|
|
||||||
defer privVal.mtx.Unlock()
|
|
||||||
if err := privVal.signProposal(chainID, proposal); err != nil {
|
|
||||||
return fmt.Errorf("Error signing proposal: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns error if HRS regression or no LastSignBytes. returns true if HRS is unchanged
|
|
||||||
func (privVal *PrivValidatorFS) checkHRS(height int64, round int, step int8) (bool, error) {
|
|
||||||
if privVal.LastHeight > height {
|
|
||||||
return false, errors.New("Height regression")
|
|
||||||
}
|
|
||||||
|
|
||||||
if privVal.LastHeight == height {
|
|
||||||
if privVal.LastRound > round {
|
|
||||||
return false, errors.New("Round regression")
|
|
||||||
}
|
|
||||||
|
|
||||||
if privVal.LastRound == round {
|
|
||||||
if privVal.LastStep > step {
|
|
||||||
return false, errors.New("Step regression")
|
|
||||||
} else if privVal.LastStep == step {
|
|
||||||
if privVal.LastSignBytes != nil {
|
|
||||||
if privVal.LastSignature.Empty() {
|
|
||||||
panic("privVal: LastSignature is nil but LastSignBytes is not!")
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, errors.New("No LastSignature found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// signVote checks if the vote is good to sign and sets the vote signature.
|
|
||||||
// It may need to set the timestamp as well if the vote is otherwise the same as
|
|
||||||
// a previously signed vote (ie. we crashed after signing but before the vote hit the WAL).
|
|
||||||
func (privVal *PrivValidatorFS) signVote(chainID string, vote *Vote) error {
|
|
||||||
height, round, step := vote.Height, vote.Round, voteToStep(vote)
|
|
||||||
signBytes := vote.SignBytes(chainID)
|
|
||||||
|
|
||||||
sameHRS, err := privVal.checkHRS(height, round, step)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We might crash before writing to the wal,
|
|
||||||
// causing us to try to re-sign for the same HRS.
|
|
||||||
// If signbytes are the same, use the last signature.
|
|
||||||
// If they only differ by timestamp, use last timestamp and signature
|
|
||||||
// Otherwise, return error
|
|
||||||
if sameHRS {
|
|
||||||
if bytes.Equal(signBytes, privVal.LastSignBytes) {
|
|
||||||
vote.Signature = privVal.LastSignature
|
|
||||||
} else if timestamp, ok := checkVotesOnlyDifferByTimestamp(privVal.LastSignBytes, signBytes); ok {
|
|
||||||
vote.Timestamp = timestamp
|
|
||||||
vote.Signature = privVal.LastSignature
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("Conflicting data")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// It passed the checks. Sign the vote
|
|
||||||
sig, err := privVal.Sign(signBytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
privVal.saveSigned(height, round, step, signBytes, sig)
|
|
||||||
vote.Signature = sig
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// signProposal checks if the proposal is good to sign and sets the proposal signature.
|
|
||||||
// It may need to set the timestamp as well if the proposal is otherwise the same as
|
|
||||||
// a previously signed proposal ie. we crashed after signing but before the proposal hit the WAL).
|
|
||||||
func (privVal *PrivValidatorFS) signProposal(chainID string, proposal *Proposal) error {
|
|
||||||
height, round, step := proposal.Height, proposal.Round, stepPropose
|
|
||||||
signBytes := proposal.SignBytes(chainID)
|
|
||||||
|
|
||||||
sameHRS, err := privVal.checkHRS(height, round, step)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We might crash before writing to the wal,
|
|
||||||
// causing us to try to re-sign for the same HRS.
|
|
||||||
// If signbytes are the same, use the last signature.
|
|
||||||
// If they only differ by timestamp, use last timestamp and signature
|
|
||||||
// Otherwise, return error
|
|
||||||
if sameHRS {
|
|
||||||
if bytes.Equal(signBytes, privVal.LastSignBytes) {
|
|
||||||
proposal.Signature = privVal.LastSignature
|
|
||||||
} else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(privVal.LastSignBytes, signBytes); ok {
|
|
||||||
proposal.Timestamp = timestamp
|
|
||||||
proposal.Signature = privVal.LastSignature
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("Conflicting data")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// It passed the checks. Sign the proposal
|
|
||||||
sig, err := privVal.Sign(signBytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
privVal.saveSigned(height, round, step, signBytes, sig)
|
|
||||||
proposal.Signature = sig
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Persist height/round/step and signature
|
|
||||||
func (privVal *PrivValidatorFS) saveSigned(height int64, round int, step int8,
|
|
||||||
signBytes []byte, sig crypto.Signature) {
|
|
||||||
|
|
||||||
privVal.LastHeight = height
|
|
||||||
privVal.LastRound = round
|
|
||||||
privVal.LastStep = step
|
|
||||||
privVal.LastSignature = sig
|
|
||||||
privVal.LastSignBytes = signBytes
|
|
||||||
privVal.save()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignHeartbeat signs a canonical representation of the heartbeat, along with the chainID.
|
|
||||||
// Implements PrivValidator.
|
|
||||||
func (privVal *PrivValidatorFS) SignHeartbeat(chainID string, heartbeat *Heartbeat) error {
|
|
||||||
privVal.mtx.Lock()
|
|
||||||
defer privVal.mtx.Unlock()
|
|
||||||
var err error
|
|
||||||
heartbeat.Signature, err = privVal.Sign(heartbeat.SignBytes(chainID))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a string representation of the PrivValidatorFS.
|
|
||||||
func (privVal *PrivValidatorFS) String() string {
|
|
||||||
return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", privVal.GetAddress(), privVal.LastHeight, privVal.LastRound, privVal.LastStep)
|
|
||||||
}
|
|
||||||
|
|
||||||
//-------------------------------------
|
|
||||||
|
|
||||||
type PrivValidatorsByAddress []*PrivValidatorFS
|
|
||||||
|
|
||||||
func (pvs PrivValidatorsByAddress) Len() int {
|
func (pvs PrivValidatorsByAddress) Len() int {
|
||||||
return len(pvs)
|
return len(pvs)
|
||||||
|
@ -417,56 +37,53 @@ func (pvs PrivValidatorsByAddress) Swap(i, j int) {
|
||||||
pvs[j] = it
|
pvs[j] = it
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------------------------------------
|
//----------------------------------------
|
||||||
|
// MockPV
|
||||||
|
|
||||||
// returns the timestamp from the lastSignBytes.
|
// MockPV implements PrivValidator without any safety or persistence.
|
||||||
// returns true if the only difference in the votes is their timestamp.
|
// Only use it for testing.
|
||||||
func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {
|
type MockPV struct {
|
||||||
var lastVote, newVote CanonicalJSONOnceVote
|
privKey crypto.PrivKey
|
||||||
if err := json.Unmarshal(lastSignBytes, &lastVote); err != nil {
|
|
||||||
panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into vote: %v", err))
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(newSignBytes, &newVote); err != nil {
|
|
||||||
panic(fmt.Sprintf("signBytes cannot be unmarshalled into vote: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
lastTime, err := time.Parse(TimeFormat, lastVote.Vote.Timestamp)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the times to the same value and check equality
|
|
||||||
now := CanonicalTime(time.Now())
|
|
||||||
lastVote.Vote.Timestamp = now
|
|
||||||
newVote.Vote.Timestamp = now
|
|
||||||
lastVoteBytes, _ := json.Marshal(lastVote)
|
|
||||||
newVoteBytes, _ := json.Marshal(newVote)
|
|
||||||
|
|
||||||
return lastTime, bytes.Equal(newVoteBytes, lastVoteBytes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns the timestamp from the lastSignBytes.
|
func NewMockPV() *MockPV {
|
||||||
// returns true if the only difference in the proposals is their timestamp
|
return &MockPV{crypto.GenPrivKeyEd25519()}
|
||||||
func checkProposalsOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {
|
}
|
||||||
var lastProposal, newProposal CanonicalJSONOnceProposal
|
|
||||||
if err := json.Unmarshal(lastSignBytes, &lastProposal); err != nil {
|
// Implements PrivValidator.
|
||||||
panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into proposal: %v", err))
|
func (pv *MockPV) GetAddress() Address {
|
||||||
}
|
return pv.privKey.PubKey().Address()
|
||||||
if err := json.Unmarshal(newSignBytes, &newProposal); err != nil {
|
}
|
||||||
panic(fmt.Sprintf("signBytes cannot be unmarshalled into proposal: %v", err))
|
|
||||||
}
|
// Implements PrivValidator.
|
||||||
|
func (pv *MockPV) GetPubKey() crypto.PubKey {
|
||||||
lastTime, err := time.Parse(TimeFormat, lastProposal.Proposal.Timestamp)
|
return pv.privKey.PubKey()
|
||||||
if err != nil {
|
}
|
||||||
panic(err)
|
|
||||||
}
|
// Implements PrivValidator.
|
||||||
|
func (pv *MockPV) SignVote(chainID string, vote *Vote) error {
|
||||||
// set the times to the same value and check equality
|
signBytes := vote.SignBytes(chainID)
|
||||||
now := CanonicalTime(time.Now())
|
sig := pv.privKey.Sign(signBytes)
|
||||||
lastProposal.Proposal.Timestamp = now
|
vote.Signature = sig
|
||||||
newProposal.Proposal.Timestamp = now
|
return nil
|
||||||
lastProposalBytes, _ := json.Marshal(lastProposal)
|
}
|
||||||
newProposalBytes, _ := json.Marshal(newProposal)
|
|
||||||
|
// Implements PrivValidator.
|
||||||
return lastTime, bytes.Equal(newProposalBytes, lastProposalBytes)
|
func (pv *MockPV) SignProposal(chainID string, proposal *Proposal) error {
|
||||||
|
signBytes := proposal.SignBytes(chainID)
|
||||||
|
sig := pv.privKey.Sign(signBytes)
|
||||||
|
proposal.Signature = sig
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// signHeartbeat signs the heartbeat without any checking.
|
||||||
|
func (pv *MockPV) SignHeartbeat(chainID string, heartbeat *Heartbeat) error {
|
||||||
|
sig := pv.privKey.Sign(heartbeat.SignBytes(chainID))
|
||||||
|
heartbeat.Signature = sig
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the MockPV.
|
||||||
|
func (pv *MockPV) String() string {
|
||||||
|
return fmt.Sprintf("MockPV{%v}", pv.GetAddress())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,197 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
crypto "github.com/tendermint/go-crypto"
|
|
||||||
"github.com/tendermint/tendermint/types"
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PrivValidator aliases types.PrivValidator
|
|
||||||
type PrivValidator = types.PrivValidator2
|
|
||||||
|
|
||||||
//-----------------------------------------------------
|
|
||||||
|
|
||||||
// PrivKey implements Signer
|
|
||||||
type PrivKey crypto.PrivKey
|
|
||||||
|
|
||||||
// Sign - Implements Signer
|
|
||||||
func (pk PrivKey) Sign(msg []byte) (crypto.Signature, error) {
|
|
||||||
return crypto.PrivKey(pk).Sign(msg), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON satisfies json.Marshaler.
|
|
||||||
func (pk PrivKey) MarshalJSON() ([]byte, error) {
|
|
||||||
return crypto.PrivKey(pk).MarshalJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON satisfies json.Unmarshaler.
|
|
||||||
func (pk *PrivKey) UnmarshalJSON(b []byte) error {
|
|
||||||
cpk := new(crypto.PrivKey)
|
|
||||||
if err := cpk.UnmarshalJSON(b); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*pk = (PrivKey)(*cpk)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------
|
|
||||||
|
|
||||||
var _ types.PrivValidator2 = (*PrivValidatorJSON)(nil)
|
|
||||||
|
|
||||||
// PrivValidatorJSON wraps PrivValidatorUnencrypted
|
|
||||||
// and persists it to disk after every SignVote and SignProposal.
|
|
||||||
type PrivValidatorJSON struct {
|
|
||||||
*PrivValidatorUnencrypted
|
|
||||||
|
|
||||||
filePath string
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignVote implements PrivValidator. It persists to disk.
|
|
||||||
func (pvj *PrivValidatorJSON) SignVote(chainID string, vote *types.Vote) error {
|
|
||||||
err := pvj.PrivValidatorUnencrypted.SignVote(chainID, vote)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pvj.Save()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignProposal implements PrivValidator. It persists to disk.
|
|
||||||
func (pvj *PrivValidatorJSON) SignProposal(chainID string, proposal *types.Proposal) error {
|
|
||||||
err := pvj.PrivValidatorUnencrypted.SignProposal(chainID, proposal)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pvj.Save()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//-------------------------------------------------------
|
|
||||||
|
|
||||||
// String returns a string representation of the PrivValidatorJSON.
|
|
||||||
func (pvj *PrivValidatorJSON) String() string {
|
|
||||||
addr, err := pvj.Address()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("PrivValidator{%v %v}", addr, pvj.PrivValidatorUnencrypted.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save persists the PrivValidatorJSON to disk.
|
|
||||||
func (pvj *PrivValidatorJSON) Save() {
|
|
||||||
pvj.save()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pvj *PrivValidatorJSON) save() {
|
|
||||||
if pvj.filePath == "" {
|
|
||||||
panic("Cannot save PrivValidator: filePath not set")
|
|
||||||
}
|
|
||||||
jsonBytes, err := json.Marshal(pvj)
|
|
||||||
if err != nil {
|
|
||||||
// ; BOOM!!!
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
err = cmn.WriteFileAtomic(pvj.filePath, jsonBytes, 0600)
|
|
||||||
if err != nil {
|
|
||||||
// ; BOOM!!!
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset resets the PrivValidatorUnencrypted. Panics if the Signer is the wrong type.
|
|
||||||
// NOTE: Unsafe!
|
|
||||||
func (pvj *PrivValidatorJSON) Reset() {
|
|
||||||
pvj.PrivValidatorUnencrypted.LastSignedInfo.Reset()
|
|
||||||
pvj.Save()
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------
|
|
||||||
|
|
||||||
// GenPrivValidatorJSON generates a new validator with randomly generated private key
|
|
||||||
// and the given filePath. It does not persist to file.
|
|
||||||
func GenPrivValidatorJSON(filePath string) *PrivValidatorJSON {
|
|
||||||
privKey := crypto.GenPrivKeyEd25519().Wrap()
|
|
||||||
return &PrivValidatorJSON{
|
|
||||||
PrivValidatorUnencrypted: NewPrivValidatorUnencrypted(privKey),
|
|
||||||
filePath: filePath,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadPrivValidatorJSON loads a PrivValidatorJSON from the filePath.
|
|
||||||
func LoadPrivValidatorJSON(filePath string) *PrivValidatorJSON {
|
|
||||||
pvJSONBytes, err := ioutil.ReadFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
cmn.Exit(err.Error())
|
|
||||||
}
|
|
||||||
pvj := PrivValidatorJSON{}
|
|
||||||
err = json.Unmarshal(pvJSONBytes, &pvj)
|
|
||||||
if err != nil {
|
|
||||||
cmn.Exit(cmn.Fmt("Error reading PrivValidatorJSON from %v: %v\n", filePath, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// enable persistence
|
|
||||||
pvj.filePath = filePath
|
|
||||||
return &pvj
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadOrGenPrivValidatorJSON loads a PrivValidatorJSON from the given filePath
|
|
||||||
// or else generates a new one and saves it to the filePath.
|
|
||||||
func LoadOrGenPrivValidatorJSON(filePath string) *PrivValidatorJSON {
|
|
||||||
var pvj *PrivValidatorJSON
|
|
||||||
if cmn.FileExists(filePath) {
|
|
||||||
pvj = LoadPrivValidatorJSON(filePath)
|
|
||||||
} else {
|
|
||||||
pvj = GenPrivValidatorJSON(filePath)
|
|
||||||
pvj.Save()
|
|
||||||
}
|
|
||||||
return pvj
|
|
||||||
}
|
|
||||||
|
|
||||||
//--------------------------------------------------------------
|
|
||||||
|
|
||||||
// NewTestPrivValidator returns a PrivValidatorJSON with a tempfile
|
|
||||||
// for the file path.
|
|
||||||
func NewTestPrivValidator(signer types.TestSigner) *PrivValidatorJSON {
|
|
||||||
_, tempFilePath := cmn.Tempfile("priv_validator_")
|
|
||||||
pv := &PrivValidatorJSON{
|
|
||||||
PrivValidatorUnencrypted: NewPrivValidatorUnencrypted(signer.(*types.DefaultTestSigner).PrivKey),
|
|
||||||
filePath: tempFilePath,
|
|
||||||
}
|
|
||||||
return pv
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------
|
|
||||||
|
|
||||||
// PrivValidatorsByAddress is a list of PrivValidatorJSON ordered by their
|
|
||||||
// addresses.
|
|
||||||
type PrivValidatorsByAddress []*PrivValidatorJSON
|
|
||||||
|
|
||||||
func (pvs PrivValidatorsByAddress) Len() int {
|
|
||||||
return len(pvs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pvs PrivValidatorsByAddress) Less(i, j int) bool {
|
|
||||||
iaddr, err := pvs[j].Address()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
jaddr, err := pvs[i].Address()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytes.Compare(iaddr, jaddr) == -1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pvs PrivValidatorsByAddress) Swap(i, j int) {
|
|
||||||
it := pvs[i]
|
|
||||||
pvs[i] = pvs[j]
|
|
||||||
pvs[j] = it
|
|
||||||
}
|
|
|
@ -0,0 +1,345 @@
|
||||||
|
package privval
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tendermint/go-crypto"
|
||||||
|
"github.com/tendermint/tendermint/types"
|
||||||
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: type ?
|
||||||
|
const (
|
||||||
|
stepNone int8 = 0 // Used to distinguish the initial state
|
||||||
|
stepPropose int8 = 1
|
||||||
|
stepPrevote int8 = 2
|
||||||
|
stepPrecommit int8 = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
func voteToStep(vote *types.Vote) int8 {
|
||||||
|
switch vote.Type {
|
||||||
|
case types.VoteTypePrevote:
|
||||||
|
return stepPrevote
|
||||||
|
case types.VoteTypePrecommit:
|
||||||
|
return stepPrecommit
|
||||||
|
default:
|
||||||
|
cmn.PanicSanity("Unknown vote type")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilePV implements PrivValidator using data persisted to disk
|
||||||
|
// to prevent double signing.
|
||||||
|
// NOTE: the directory containing the pv.filePath must already exist.
|
||||||
|
type FilePV struct {
|
||||||
|
Address types.Address `json:"address"`
|
||||||
|
PubKey crypto.PubKey `json:"pub_key"`
|
||||||
|
LastHeight int64 `json:"last_height"`
|
||||||
|
LastRound int `json:"last_round"`
|
||||||
|
LastStep int8 `json:"last_step"`
|
||||||
|
LastSignature crypto.Signature `json:"last_signature,omitempty"` // so we dont lose signatures XXX Why would we lose signatures?
|
||||||
|
LastSignBytes cmn.HexBytes `json:"last_signbytes,omitempty"` // so we dont lose signatures XXX Why would we lose signatures?
|
||||||
|
PrivKey crypto.PrivKey `json:"priv_key"`
|
||||||
|
|
||||||
|
// For persistence.
|
||||||
|
// Overloaded for testing.
|
||||||
|
filePath string
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAddress returns the address of the validator.
|
||||||
|
// Implements PrivValidator.
|
||||||
|
func (pv *FilePV) GetAddress() types.Address {
|
||||||
|
return pv.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPubKey returns the public key of the validator.
|
||||||
|
// Implements PrivValidator.
|
||||||
|
func (pv *FilePV) GetPubKey() crypto.PubKey {
|
||||||
|
return pv.PubKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenFilePV generates a new validator with randomly generated private key
|
||||||
|
// and sets the filePath, but does not call Save().
|
||||||
|
func GenFilePV(filePath string) *FilePV {
|
||||||
|
privKey := crypto.GenPrivKeyEd25519()
|
||||||
|
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(cmn.Fmt("Error reading PrivValidator from %v: %v\n", filePath, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
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 == "" {
|
||||||
|
panic("Cannot save PrivValidator: filePath not set")
|
||||||
|
}
|
||||||
|
jsonBytes, err := cdc.MarshalJSON(pv)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets all fields in the FilePV.
|
||||||
|
// NOTE: Unsafe!
|
||||||
|
func (pv *FilePV) Reset() {
|
||||||
|
var sig crypto.Signature
|
||||||
|
pv.LastHeight = 0
|
||||||
|
pv.LastRound = 0
|
||||||
|
pv.LastStep = 0
|
||||||
|
pv.LastSignature = sig
|
||||||
|
pv.LastSignBytes = nil
|
||||||
|
pv.Save()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignVote signs a canonical representation of the vote, along with the
|
||||||
|
// chainID. Implements PrivValidator.
|
||||||
|
func (pv *FilePV) SignVote(chainID string, vote *types.Vote) error {
|
||||||
|
pv.mtx.Lock()
|
||||||
|
defer pv.mtx.Unlock()
|
||||||
|
if err := pv.signVote(chainID, vote); err != nil {
|
||||||
|
return errors.New(cmn.Fmt("Error signing vote: %v", err))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignProposal signs a canonical representation of the proposal, along with
|
||||||
|
// the chainID. Implements PrivValidator.
|
||||||
|
func (pv *FilePV) SignProposal(chainID string, proposal *types.Proposal) error {
|
||||||
|
pv.mtx.Lock()
|
||||||
|
defer pv.mtx.Unlock()
|
||||||
|
if err := pv.signProposal(chainID, proposal); err != nil {
|
||||||
|
return fmt.Errorf("Error signing proposal: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns error if HRS regression or no LastSignBytes. returns true if HRS is unchanged
|
||||||
|
func (pv *FilePV) checkHRS(height int64, round int, step int8) (bool, error) {
|
||||||
|
if pv.LastHeight > height {
|
||||||
|
return false, errors.New("Height regression")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pv.LastHeight == height {
|
||||||
|
if pv.LastRound > round {
|
||||||
|
return false, errors.New("Round regression")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pv.LastRound == round {
|
||||||
|
if pv.LastStep > step {
|
||||||
|
return false, errors.New("Step regression")
|
||||||
|
} else if pv.LastStep == step {
|
||||||
|
if pv.LastSignBytes != nil {
|
||||||
|
if pv.LastSignature == nil {
|
||||||
|
panic("pv: LastSignature is nil but LastSignBytes is not!")
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, errors.New("No LastSignature found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// signVote checks if the vote is good to sign and sets the vote signature.
|
||||||
|
// It may need to set the timestamp as well if the vote is otherwise the same as
|
||||||
|
// a previously signed vote (ie. we crashed after signing but before the vote hit the WAL).
|
||||||
|
func (pv *FilePV) signVote(chainID string, vote *types.Vote) error {
|
||||||
|
height, round, step := vote.Height, vote.Round, voteToStep(vote)
|
||||||
|
signBytes := vote.SignBytes(chainID)
|
||||||
|
|
||||||
|
sameHRS, err := pv.checkHRS(height, round, step)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We might crash before writing to the wal,
|
||||||
|
// causing us to try to re-sign for the same HRS.
|
||||||
|
// If signbytes are the same, use the last signature.
|
||||||
|
// If they only differ by timestamp, use last timestamp and signature
|
||||||
|
// Otherwise, return error
|
||||||
|
if sameHRS {
|
||||||
|
if bytes.Equal(signBytes, pv.LastSignBytes) {
|
||||||
|
vote.Signature = pv.LastSignature
|
||||||
|
} else if timestamp, ok := checkVotesOnlyDifferByTimestamp(pv.LastSignBytes, signBytes); ok {
|
||||||
|
vote.Timestamp = timestamp
|
||||||
|
vote.Signature = pv.LastSignature
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("Conflicting data")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// It passed the checks. Sign the vote
|
||||||
|
sig := pv.PrivKey.Sign(signBytes)
|
||||||
|
pv.saveSigned(height, round, step, signBytes, sig)
|
||||||
|
vote.Signature = sig
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// signProposal checks if the proposal is good to sign and sets the proposal signature.
|
||||||
|
// It may need to set the timestamp as well if the proposal is otherwise the same as
|
||||||
|
// a previously signed proposal ie. we crashed after signing but before the proposal hit the WAL).
|
||||||
|
func (pv *FilePV) signProposal(chainID string, proposal *types.Proposal) error {
|
||||||
|
height, round, step := proposal.Height, proposal.Round, stepPropose
|
||||||
|
signBytes := proposal.SignBytes(chainID)
|
||||||
|
|
||||||
|
sameHRS, err := pv.checkHRS(height, round, step)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We might crash before writing to the wal,
|
||||||
|
// causing us to try to re-sign for the same HRS.
|
||||||
|
// If signbytes are the same, use the last signature.
|
||||||
|
// If they only differ by timestamp, use last timestamp and signature
|
||||||
|
// Otherwise, return error
|
||||||
|
if sameHRS {
|
||||||
|
if bytes.Equal(signBytes, pv.LastSignBytes) {
|
||||||
|
proposal.Signature = pv.LastSignature
|
||||||
|
} else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(pv.LastSignBytes, signBytes); ok {
|
||||||
|
proposal.Timestamp = timestamp
|
||||||
|
proposal.Signature = pv.LastSignature
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("Conflicting data")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// It passed the checks. Sign the proposal
|
||||||
|
sig := pv.PrivKey.Sign(signBytes)
|
||||||
|
pv.saveSigned(height, round, step, signBytes, sig)
|
||||||
|
proposal.Signature = sig
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist height/round/step and signature
|
||||||
|
func (pv *FilePV) saveSigned(height int64, round int, step int8,
|
||||||
|
signBytes []byte, sig crypto.Signature) {
|
||||||
|
|
||||||
|
pv.LastHeight = height
|
||||||
|
pv.LastRound = round
|
||||||
|
pv.LastStep = step
|
||||||
|
pv.LastSignature = sig
|
||||||
|
pv.LastSignBytes = signBytes
|
||||||
|
pv.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignHeartbeat signs a canonical representation of the heartbeat, along with the chainID.
|
||||||
|
// Implements PrivValidator.
|
||||||
|
func (pv *FilePV) SignHeartbeat(chainID string, heartbeat *types.Heartbeat) error {
|
||||||
|
pv.mtx.Lock()
|
||||||
|
defer pv.mtx.Unlock()
|
||||||
|
heartbeat.Signature = pv.PrivKey.Sign(heartbeat.SignBytes(chainID))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the FilePV.
|
||||||
|
func (pv *FilePV) String() string {
|
||||||
|
return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", pv.GetAddress(), pv.LastHeight, pv.LastRound, pv.LastStep)
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------
|
||||||
|
|
||||||
|
// returns the timestamp from the lastSignBytes.
|
||||||
|
// returns true if the only difference in the votes is their timestamp.
|
||||||
|
func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {
|
||||||
|
var lastVote, newVote types.CanonicalJSONVote
|
||||||
|
if err := cdc.UnmarshalJSON(lastSignBytes, &lastVote); err != nil {
|
||||||
|
panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into vote: %v", err))
|
||||||
|
}
|
||||||
|
if err := cdc.UnmarshalJSON(newSignBytes, &newVote); err != nil {
|
||||||
|
panic(fmt.Sprintf("signBytes cannot be unmarshalled into vote: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTime, err := time.Parse(types.TimeFormat, lastVote.Timestamp)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the times to the same value and check equality
|
||||||
|
now := types.CanonicalTime(time.Now())
|
||||||
|
lastVote.Timestamp = now
|
||||||
|
newVote.Timestamp = now
|
||||||
|
lastVoteBytes, _ := cdc.MarshalJSON(lastVote)
|
||||||
|
newVoteBytes, _ := cdc.MarshalJSON(newVote)
|
||||||
|
|
||||||
|
return lastTime, bytes.Equal(newVoteBytes, lastVoteBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the timestamp from the lastSignBytes.
|
||||||
|
// returns true if the only difference in the proposals is their timestamp
|
||||||
|
func checkProposalsOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {
|
||||||
|
var lastProposal, newProposal types.CanonicalJSONProposal
|
||||||
|
if err := cdc.UnmarshalJSON(lastSignBytes, &lastProposal); err != nil {
|
||||||
|
panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into proposal: %v", err))
|
||||||
|
}
|
||||||
|
if err := cdc.UnmarshalJSON(newSignBytes, &newProposal); err != nil {
|
||||||
|
panic(fmt.Sprintf("signBytes cannot be unmarshalled into proposal: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTime, err := time.Parse(types.TimeFormat, lastProposal.Timestamp)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the times to the same value and check equality
|
||||||
|
now := types.CanonicalTime(time.Now())
|
||||||
|
lastProposal.Timestamp = now
|
||||||
|
newProposal.Timestamp = now
|
||||||
|
lastProposalBytes, _ := cdc.MarshalJSON(lastProposal)
|
||||||
|
newProposalBytes, _ := cdc.MarshalJSON(newProposal)
|
||||||
|
|
||||||
|
return lastTime, bytes.Equal(newProposalBytes, lastProposalBytes)
|
||||||
|
}
|
|
@ -1,8 +1,7 @@
|
||||||
package types
|
package privval
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -10,113 +9,89 @@ import (
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
crypto "github.com/tendermint/go-crypto"
|
"github.com/tendermint/go-crypto"
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/types"
|
"github.com/tendermint/tendermint/types"
|
||||||
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGenLoadValidator(t *testing.T) {
|
func TestGenLoadValidator(t *testing.T) {
|
||||||
assert, require := assert.New(t), require.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
_, tempFilePath := cmn.Tempfile("priv_validator_")
|
_, tempFilePath := cmn.Tempfile("priv_validator_")
|
||||||
privVal := GenPrivValidatorJSON(tempFilePath)
|
privVal := GenFilePV(tempFilePath)
|
||||||
|
|
||||||
height := int64(100)
|
height := int64(100)
|
||||||
privVal.LastSignedInfo.Height = height
|
privVal.LastHeight = height
|
||||||
privVal.Save()
|
privVal.Save()
|
||||||
addr, err := privVal.Address()
|
addr := privVal.GetAddress()
|
||||||
require.Nil(err)
|
|
||||||
|
|
||||||
privVal = LoadPrivValidatorJSON(tempFilePath)
|
privVal = LoadFilePV(tempFilePath)
|
||||||
pAddr, err := privVal.Address()
|
assert.Equal(addr, privVal.GetAddress(), "expected privval addr to be the same")
|
||||||
require.Nil(err)
|
assert.Equal(height, privVal.LastHeight, "expected privval.LastHeight to have been saved")
|
||||||
|
|
||||||
assert.Equal(addr, pAddr, "expected privval addr to be the same")
|
|
||||||
assert.Equal(height, privVal.LastSignedInfo.Height, "expected privval.LastHeight to have been saved")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadOrGenValidator(t *testing.T) {
|
func TestLoadOrGenValidator(t *testing.T) {
|
||||||
assert, require := assert.New(t), require.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
_, tempFilePath := cmn.Tempfile("priv_validator_")
|
_, tempFilePath := cmn.Tempfile("priv_validator_")
|
||||||
if err := os.Remove(tempFilePath); err != nil {
|
if err := os.Remove(tempFilePath); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
privVal := LoadOrGenPrivValidatorJSON(tempFilePath)
|
privVal := LoadOrGenFilePV(tempFilePath)
|
||||||
addr, err := privVal.Address()
|
addr := privVal.GetAddress()
|
||||||
require.Nil(err)
|
privVal = LoadOrGenFilePV(tempFilePath)
|
||||||
|
assert.Equal(addr, privVal.GetAddress(), "expected privval addr to be the same")
|
||||||
privVal = LoadOrGenPrivValidatorJSON(tempFilePath)
|
|
||||||
pAddr, err := privVal.Address()
|
|
||||||
require.Nil(err)
|
|
||||||
|
|
||||||
assert.Equal(addr, pAddr, "expected privval addr to be the same")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnmarshalValidator(t *testing.T) {
|
func TestUnmarshalValidator(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
|
||||||
addrStr := "D028C9981F7A87F3093672BF0D5B0E2A1B3ED456"
|
privKey := crypto.GenPrivKeyEd25519()
|
||||||
pubStr := "3B3069C422E19688B45CBFAE7BB009FC0FA1B1EA86593519318B7214853803C8"
|
pubKey := privKey.PubKey()
|
||||||
privStr := "27F82582AEFAE7AB151CFB01C48BB6C1A0DA78F9BDDA979A9F70A84D074EB07D3B3069C422E19688B45CBFAE7BB009FC0FA1B1EA86593519318B7214853803C8"
|
addr := pubKey.Address()
|
||||||
addrBytes, _ := hex.DecodeString(addrStr)
|
pubArray := [32]byte(pubKey.(crypto.PubKeyEd25519))
|
||||||
pubBytes, _ := hex.DecodeString(pubStr)
|
pubBytes := pubArray[:]
|
||||||
privBytes, _ := hex.DecodeString(privStr)
|
privArray := [64]byte(privKey)
|
||||||
|
privBytes := privArray[:]
|
||||||
// prepend type byte
|
pubB64 := base64.StdEncoding.EncodeToString(pubBytes)
|
||||||
pubKey, err := crypto.PubKeyFromBytes(append([]byte{1}, pubBytes...))
|
privB64 := base64.StdEncoding.EncodeToString(privBytes)
|
||||||
require.Nil(err, "%+v", err)
|
|
||||||
privKey, err := crypto.PrivKeyFromBytes(append([]byte{1}, privBytes...))
|
|
||||||
require.Nil(err, "%+v", err)
|
|
||||||
|
|
||||||
serialized := fmt.Sprintf(`{
|
serialized := fmt.Sprintf(`{
|
||||||
"id": {
|
"address": "%s",
|
||||||
"address": "%s",
|
"pub_key": {
|
||||||
"pub_key": {
|
"type": "AC26791624DE60",
|
||||||
"type": "ed25519",
|
"value": "%s"
|
||||||
"data": "%s"
|
},
|
||||||
}
|
"last_height": 0,
|
||||||
},
|
"last_round": 0,
|
||||||
"priv_key": {
|
"last_step": 0,
|
||||||
"type": "ed25519",
|
"priv_key": {
|
||||||
"data": "%s"
|
"type": "954568A3288910",
|
||||||
},
|
"value": "%s"
|
||||||
"last_signed_info": {
|
}
|
||||||
"height": 0,
|
}`, addr, pubB64, privB64)
|
||||||
"round": 0,
|
|
||||||
"step": 0,
|
|
||||||
"signature": null
|
|
||||||
}
|
|
||||||
}`, addrStr, pubStr, privStr)
|
|
||||||
|
|
||||||
val := PrivValidatorJSON{}
|
val := FilePV{}
|
||||||
err = json.Unmarshal([]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
|
||||||
vAddr, err := val.Address()
|
assert.EqualValues(addr, val.GetAddress())
|
||||||
require.Nil(err)
|
assert.EqualValues(pubKey, val.GetPubKey())
|
||||||
|
|
||||||
pKey, err := val.PubKey()
|
|
||||||
require.Nil(err)
|
|
||||||
|
|
||||||
assert.EqualValues(addrBytes, vAddr)
|
|
||||||
assert.EqualValues(pubKey, pKey)
|
|
||||||
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
|
||||||
out, err := json.Marshal(val)
|
out, err := cdc.MarshalJSON(val)
|
||||||
require.Nil(err, "%+v", err)
|
require.Nil(err, "%+v", err)
|
||||||
assert.JSONEq(serialized, string(out))
|
assert.JSONEq(serialized, string(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSignVote(t *testing.T) {
|
func TestSignVote(t *testing.T) {
|
||||||
assert, require := assert.New(t), require.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
_, tempFilePath := cmn.Tempfile("priv_validator_")
|
_, tempFilePath := cmn.Tempfile("priv_validator_")
|
||||||
privVal := GenPrivValidatorJSON(tempFilePath)
|
privVal := GenFilePV(tempFilePath)
|
||||||
|
|
||||||
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{}}
|
||||||
|
@ -124,11 +99,8 @@ func TestSignVote(t *testing.T) {
|
||||||
voteType := types.VoteTypePrevote
|
voteType := types.VoteTypePrevote
|
||||||
|
|
||||||
// sign a vote for first time
|
// sign a vote for first time
|
||||||
addr, err := privVal.Address()
|
vote := newVote(privVal.Address, 0, height, round, voteType, block1)
|
||||||
require.Nil(err)
|
err := privVal.SignVote("mychainid", vote)
|
||||||
|
|
||||||
vote := newVote(addr, 0, height, round, voteType, block1)
|
|
||||||
err = privVal.SignVote("mychainid", vote)
|
|
||||||
assert.NoError(err, "expected no error signing vote")
|
assert.NoError(err, "expected no error signing vote")
|
||||||
|
|
||||||
// try to sign the same vote again; should be fine
|
// try to sign the same vote again; should be fine
|
||||||
|
@ -137,10 +109,10 @@ func TestSignVote(t *testing.T) {
|
||||||
|
|
||||||
// now try some bad votes
|
// now try some bad votes
|
||||||
cases := []*types.Vote{
|
cases := []*types.Vote{
|
||||||
newVote(addr, 0, height, round-1, voteType, block1), // round regression
|
newVote(privVal.Address, 0, height, round-1, voteType, block1), // round regression
|
||||||
newVote(addr, 0, height-1, round, voteType, block1), // height regression
|
newVote(privVal.Address, 0, height-1, round, voteType, block1), // height regression
|
||||||
newVote(addr, 0, height-2, round+4, voteType, block1), // height regression and different round
|
newVote(privVal.Address, 0, height-2, round+4, voteType, block1), // height regression and different round
|
||||||
newVote(addr, 0, height, round, voteType, block2), // different block
|
newVote(privVal.Address, 0, height, round, voteType, block2), // different block
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
|
@ -160,7 +132,7 @@ func TestSignProposal(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
_, tempFilePath := cmn.Tempfile("priv_validator_")
|
_, tempFilePath := cmn.Tempfile("priv_validator_")
|
||||||
privVal := GenPrivValidatorJSON(tempFilePath)
|
privVal := GenFilePV(tempFilePath)
|
||||||
|
|
||||||
block1 := types.PartSetHeader{5, []byte{1, 2, 3}}
|
block1 := types.PartSetHeader{5, []byte{1, 2, 3}}
|
||||||
block2 := types.PartSetHeader{10, []byte{3, 2, 1}}
|
block2 := types.PartSetHeader{10, []byte{3, 2, 1}}
|
||||||
|
@ -197,10 +169,8 @@ func TestSignProposal(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDifferByTimestamp(t *testing.T) {
|
func TestDifferByTimestamp(t *testing.T) {
|
||||||
require := require.New(t)
|
|
||||||
|
|
||||||
_, tempFilePath := cmn.Tempfile("priv_validator_")
|
_, tempFilePath := cmn.Tempfile("priv_validator_")
|
||||||
privVal := GenPrivValidatorJSON(tempFilePath)
|
privVal := GenFilePV(tempFilePath)
|
||||||
|
|
||||||
block1 := types.PartSetHeader{5, []byte{1, 2, 3}}
|
block1 := types.PartSetHeader{5, []byte{1, 2, 3}}
|
||||||
height, round := int64(10), 1
|
height, round := int64(10), 1
|
||||||
|
@ -217,7 +187,8 @@ func TestDifferByTimestamp(t *testing.T) {
|
||||||
|
|
||||||
// manipulate the timestamp. should get changed back
|
// manipulate the timestamp. should get changed back
|
||||||
proposal.Timestamp = proposal.Timestamp.Add(time.Millisecond)
|
proposal.Timestamp = proposal.Timestamp.Add(time.Millisecond)
|
||||||
proposal.Signature = crypto.Signature{}
|
var emptySig crypto.Signature
|
||||||
|
proposal.Signature = emptySig
|
||||||
err = privVal.SignProposal("mychainid", proposal)
|
err = privVal.SignProposal("mychainid", proposal)
|
||||||
assert.NoError(t, err, "expected no error on signing same proposal")
|
assert.NoError(t, err, "expected no error on signing same proposal")
|
||||||
|
|
||||||
|
@ -228,13 +199,10 @@ func TestDifferByTimestamp(t *testing.T) {
|
||||||
|
|
||||||
// test vote
|
// test vote
|
||||||
{
|
{
|
||||||
addr, err := privVal.Address()
|
|
||||||
require.Nil(err)
|
|
||||||
|
|
||||||
voteType := types.VoteTypePrevote
|
voteType := types.VoteTypePrevote
|
||||||
blockID := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}}
|
blockID := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}}
|
||||||
vote := newVote(addr, 0, height, round, voteType, blockID)
|
vote := newVote(privVal.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")
|
||||||
|
|
||||||
signBytes := vote.SignBytes(chainID)
|
signBytes := vote.SignBytes(chainID)
|
||||||
|
@ -243,7 +211,8 @@ func TestDifferByTimestamp(t *testing.T) {
|
||||||
|
|
||||||
// manipulate the timestamp. should get changed back
|
// manipulate the timestamp. should get changed back
|
||||||
vote.Timestamp = vote.Timestamp.Add(time.Millisecond)
|
vote.Timestamp = vote.Timestamp.Add(time.Millisecond)
|
||||||
vote.Signature = crypto.Signature{}
|
var emptySig crypto.Signature
|
||||||
|
vote.Signature = emptySig
|
||||||
err = privVal.SignVote("mychainid", vote)
|
err = privVal.SignVote("mychainid", vote)
|
||||||
assert.NoError(t, err, "expected no error on signing same vote")
|
assert.NoError(t, err, "expected no error on signing same vote")
|
||||||
|
|
||||||
|
@ -253,7 +222,7 @@ func TestDifferByTimestamp(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newVote(addr cmn.HexBytes, idx int, height int64, round int, typ byte, blockID types.BlockID) *types.Vote {
|
func newVote(addr types.Address, idx int, height int64, round int, typ byte, blockID types.BlockID) *types.Vote {
|
||||||
return &types.Vote{
|
return &types.Vote{
|
||||||
ValidatorAddress: addr,
|
ValidatorAddress: addr,
|
||||||
ValidatorIndex: idx,
|
ValidatorIndex: idx,
|
||||||
|
|
|
@ -1,238 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
crypto "github.com/tendermint/go-crypto"
|
|
||||||
"github.com/tendermint/tendermint/types"
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: type ?
|
|
||||||
const (
|
|
||||||
stepNone int8 = 0 // Used to distinguish the initial state
|
|
||||||
stepPropose int8 = 1
|
|
||||||
stepPrevote int8 = 2
|
|
||||||
stepPrecommit int8 = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
func voteToStep(vote *types.Vote) int8 {
|
|
||||||
switch vote.Type {
|
|
||||||
case types.VoteTypePrevote:
|
|
||||||
return stepPrevote
|
|
||||||
case types.VoteTypePrecommit:
|
|
||||||
return stepPrecommit
|
|
||||||
default:
|
|
||||||
panic("Unknown vote type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//-------------------------------------
|
|
||||||
|
|
||||||
// LastSignedInfo contains information about the latest
|
|
||||||
// data signed by a validator to help prevent double signing.
|
|
||||||
type LastSignedInfo struct {
|
|
||||||
Height int64 `json:"height"`
|
|
||||||
Round int `json:"round"`
|
|
||||||
Step int8 `json:"step"`
|
|
||||||
Signature crypto.Signature `json:"signature,omitempty"` // so we dont lose signatures
|
|
||||||
SignBytes cmn.HexBytes `json:"signbytes,omitempty"` // so we dont lose signatures
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLastSignedInfo() *LastSignedInfo {
|
|
||||||
return &LastSignedInfo{
|
|
||||||
Step: stepNone,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (info *LastSignedInfo) String() string {
|
|
||||||
return fmt.Sprintf("LH:%v, LR:%v, LS:%v", info.Height, info.Round, info.Step)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify returns an error if there is a height/round/step regression
|
|
||||||
// or if the HRS matches but there are no LastSignBytes.
|
|
||||||
// It returns true if HRS matches exactly and the LastSignature exists.
|
|
||||||
// It panics if the HRS matches, the LastSignBytes are not empty, but the LastSignature is empty.
|
|
||||||
func (info LastSignedInfo) Verify(height int64, round int, step int8) (bool, error) {
|
|
||||||
if info.Height > height {
|
|
||||||
return false, errors.New("Height regression")
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.Height == height {
|
|
||||||
if info.Round > round {
|
|
||||||
return false, errors.New("Round regression")
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.Round == round {
|
|
||||||
if info.Step > step {
|
|
||||||
return false, errors.New("Step regression")
|
|
||||||
} else if info.Step == step {
|
|
||||||
if info.SignBytes != nil {
|
|
||||||
if info.Signature.Empty() {
|
|
||||||
panic("info: LastSignature is nil but LastSignBytes is not!")
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, errors.New("No LastSignature found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set height/round/step and signature on the info
|
|
||||||
func (info *LastSignedInfo) Set(height int64, round int, step int8,
|
|
||||||
signBytes []byte, sig crypto.Signature) {
|
|
||||||
|
|
||||||
info.Height = height
|
|
||||||
info.Round = round
|
|
||||||
info.Step = step
|
|
||||||
info.Signature = sig
|
|
||||||
info.SignBytes = signBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset resets all the values.
|
|
||||||
// XXX: Unsafe.
|
|
||||||
func (info *LastSignedInfo) Reset() {
|
|
||||||
info.Height = 0
|
|
||||||
info.Round = 0
|
|
||||||
info.Step = 0
|
|
||||||
info.Signature = crypto.Signature{}
|
|
||||||
info.SignBytes = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignVote checks the height/round/step (HRS) are greater than the latest state of the LastSignedInfo.
|
|
||||||
// If so, it signs the vote, updates the LastSignedInfo, and sets the signature on the vote.
|
|
||||||
// If the HRS are equal and the only thing changed is the timestamp, it sets the vote.Timestamp to the previous
|
|
||||||
// value and the Signature to the LastSignedInfo.Signature.
|
|
||||||
// Else it returns an error.
|
|
||||||
func (lsi *LastSignedInfo) SignVote(signer types.Signer, chainID string, vote *types.Vote) error {
|
|
||||||
height, round, step := vote.Height, vote.Round, voteToStep(vote)
|
|
||||||
signBytes := vote.SignBytes(chainID)
|
|
||||||
|
|
||||||
sameHRS, err := lsi.Verify(height, round, step)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We might crash before writing to the wal,
|
|
||||||
// causing us to try to re-sign for the same HRS.
|
|
||||||
// If signbytes are the same, use the last signature.
|
|
||||||
// If they only differ by timestamp, use last timestamp and signature
|
|
||||||
// Otherwise, return error
|
|
||||||
if sameHRS {
|
|
||||||
if bytes.Equal(signBytes, lsi.SignBytes) {
|
|
||||||
vote.Signature = lsi.Signature
|
|
||||||
} else if timestamp, ok := checkVotesOnlyDifferByTimestamp(lsi.SignBytes, signBytes); ok {
|
|
||||||
vote.Timestamp = timestamp
|
|
||||||
vote.Signature = lsi.Signature
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("Conflicting data")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sig, err := signer.Sign(signBytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
lsi.Set(height, round, step, signBytes, sig)
|
|
||||||
vote.Signature = sig
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignProposal checks if the height/round/step (HRS) are greater than the latest state of the LastSignedInfo.
|
|
||||||
// If so, it signs the proposal, updates the LastSignedInfo, and sets the signature on the proposal.
|
|
||||||
// If the HRS are equal and the only thing changed is the timestamp, it sets the timestamp to the previous
|
|
||||||
// value and the Signature to the LastSignedInfo.Signature.
|
|
||||||
// Else it returns an error.
|
|
||||||
func (lsi *LastSignedInfo) SignProposal(signer types.Signer, chainID string, proposal *types.Proposal) error {
|
|
||||||
height, round, step := proposal.Height, proposal.Round, stepPropose
|
|
||||||
signBytes := proposal.SignBytes(chainID)
|
|
||||||
|
|
||||||
sameHRS, err := lsi.Verify(height, round, step)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We might crash before writing to the wal,
|
|
||||||
// causing us to try to re-sign for the same HRS.
|
|
||||||
// If signbytes are the same, use the last signature.
|
|
||||||
// If they only differ by timestamp, use last timestamp and signature
|
|
||||||
// Otherwise, return error
|
|
||||||
if sameHRS {
|
|
||||||
if bytes.Equal(signBytes, lsi.SignBytes) {
|
|
||||||
proposal.Signature = lsi.Signature
|
|
||||||
} else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(lsi.SignBytes, signBytes); ok {
|
|
||||||
proposal.Timestamp = timestamp
|
|
||||||
proposal.Signature = lsi.Signature
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("Conflicting data")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sig, err := signer.Sign(signBytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
lsi.Set(height, round, step, signBytes, sig)
|
|
||||||
proposal.Signature = sig
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//-------------------------------------
|
|
||||||
|
|
||||||
// returns the timestamp from the lastSignBytes.
|
|
||||||
// returns true if the only difference in the votes is their timestamp.
|
|
||||||
func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {
|
|
||||||
var lastVote, newVote types.CanonicalJSONOnceVote
|
|
||||||
if err := json.Unmarshal(lastSignBytes, &lastVote); err != nil {
|
|
||||||
panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into vote: %v", err))
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(newSignBytes, &newVote); err != nil {
|
|
||||||
panic(fmt.Sprintf("signBytes cannot be unmarshalled into vote: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
lastTime, err := time.Parse(types.TimeFormat, lastVote.Vote.Timestamp)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the times to the same value and check equality
|
|
||||||
now := types.CanonicalTime(time.Now())
|
|
||||||
lastVote.Vote.Timestamp = now
|
|
||||||
newVote.Vote.Timestamp = now
|
|
||||||
lastVoteBytes, _ := json.Marshal(lastVote)
|
|
||||||
newVoteBytes, _ := json.Marshal(newVote)
|
|
||||||
|
|
||||||
return lastTime, bytes.Equal(newVoteBytes, lastVoteBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns the timestamp from the lastSignBytes.
|
|
||||||
// returns true if the only difference in the proposals is their timestamp
|
|
||||||
func checkProposalsOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {
|
|
||||||
var lastProposal, newProposal types.CanonicalJSONOnceProposal
|
|
||||||
if err := json.Unmarshal(lastSignBytes, &lastProposal); err != nil {
|
|
||||||
panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into proposal: %v", err))
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(newSignBytes, &newProposal); err != nil {
|
|
||||||
panic(fmt.Sprintf("signBytes cannot be unmarshalled into proposal: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
lastTime, err := time.Parse(types.TimeFormat, lastProposal.Proposal.Timestamp)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the times to the same value and check equality
|
|
||||||
now := types.CanonicalTime(time.Now())
|
|
||||||
lastProposal.Proposal.Timestamp = now
|
|
||||||
newProposal.Proposal.Timestamp = now
|
|
||||||
lastProposalBytes, _ := json.Marshal(lastProposal)
|
|
||||||
newProposalBytes, _ := json.Marshal(newProposal)
|
|
||||||
|
|
||||||
return lastTime, bytes.Equal(newProposalBytes, lastProposalBytes)
|
|
||||||
}
|
|
|
@ -1,14 +1,14 @@
|
||||||
package types
|
package privval
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/tendermint/go-amino"
|
||||||
crypto "github.com/tendermint/go-crypto"
|
"github.com/tendermint/go-crypto"
|
||||||
wire "github.com/tendermint/go-wire"
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
"github.com/tendermint/tmlibs/log"
|
"github.com/tendermint/tmlibs/log"
|
||||||
|
|
||||||
|
@ -37,36 +37,36 @@ var (
|
||||||
connHeartbeat = time.Second * defaultConnHeartBeatSeconds
|
connHeartbeat = time.Second * defaultConnHeartBeatSeconds
|
||||||
)
|
)
|
||||||
|
|
||||||
// SocketClientOption sets an optional parameter on the SocketClient.
|
// SocketPVOption sets an optional parameter on the SocketPV.
|
||||||
type SocketClientOption func(*SocketClient)
|
type SocketPVOption func(*SocketPV)
|
||||||
|
|
||||||
// SocketClientAcceptDeadline sets the deadline for the SocketClient listener.
|
// SocketPVAcceptDeadline sets the deadline for the SocketPV listener.
|
||||||
// A zero time value disables the deadline.
|
// A zero time value disables the deadline.
|
||||||
func SocketClientAcceptDeadline(deadline time.Duration) SocketClientOption {
|
func SocketPVAcceptDeadline(deadline time.Duration) SocketPVOption {
|
||||||
return func(sc *SocketClient) { sc.acceptDeadline = deadline }
|
return func(sc *SocketPV) { sc.acceptDeadline = deadline }
|
||||||
}
|
}
|
||||||
|
|
||||||
// SocketClientConnDeadline sets the read and write deadline for connections
|
// SocketPVConnDeadline sets the read and write deadline for connections
|
||||||
// from external signing processes.
|
// from external signing processes.
|
||||||
func SocketClientConnDeadline(deadline time.Duration) SocketClientOption {
|
func SocketPVConnDeadline(deadline time.Duration) SocketPVOption {
|
||||||
return func(sc *SocketClient) { sc.connDeadline = deadline }
|
return func(sc *SocketPV) { sc.connDeadline = deadline }
|
||||||
}
|
}
|
||||||
|
|
||||||
// SocketClientHeartbeat sets the period on which to check the liveness of the
|
// SocketPVHeartbeat sets the period on which to check the liveness of the
|
||||||
// connected Signer connections.
|
// connected Signer connections.
|
||||||
func SocketClientHeartbeat(period time.Duration) SocketClientOption {
|
func SocketPVHeartbeat(period time.Duration) SocketPVOption {
|
||||||
return func(sc *SocketClient) { sc.connHeartbeat = period }
|
return func(sc *SocketPV) { sc.connHeartbeat = period }
|
||||||
}
|
}
|
||||||
|
|
||||||
// SocketClientConnWait sets the timeout duration before connection of external
|
// SocketPVConnWait sets the timeout duration before connection of external
|
||||||
// signing processes are considered to be unsuccessful.
|
// signing processes are considered to be unsuccessful.
|
||||||
func SocketClientConnWait(timeout time.Duration) SocketClientOption {
|
func SocketPVConnWait(timeout time.Duration) SocketPVOption {
|
||||||
return func(sc *SocketClient) { sc.connWaitTimeout = timeout }
|
return func(sc *SocketPV) { sc.connWaitTimeout = timeout }
|
||||||
}
|
}
|
||||||
|
|
||||||
// SocketClient implements PrivValidator, it uses a socket to request signatures
|
// SocketPV implements PrivValidator, it uses a socket to request signatures
|
||||||
// from an external process.
|
// from an external process.
|
||||||
type SocketClient struct {
|
type SocketPV struct {
|
||||||
cmn.BaseService
|
cmn.BaseService
|
||||||
|
|
||||||
addr string
|
addr string
|
||||||
|
@ -80,16 +80,16 @@ type SocketClient struct {
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that SocketClient implements PrivValidator2.
|
// Check that SocketPV implements PrivValidator.
|
||||||
var _ types.PrivValidator2 = (*SocketClient)(nil)
|
var _ types.PrivValidator = (*SocketPV)(nil)
|
||||||
|
|
||||||
// NewSocketClient returns an instance of SocketClient.
|
// NewSocketPV returns an instance of SocketPV.
|
||||||
func NewSocketClient(
|
func NewSocketPV(
|
||||||
logger log.Logger,
|
logger log.Logger,
|
||||||
socketAddr string,
|
socketAddr string,
|
||||||
privKey crypto.PrivKeyEd25519,
|
privKey crypto.PrivKeyEd25519,
|
||||||
) *SocketClient {
|
) *SocketPV {
|
||||||
sc := &SocketClient{
|
sc := &SocketPV{
|
||||||
addr: socketAddr,
|
addr: socketAddr,
|
||||||
acceptDeadline: acceptDeadline,
|
acceptDeadline: acceptDeadline,
|
||||||
connDeadline: connDeadline,
|
connDeadline: connDeadline,
|
||||||
|
@ -98,15 +98,14 @@ func NewSocketClient(
|
||||||
privKey: privKey,
|
privKey: privKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
sc.BaseService = *cmn.NewBaseService(logger, "SocketClient", sc)
|
sc.BaseService = *cmn.NewBaseService(logger, "SocketPV", sc)
|
||||||
|
|
||||||
return sc
|
return sc
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAddress implements PrivValidator.
|
// GetAddress implements PrivValidator.
|
||||||
// TODO(xla): Remove when PrivValidator2 replaced PrivValidator.
|
func (sc *SocketPV) GetAddress() types.Address {
|
||||||
func (sc *SocketClient) GetAddress() types.Address {
|
addr, err := sc.getAddress()
|
||||||
addr, err := sc.Address()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -115,8 +114,8 @@ func (sc *SocketClient) GetAddress() types.Address {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Address is an alias for PubKey().Address().
|
// Address is an alias for PubKey().Address().
|
||||||
func (sc *SocketClient) Address() (cmn.HexBytes, error) {
|
func (sc *SocketPV) getAddress() (cmn.HexBytes, error) {
|
||||||
p, err := sc.PubKey()
|
p, err := sc.getPubKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -125,9 +124,8 @@ func (sc *SocketClient) Address() (cmn.HexBytes, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPubKey implements PrivValidator.
|
// GetPubKey implements PrivValidator.
|
||||||
// TODO(xla): Remove when PrivValidator2 replaced PrivValidator.
|
func (sc *SocketPV) GetPubKey() crypto.PubKey {
|
||||||
func (sc *SocketClient) GetPubKey() crypto.PubKey {
|
pubKey, err := sc.getPubKey()
|
||||||
pubKey, err := sc.PubKey()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -135,23 +133,22 @@ func (sc *SocketClient) GetPubKey() crypto.PubKey {
|
||||||
return pubKey
|
return pubKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// PubKey implements PrivValidator2.
|
func (sc *SocketPV) getPubKey() (crypto.PubKey, error) {
|
||||||
func (sc *SocketClient) PubKey() (crypto.PubKey, error) {
|
|
||||||
err := writeMsg(sc.conn, &PubKeyMsg{})
|
err := writeMsg(sc.conn, &PubKeyMsg{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return crypto.PubKey{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := readMsg(sc.conn)
|
res, err := readMsg(sc.conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return crypto.PubKey{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.(*PubKeyMsg).PubKey, nil
|
return res.(*PubKeyMsg).PubKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignVote implements PrivValidator2.
|
// SignVote implements PrivValidator.
|
||||||
func (sc *SocketClient) SignVote(chainID string, vote *types.Vote) error {
|
func (sc *SocketPV) SignVote(chainID string, vote *types.Vote) error {
|
||||||
err := writeMsg(sc.conn, &SignVoteMsg{Vote: vote})
|
err := writeMsg(sc.conn, &SignVoteMsg{Vote: vote})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -167,8 +164,8 @@ func (sc *SocketClient) SignVote(chainID string, vote *types.Vote) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignProposal implements PrivValidator2.
|
// SignProposal implements PrivValidator.
|
||||||
func (sc *SocketClient) SignProposal(
|
func (sc *SocketPV) SignProposal(
|
||||||
chainID string,
|
chainID string,
|
||||||
proposal *types.Proposal,
|
proposal *types.Proposal,
|
||||||
) error {
|
) error {
|
||||||
|
@ -187,8 +184,8 @@ func (sc *SocketClient) SignProposal(
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignHeartbeat implements PrivValidator2.
|
// SignHeartbeat implements PrivValidator.
|
||||||
func (sc *SocketClient) SignHeartbeat(
|
func (sc *SocketPV) SignHeartbeat(
|
||||||
chainID string,
|
chainID string,
|
||||||
heartbeat *types.Heartbeat,
|
heartbeat *types.Heartbeat,
|
||||||
) error {
|
) error {
|
||||||
|
@ -208,21 +205,22 @@ func (sc *SocketClient) SignHeartbeat(
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnStart implements cmn.Service.
|
// OnStart implements cmn.Service.
|
||||||
func (sc *SocketClient) OnStart() error {
|
func (sc *SocketPV) OnStart() error {
|
||||||
if err := sc.listen(); err != nil {
|
if err := sc.listen(); err != nil {
|
||||||
|
err = cmn.ErrorWrap(err, "failed to listen")
|
||||||
sc.Logger.Error(
|
sc.Logger.Error(
|
||||||
"OnStart",
|
"OnStart",
|
||||||
"err", errors.Wrap(err, "failed to listen"),
|
"err", err,
|
||||||
)
|
)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := sc.waitConnection()
|
conn, err := sc.waitConnection()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
err = cmn.ErrorWrap(err, "failed to accept connection")
|
||||||
sc.Logger.Error(
|
sc.Logger.Error(
|
||||||
"OnStart",
|
"OnStart",
|
||||||
"err", errors.Wrap(err, "failed to accept connection"),
|
"err", err,
|
||||||
)
|
)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
@ -234,27 +232,29 @@ func (sc *SocketClient) OnStart() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnStop implements cmn.Service.
|
// OnStop implements cmn.Service.
|
||||||
func (sc *SocketClient) OnStop() {
|
func (sc *SocketPV) OnStop() {
|
||||||
if sc.conn != nil {
|
if sc.conn != nil {
|
||||||
if err := sc.conn.Close(); err != nil {
|
if err := sc.conn.Close(); err != nil {
|
||||||
|
err = cmn.ErrorWrap(err, "failed to close connection")
|
||||||
sc.Logger.Error(
|
sc.Logger.Error(
|
||||||
"OnStop",
|
"OnStop",
|
||||||
"err", errors.Wrap(err, "failed to close connection"),
|
"err", err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if sc.listener != nil {
|
if sc.listener != nil {
|
||||||
if err := sc.listener.Close(); err != nil {
|
if err := sc.listener.Close(); err != nil {
|
||||||
|
err = cmn.ErrorWrap(err, "failed to close listener")
|
||||||
sc.Logger.Error(
|
sc.Logger.Error(
|
||||||
"OnStop",
|
"OnStop",
|
||||||
"err", errors.Wrap(err, "failed to close listener"),
|
"err", err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *SocketClient) acceptConnection() (net.Conn, error) {
|
func (sc *SocketPV) acceptConnection() (net.Conn, error) {
|
||||||
conn, err := sc.listener.Accept()
|
conn, err := sc.listener.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !sc.IsRunning() {
|
if !sc.IsRunning() {
|
||||||
|
@ -264,7 +264,7 @@ func (sc *SocketClient) acceptConnection() (net.Conn, error) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err = p2pconn.MakeSecretConnection(conn, sc.privKey.Wrap())
|
conn, err = p2pconn.MakeSecretConnection(conn, sc.privKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -272,7 +272,7 @@ func (sc *SocketClient) acceptConnection() (net.Conn, error) {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *SocketClient) listen() error {
|
func (sc *SocketPV) listen() error {
|
||||||
ln, err := net.Listen(cmn.ProtocolAndAddress(sc.addr))
|
ln, err := net.Listen(cmn.ProtocolAndAddress(sc.addr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -290,7 +290,7 @@ func (sc *SocketClient) listen() error {
|
||||||
|
|
||||||
// waitConnection uses the configured wait timeout to error if no external
|
// waitConnection uses the configured wait timeout to error if no external
|
||||||
// process connects in the time period.
|
// process connects in the time period.
|
||||||
func (sc *SocketClient) waitConnection() (net.Conn, error) {
|
func (sc *SocketPV) waitConnection() (net.Conn, error) {
|
||||||
var (
|
var (
|
||||||
connc = make(chan net.Conn, 1)
|
connc = make(chan net.Conn, 1)
|
||||||
errc = make(chan error, 1)
|
errc = make(chan error, 1)
|
||||||
|
@ -311,7 +311,7 @@ func (sc *SocketClient) waitConnection() (net.Conn, error) {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
case err := <-errc:
|
case err := <-errc:
|
||||||
if _, ok := err.(timeoutError); ok {
|
if _, ok := err.(timeoutError); ok {
|
||||||
return nil, errors.Wrap(ErrConnWaitTimeout, err.Error())
|
return nil, cmn.ErrorWrap(ErrConnWaitTimeout, err.Error())
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
case <-time.After(sc.connWaitTimeout):
|
case <-time.After(sc.connWaitTimeout):
|
||||||
|
@ -344,7 +344,7 @@ type RemoteSigner struct {
|
||||||
connDeadline time.Duration
|
connDeadline time.Duration
|
||||||
connRetries int
|
connRetries int
|
||||||
privKey crypto.PrivKeyEd25519
|
privKey crypto.PrivKeyEd25519
|
||||||
privVal PrivValidator
|
privVal types.PrivValidator
|
||||||
|
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
}
|
}
|
||||||
|
@ -353,7 +353,7 @@ type RemoteSigner struct {
|
||||||
func NewRemoteSigner(
|
func NewRemoteSigner(
|
||||||
logger log.Logger,
|
logger log.Logger,
|
||||||
chainID, socketAddr string,
|
chainID, socketAddr string,
|
||||||
privVal PrivValidator,
|
privVal types.PrivValidator,
|
||||||
privKey crypto.PrivKeyEd25519,
|
privKey crypto.PrivKeyEd25519,
|
||||||
) *RemoteSigner {
|
) *RemoteSigner {
|
||||||
rs := &RemoteSigner{
|
rs := &RemoteSigner{
|
||||||
|
@ -374,8 +374,8 @@ func NewRemoteSigner(
|
||||||
func (rs *RemoteSigner) OnStart() error {
|
func (rs *RemoteSigner) OnStart() error {
|
||||||
conn, err := rs.connect()
|
conn, err := rs.connect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rs.Logger.Error("OnStart", "err", errors.Wrap(err, "connect"))
|
err = cmn.ErrorWrap(err, "connect")
|
||||||
|
rs.Logger.Error("OnStart", "err", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -391,7 +391,7 @@ func (rs *RemoteSigner) OnStop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := rs.conn.Close(); err != nil {
|
if err := rs.conn.Close(); err != nil {
|
||||||
rs.Logger.Error("OnStop", "err", errors.Wrap(err, "closing listener failed"))
|
rs.Logger.Error("OnStop", "err", cmn.ErrorWrap(err, "closing listener failed"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,28 +404,31 @@ func (rs *RemoteSigner) connect() (net.Conn, error) {
|
||||||
|
|
||||||
conn, err := cmn.Connect(rs.addr)
|
conn, err := cmn.Connect(rs.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
err = cmn.ErrorWrap(err, "connection failed")
|
||||||
rs.Logger.Error(
|
rs.Logger.Error(
|
||||||
"connect",
|
"connect",
|
||||||
"addr", rs.addr,
|
"addr", rs.addr,
|
||||||
"err", errors.Wrap(err, "connection failed"),
|
"err", err,
|
||||||
)
|
)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := conn.SetDeadline(time.Now().Add(connDeadline)); err != nil {
|
if err := conn.SetDeadline(time.Now().Add(connDeadline)); err != nil {
|
||||||
|
err = cmn.ErrorWrap(err, "setting connection timeout failed")
|
||||||
rs.Logger.Error(
|
rs.Logger.Error(
|
||||||
"connect",
|
"connect",
|
||||||
"err", errors.Wrap(err, "setting connection timeout failed"),
|
"err", err,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err = p2pconn.MakeSecretConnection(conn, rs.privKey.Wrap())
|
conn, err = p2pconn.MakeSecretConnection(conn, rs.privKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
err = cmn.ErrorWrap(err, "encrypting connection failed")
|
||||||
rs.Logger.Error(
|
rs.Logger.Error(
|
||||||
"connect",
|
"connect",
|
||||||
"err", errors.Wrap(err, "encrypting connection failed"),
|
"err", err,
|
||||||
)
|
)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
@ -451,13 +454,12 @@ func (rs *RemoteSigner) handleConnection(conn net.Conn) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var res PrivValMsg
|
var res SocketPVMsg
|
||||||
|
|
||||||
switch r := req.(type) {
|
switch r := req.(type) {
|
||||||
case *PubKeyMsg:
|
case *PubKeyMsg:
|
||||||
var p crypto.PubKey
|
var p crypto.PubKey
|
||||||
|
p = rs.privVal.GetPubKey()
|
||||||
p, err = rs.privVal.PubKey()
|
|
||||||
res = &PubKeyMsg{p}
|
res = &PubKeyMsg{p}
|
||||||
case *SignVoteMsg:
|
case *SignVoteMsg:
|
||||||
err = rs.privVal.SignVote(rs.chainID, r.Vote)
|
err = rs.privVal.SignVote(rs.chainID, r.Vote)
|
||||||
|
@ -487,23 +489,16 @@ func (rs *RemoteSigner) handleConnection(conn net.Conn) {
|
||||||
|
|
||||||
//---------------------------------------------------------
|
//---------------------------------------------------------
|
||||||
|
|
||||||
const (
|
// SocketPVMsg is sent between RemoteSigner and SocketPV.
|
||||||
msgTypePubKey = byte(0x01)
|
type SocketPVMsg interface{}
|
||||||
msgTypeSignVote = byte(0x10)
|
|
||||||
msgTypeSignProposal = byte(0x11)
|
|
||||||
msgTypeSignHeartbeat = byte(0x12)
|
|
||||||
)
|
|
||||||
|
|
||||||
// PrivValMsg is sent between RemoteSigner and SocketClient.
|
func RegisterSocketPVMsg(cdc *amino.Codec) {
|
||||||
type PrivValMsg interface{}
|
cdc.RegisterInterface((*SocketPVMsg)(nil), nil)
|
||||||
|
cdc.RegisterConcrete(&PubKeyMsg{}, "tendermint/socketpv/PubKeyMsg", nil)
|
||||||
var _ = wire.RegisterInterface(
|
cdc.RegisterConcrete(&SignVoteMsg{}, "tendermint/socketpv/SignVoteMsg", nil)
|
||||||
struct{ PrivValMsg }{},
|
cdc.RegisterConcrete(&SignProposalMsg{}, "tendermint/socketpv/SignProposalMsg", nil)
|
||||||
wire.ConcreteType{&PubKeyMsg{}, msgTypePubKey},
|
cdc.RegisterConcrete(&SignHeartbeatMsg{}, "tendermint/socketpv/SignHeartbeatMsg", nil)
|
||||||
wire.ConcreteType{&SignVoteMsg{}, msgTypeSignVote},
|
}
|
||||||
wire.ConcreteType{&SignProposalMsg{}, msgTypeSignProposal},
|
|
||||||
wire.ConcreteType{&SignHeartbeatMsg{}, msgTypeSignHeartbeat},
|
|
||||||
)
|
|
||||||
|
|
||||||
// PubKeyMsg is a PrivValidatorSocket message containing the public key.
|
// PubKeyMsg is a PrivValidatorSocket message containing the public key.
|
||||||
type PubKeyMsg struct {
|
type PubKeyMsg struct {
|
||||||
|
@ -525,40 +520,19 @@ type SignHeartbeatMsg struct {
|
||||||
Heartbeat *types.Heartbeat
|
Heartbeat *types.Heartbeat
|
||||||
}
|
}
|
||||||
|
|
||||||
func readMsg(r io.Reader) (PrivValMsg, error) {
|
func readMsg(r io.Reader) (msg SocketPVMsg, err error) {
|
||||||
var (
|
const maxSocketPVMsgSize = 1024 * 10
|
||||||
n int
|
_, err = cdc.UnmarshalBinaryReader(r, &msg, maxSocketPVMsgSize)
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
read := wire.ReadBinary(struct{ PrivValMsg }{}, r, 0, &n, &err)
|
|
||||||
if err != nil {
|
|
||||||
if _, ok := err.(timeoutError); ok {
|
|
||||||
return nil, errors.Wrap(ErrConnTimeout, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
w, ok := read.(struct{ PrivValMsg })
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("unknown type")
|
|
||||||
}
|
|
||||||
|
|
||||||
return w.PrivValMsg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeMsg(w io.Writer, msg interface{}) error {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
n int
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO(xla): This extra wrap should be gone with the sdk-2 update.
|
|
||||||
wire.WriteBinary(struct{ PrivValMsg }{msg}, w, &n, &err)
|
|
||||||
if _, ok := err.(timeoutError); ok {
|
if _, ok := err.(timeoutError); ok {
|
||||||
return errors.Wrap(ErrConnTimeout, err.Error())
|
err = cmn.ErrorWrap(ErrConnTimeout, err.Error())
|
||||||
}
|
}
|
||||||
|
return
|
||||||
return err
|
}
|
||||||
|
|
||||||
|
func writeMsg(w io.Writer, msg interface{}) (err error) {
|
||||||
|
_, err = cdc.MarshalBinaryWriter(w, msg)
|
||||||
|
if _, ok := err.(timeoutError); ok {
|
||||||
|
err = cmn.ErrorWrap(ErrConnTimeout, err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package types
|
package privval
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package types
|
package privval
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package types
|
package privval
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -6,11 +6,10 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
crypto "github.com/tendermint/go-crypto"
|
"github.com/tendermint/go-crypto"
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
"github.com/tendermint/tmlibs/log"
|
"github.com/tendermint/tmlibs/log"
|
||||||
|
|
||||||
|
@ -18,7 +17,7 @@ import (
|
||||||
"github.com/tendermint/tendermint/types"
|
"github.com/tendermint/tendermint/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSocketClientAddress(t *testing.T) {
|
func TestSocketPVAddress(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
chainID = cmn.RandStr(12)
|
chainID = cmn.RandStr(12)
|
||||||
sc, rs = testSetupSocketPair(t, chainID)
|
sc, rs = testSetupSocketPair(t, chainID)
|
||||||
|
@ -26,10 +25,9 @@ func TestSocketClientAddress(t *testing.T) {
|
||||||
defer sc.Stop()
|
defer sc.Stop()
|
||||||
defer rs.Stop()
|
defer rs.Stop()
|
||||||
|
|
||||||
serverAddr, err := rs.privVal.Address()
|
serverAddr := rs.privVal.GetAddress()
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
clientAddr, err := sc.Address()
|
clientAddr, err := sc.getAddress()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, serverAddr, clientAddr)
|
assert.Equal(t, serverAddr, clientAddr)
|
||||||
|
@ -39,7 +37,7 @@ func TestSocketClientAddress(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSocketClientPubKey(t *testing.T) {
|
func TestSocketPVPubKey(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
chainID = cmn.RandStr(12)
|
chainID = cmn.RandStr(12)
|
||||||
sc, rs = testSetupSocketPair(t, chainID)
|
sc, rs = testSetupSocketPair(t, chainID)
|
||||||
|
@ -47,11 +45,10 @@ func TestSocketClientPubKey(t *testing.T) {
|
||||||
defer sc.Stop()
|
defer sc.Stop()
|
||||||
defer rs.Stop()
|
defer rs.Stop()
|
||||||
|
|
||||||
clientKey, err := sc.PubKey()
|
clientKey, err := sc.getPubKey()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
privKey, err := rs.privVal.PubKey()
|
privKey := rs.privVal.GetPubKey()
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, privKey, clientKey)
|
assert.Equal(t, privKey, clientKey)
|
||||||
|
|
||||||
|
@ -59,7 +56,7 @@ func TestSocketClientPubKey(t *testing.T) {
|
||||||
assert.Equal(t, privKey, sc.GetPubKey())
|
assert.Equal(t, privKey, sc.GetPubKey())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSocketClientProposal(t *testing.T) {
|
func TestSocketPVProposal(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
chainID = cmn.RandStr(12)
|
chainID = cmn.RandStr(12)
|
||||||
sc, rs = testSetupSocketPair(t, chainID)
|
sc, rs = testSetupSocketPair(t, chainID)
|
||||||
|
@ -76,7 +73,7 @@ func TestSocketClientProposal(t *testing.T) {
|
||||||
assert.Equal(t, privProposal.Signature, clientProposal.Signature)
|
assert.Equal(t, privProposal.Signature, clientProposal.Signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSocketClientVote(t *testing.T) {
|
func TestSocketPVVote(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
chainID = cmn.RandStr(12)
|
chainID = cmn.RandStr(12)
|
||||||
sc, rs = testSetupSocketPair(t, chainID)
|
sc, rs = testSetupSocketPair(t, chainID)
|
||||||
|
@ -94,7 +91,7 @@ func TestSocketClientVote(t *testing.T) {
|
||||||
assert.Equal(t, want.Signature, have.Signature)
|
assert.Equal(t, want.Signature, have.Signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSocketClientHeartbeat(t *testing.T) {
|
func TestSocketPVHeartbeat(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
chainID = cmn.RandStr(12)
|
chainID = cmn.RandStr(12)
|
||||||
sc, rs = testSetupSocketPair(t, chainID)
|
sc, rs = testSetupSocketPair(t, chainID)
|
||||||
|
@ -110,9 +107,9 @@ func TestSocketClientHeartbeat(t *testing.T) {
|
||||||
assert.Equal(t, want.Signature, have.Signature)
|
assert.Equal(t, want.Signature, have.Signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSocketClientAcceptDeadline(t *testing.T) {
|
func TestSocketPVAcceptDeadline(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
sc = NewSocketClient(
|
sc = NewSocketPV(
|
||||||
log.TestingLogger(),
|
log.TestingLogger(),
|
||||||
"127.0.0.1:0",
|
"127.0.0.1:0",
|
||||||
crypto.GenPrivKeyEd25519(),
|
crypto.GenPrivKeyEd25519(),
|
||||||
|
@ -120,26 +117,26 @@ func TestSocketClientAcceptDeadline(t *testing.T) {
|
||||||
)
|
)
|
||||||
defer sc.Stop()
|
defer sc.Stop()
|
||||||
|
|
||||||
SocketClientAcceptDeadline(time.Millisecond)(sc)
|
SocketPVAcceptDeadline(time.Millisecond)(sc)
|
||||||
|
|
||||||
assert.Equal(t, errors.Cause(sc.Start()), ErrConnWaitTimeout)
|
assert.Equal(t, sc.Start().(cmn.Error).Cause(), ErrConnWaitTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSocketClientDeadline(t *testing.T) {
|
func TestSocketPVDeadline(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
addr = testFreeAddr(t)
|
addr = testFreeAddr(t)
|
||||||
listenc = make(chan struct{})
|
listenc = make(chan struct{})
|
||||||
sc = NewSocketClient(
|
sc = NewSocketPV(
|
||||||
log.TestingLogger(),
|
log.TestingLogger(),
|
||||||
addr,
|
addr,
|
||||||
crypto.GenPrivKeyEd25519(),
|
crypto.GenPrivKeyEd25519(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
SocketClientConnDeadline(10 * time.Millisecond)(sc)
|
SocketPVConnDeadline(10 * time.Millisecond)(sc)
|
||||||
SocketClientConnWait(500 * time.Millisecond)(sc)
|
SocketPVConnWait(500 * time.Millisecond)(sc)
|
||||||
|
|
||||||
go func(sc *SocketClient) {
|
go func(sc *SocketPV) {
|
||||||
defer close(listenc)
|
defer close(listenc)
|
||||||
|
|
||||||
require.NoError(t, sc.Start())
|
require.NoError(t, sc.Start())
|
||||||
|
@ -155,7 +152,7 @@ func TestSocketClientDeadline(t *testing.T) {
|
||||||
|
|
||||||
_, err = p2pconn.MakeSecretConnection(
|
_, err = p2pconn.MakeSecretConnection(
|
||||||
conn,
|
conn,
|
||||||
crypto.GenPrivKeyEd25519().Wrap(),
|
crypto.GenPrivKeyEd25519(),
|
||||||
)
|
)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
|
@ -167,21 +164,21 @@ func TestSocketClientDeadline(t *testing.T) {
|
||||||
// Sleep to guarantee deadline has been hit.
|
// Sleep to guarantee deadline has been hit.
|
||||||
time.Sleep(20 * time.Microsecond)
|
time.Sleep(20 * time.Microsecond)
|
||||||
|
|
||||||
_, err := sc.PubKey()
|
_, err := sc.getPubKey()
|
||||||
assert.Equal(t, errors.Cause(err), ErrConnTimeout)
|
assert.Equal(t, err.(cmn.Error).Cause(), ErrConnTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSocketClientWait(t *testing.T) {
|
func TestSocketPVWait(t *testing.T) {
|
||||||
sc := NewSocketClient(
|
sc := NewSocketPV(
|
||||||
log.TestingLogger(),
|
log.TestingLogger(),
|
||||||
"127.0.0.1:0",
|
"127.0.0.1:0",
|
||||||
crypto.GenPrivKeyEd25519(),
|
crypto.GenPrivKeyEd25519(),
|
||||||
)
|
)
|
||||||
defer sc.Stop()
|
defer sc.Stop()
|
||||||
|
|
||||||
SocketClientConnWait(time.Millisecond)(sc)
|
SocketPVConnWait(time.Millisecond)(sc)
|
||||||
|
|
||||||
assert.Equal(t, errors.Cause(sc.Start()), ErrConnWaitTimeout)
|
assert.Equal(t, sc.Start().(cmn.Error).Cause(), ErrConnWaitTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRemoteSignerRetry(t *testing.T) {
|
func TestRemoteSignerRetry(t *testing.T) {
|
||||||
|
@ -216,7 +213,7 @@ func TestRemoteSignerRetry(t *testing.T) {
|
||||||
log.TestingLogger(),
|
log.TestingLogger(),
|
||||||
cmn.RandStr(12),
|
cmn.RandStr(12),
|
||||||
ln.Addr().String(),
|
ln.Addr().String(),
|
||||||
NewTestPrivValidator(types.GenSigner()),
|
types.NewMockPV(),
|
||||||
crypto.GenPrivKeyEd25519(),
|
crypto.GenPrivKeyEd25519(),
|
||||||
)
|
)
|
||||||
defer rs.Stop()
|
defer rs.Stop()
|
||||||
|
@ -224,7 +221,7 @@ func TestRemoteSignerRetry(t *testing.T) {
|
||||||
RemoteSignerConnDeadline(time.Millisecond)(rs)
|
RemoteSignerConnDeadline(time.Millisecond)(rs)
|
||||||
RemoteSignerConnRetries(retries)(rs)
|
RemoteSignerConnRetries(retries)(rs)
|
||||||
|
|
||||||
assert.Equal(t, errors.Cause(rs.Start()), ErrDialRetryMax)
|
assert.Equal(t, rs.Start().(cmn.Error).Cause(), ErrDialRetryMax)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case attempts := <-attemptc:
|
case attempts := <-attemptc:
|
||||||
|
@ -237,12 +234,11 @@ func TestRemoteSignerRetry(t *testing.T) {
|
||||||
func testSetupSocketPair(
|
func testSetupSocketPair(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
chainID string,
|
chainID string,
|
||||||
) (*SocketClient, *RemoteSigner) {
|
) (*SocketPV, *RemoteSigner) {
|
||||||
var (
|
var (
|
||||||
addr = testFreeAddr(t)
|
addr = testFreeAddr(t)
|
||||||
logger = log.TestingLogger()
|
logger = log.TestingLogger()
|
||||||
signer = types.GenSigner()
|
privVal = types.NewMockPV()
|
||||||
privVal = NewTestPrivValidator(signer)
|
|
||||||
readyc = make(chan struct{})
|
readyc = make(chan struct{})
|
||||||
rs = NewRemoteSigner(
|
rs = NewRemoteSigner(
|
||||||
logger,
|
logger,
|
||||||
|
@ -251,14 +247,14 @@ func testSetupSocketPair(
|
||||||
privVal,
|
privVal,
|
||||||
crypto.GenPrivKeyEd25519(),
|
crypto.GenPrivKeyEd25519(),
|
||||||
)
|
)
|
||||||
sc = NewSocketClient(
|
sc = NewSocketPV(
|
||||||
logger,
|
logger,
|
||||||
addr,
|
addr,
|
||||||
crypto.GenPrivKeyEd25519(),
|
crypto.GenPrivKeyEd25519(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
go func(sc *SocketClient) {
|
go func(sc *SocketPV) {
|
||||||
require.NoError(t, sc.Start())
|
require.NoError(t, sc.Start())
|
||||||
assert.True(t, sc.IsRunning())
|
assert.True(t, sc.IsRunning())
|
||||||
|
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
crypto "github.com/tendermint/go-crypto"
|
|
||||||
"github.com/tendermint/tendermint/types"
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------
|
|
||||||
|
|
||||||
var _ types.PrivValidator2 = (*PrivValidatorUnencrypted)(nil)
|
|
||||||
|
|
||||||
// PrivValidatorUnencrypted implements PrivValidator.
|
|
||||||
// It uses an in-memory crypto.PrivKey that is
|
|
||||||
// persisted to disk unencrypted.
|
|
||||||
type PrivValidatorUnencrypted struct {
|
|
||||||
ID types.ValidatorID `json:"id"`
|
|
||||||
PrivKey PrivKey `json:"priv_key"`
|
|
||||||
LastSignedInfo *LastSignedInfo `json:"last_signed_info"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPrivValidatorUnencrypted returns an instance of PrivValidatorUnencrypted.
|
|
||||||
func NewPrivValidatorUnencrypted(priv crypto.PrivKey) *PrivValidatorUnencrypted {
|
|
||||||
return &PrivValidatorUnencrypted{
|
|
||||||
ID: types.ValidatorID{
|
|
||||||
Address: priv.PubKey().Address(),
|
|
||||||
PubKey: priv.PubKey(),
|
|
||||||
},
|
|
||||||
PrivKey: PrivKey(priv),
|
|
||||||
LastSignedInfo: NewLastSignedInfo(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a string representation of the PrivValidatorUnencrypted
|
|
||||||
func (upv *PrivValidatorUnencrypted) String() string {
|
|
||||||
addr, err := upv.Address()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("PrivValidator{%v %v}", addr, upv.LastSignedInfo.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (upv *PrivValidatorUnencrypted) Address() (cmn.HexBytes, error) {
|
|
||||||
return upv.PrivKey.PubKey().Address(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (upv *PrivValidatorUnencrypted) PubKey() (crypto.PubKey, error) {
|
|
||||||
return upv.PrivKey.PubKey(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (upv *PrivValidatorUnencrypted) SignVote(chainID string, vote *types.Vote) error {
|
|
||||||
return upv.LastSignedInfo.SignVote(upv.PrivKey, chainID, vote)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (upv *PrivValidatorUnencrypted) SignProposal(chainID string, proposal *types.Proposal) error {
|
|
||||||
return upv.LastSignedInfo.SignProposal(upv.PrivKey, chainID, proposal)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (upv *PrivValidatorUnencrypted) SignHeartbeat(chainID string, heartbeat *types.Heartbeat) error {
|
|
||||||
var err error
|
|
||||||
heartbeat.Signature, err = upv.PrivKey.Sign(heartbeat.SignBytes(chainID))
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
crypto "github.com/tendermint/go-crypto"
|
|
||||||
"github.com/tendermint/tendermint/types"
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PrivValidatorV1 struct {
|
|
||||||
Address cmn.HexBytes `json:"address"`
|
|
||||||
PubKey crypto.PubKey `json:"pub_key"`
|
|
||||||
LastHeight int64 `json:"last_height"`
|
|
||||||
LastRound int `json:"last_round"`
|
|
||||||
LastStep int8 `json:"last_step"`
|
|
||||||
LastSignature crypto.Signature `json:"last_signature,omitempty"` // so we dont lose signatures
|
|
||||||
LastSignBytes cmn.HexBytes `json:"last_signbytes,omitempty"` // so we dont lose signatures
|
|
||||||
PrivKey crypto.PrivKey `json:"priv_key"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpgradePrivValidator(filePath string) (*PrivValidatorJSON, error) {
|
|
||||||
b, err := ioutil.ReadFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pv := new(PrivValidatorV1)
|
|
||||||
err = json.Unmarshal(b, pv)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pvNew := &PrivValidatorJSON{
|
|
||||||
PrivValidatorUnencrypted: &PrivValidatorUnencrypted{
|
|
||||||
ID: types.ValidatorID{
|
|
||||||
Address: pv.Address,
|
|
||||||
PubKey: pv.PubKey,
|
|
||||||
},
|
|
||||||
PrivKey: PrivKey(pv.PrivKey),
|
|
||||||
LastSignedInfo: &LastSignedInfo{
|
|
||||||
Height: pv.LastHeight,
|
|
||||||
Round: pv.LastRound,
|
|
||||||
Step: pv.LastStep,
|
|
||||||
SignBytes: pv.LastSignBytes,
|
|
||||||
Signature: pv.LastSignature,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err = json.MarshalIndent(pvNew, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ioutil.WriteFile(filePath, b, 0600)
|
|
||||||
return pvNew, err
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
priv_val "github.com/tendermint/tendermint/types/priv_validator"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if len(os.Args) < 2 {
|
|
||||||
fmt.Println("USAGE: priv_val_converter <path to priv_validator.json>")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
file := os.Args[1]
|
|
||||||
_, err := priv_val.UpgradePrivValidator(file)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package privval
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tendermint/go-amino"
|
||||||
|
"github.com/tendermint/go-crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cdc = amino.NewCodec()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
crypto.RegisterAmino(cdc)
|
||||||
|
RegisterSocketPVMsg(cdc)
|
||||||
|
}
|
|
@ -1,255 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
crypto "github.com/tendermint/go-crypto"
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGenLoadValidator(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
_, tempFilePath := cmn.Tempfile("priv_validator_")
|
|
||||||
privVal := GenPrivValidatorFS(tempFilePath)
|
|
||||||
|
|
||||||
height := int64(100)
|
|
||||||
privVal.LastHeight = height
|
|
||||||
privVal.Save()
|
|
||||||
addr := privVal.GetAddress()
|
|
||||||
|
|
||||||
privVal = LoadPrivValidatorFS(tempFilePath)
|
|
||||||
assert.Equal(addr, privVal.GetAddress(), "expected privval addr to be the same")
|
|
||||||
assert.Equal(height, privVal.LastHeight, "expected privval.LastHeight to have been saved")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadOrGenValidator(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
_, tempFilePath := cmn.Tempfile("priv_validator_")
|
|
||||||
if err := os.Remove(tempFilePath); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
privVal := LoadOrGenPrivValidatorFS(tempFilePath)
|
|
||||||
addr := privVal.GetAddress()
|
|
||||||
privVal = LoadOrGenPrivValidatorFS(tempFilePath)
|
|
||||||
assert.Equal(addr, privVal.GetAddress(), "expected privval addr to be the same")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshalValidator(t *testing.T) {
|
|
||||||
assert, require := assert.New(t), require.New(t)
|
|
||||||
|
|
||||||
// create some fixed values
|
|
||||||
addrStr := "D028C9981F7A87F3093672BF0D5B0E2A1B3ED456"
|
|
||||||
pubStr := "3B3069C422E19688B45CBFAE7BB009FC0FA1B1EA86593519318B7214853803C8"
|
|
||||||
privStr := "27F82582AEFAE7AB151CFB01C48BB6C1A0DA78F9BDDA979A9F70A84D074EB07D3B3069C422E19688B45CBFAE7BB009FC0FA1B1EA86593519318B7214853803C8"
|
|
||||||
addrBytes, _ := hex.DecodeString(addrStr)
|
|
||||||
pubBytes, _ := hex.DecodeString(pubStr)
|
|
||||||
privBytes, _ := hex.DecodeString(privStr)
|
|
||||||
|
|
||||||
// prepend type byte
|
|
||||||
pubKey, err := crypto.PubKeyFromBytes(append([]byte{1}, pubBytes...))
|
|
||||||
require.Nil(err, "%+v", err)
|
|
||||||
privKey, err := crypto.PrivKeyFromBytes(append([]byte{1}, privBytes...))
|
|
||||||
require.Nil(err, "%+v", err)
|
|
||||||
|
|
||||||
serialized := fmt.Sprintf(`{
|
|
||||||
"address": "%s",
|
|
||||||
"pub_key": {
|
|
||||||
"type": "ed25519",
|
|
||||||
"data": "%s"
|
|
||||||
},
|
|
||||||
"last_height": 0,
|
|
||||||
"last_round": 0,
|
|
||||||
"last_step": 0,
|
|
||||||
"last_signature": null,
|
|
||||||
"priv_key": {
|
|
||||||
"type": "ed25519",
|
|
||||||
"data": "%s"
|
|
||||||
}
|
|
||||||
}`, addrStr, pubStr, privStr)
|
|
||||||
|
|
||||||
val := PrivValidatorFS{}
|
|
||||||
err = json.Unmarshal([]byte(serialized), &val)
|
|
||||||
require.Nil(err, "%+v", err)
|
|
||||||
|
|
||||||
// make sure the values match
|
|
||||||
assert.EqualValues(addrBytes, val.GetAddress())
|
|
||||||
assert.EqualValues(pubKey, val.GetPubKey())
|
|
||||||
assert.EqualValues(privKey, val.PrivKey)
|
|
||||||
|
|
||||||
// export it and make sure it is the same
|
|
||||||
out, err := json.Marshal(val)
|
|
||||||
require.Nil(err, "%+v", err)
|
|
||||||
assert.JSONEq(serialized, string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSignVote(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
_, tempFilePath := cmn.Tempfile("priv_validator_")
|
|
||||||
privVal := GenPrivValidatorFS(tempFilePath)
|
|
||||||
|
|
||||||
block1 := BlockID{[]byte{1, 2, 3}, PartSetHeader{}}
|
|
||||||
block2 := BlockID{[]byte{3, 2, 1}, PartSetHeader{}}
|
|
||||||
height, round := int64(10), 1
|
|
||||||
voteType := VoteTypePrevote
|
|
||||||
|
|
||||||
// sign a vote for first time
|
|
||||||
vote := newVote(privVal.Address, 0, height, round, voteType, block1)
|
|
||||||
err := privVal.SignVote("mychainid", vote)
|
|
||||||
assert.NoError(err, "expected no error signing vote")
|
|
||||||
|
|
||||||
// try to sign the same vote again; should be fine
|
|
||||||
err = privVal.SignVote("mychainid", vote)
|
|
||||||
assert.NoError(err, "expected no error on signing same vote")
|
|
||||||
|
|
||||||
// now try some bad votes
|
|
||||||
cases := []*Vote{
|
|
||||||
newVote(privVal.Address, 0, height, round-1, voteType, block1), // round regression
|
|
||||||
newVote(privVal.Address, 0, height-1, round, voteType, block1), // height regression
|
|
||||||
newVote(privVal.Address, 0, height-2, round+4, voteType, block1), // height regression and different round
|
|
||||||
newVote(privVal.Address, 0, height, round, voteType, block2), // different block
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
err = privVal.SignVote("mychainid", c)
|
|
||||||
assert.Error(err, "expected error on signing conflicting vote")
|
|
||||||
}
|
|
||||||
|
|
||||||
// try signing a vote with a different time stamp
|
|
||||||
sig := vote.Signature
|
|
||||||
vote.Timestamp = vote.Timestamp.Add(time.Duration(1000))
|
|
||||||
err = privVal.SignVote("mychainid", vote)
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.Equal(sig, vote.Signature)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSignProposal(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
_, tempFilePath := cmn.Tempfile("priv_validator_")
|
|
||||||
privVal := GenPrivValidatorFS(tempFilePath)
|
|
||||||
|
|
||||||
block1 := PartSetHeader{5, []byte{1, 2, 3}}
|
|
||||||
block2 := PartSetHeader{10, []byte{3, 2, 1}}
|
|
||||||
height, round := int64(10), 1
|
|
||||||
|
|
||||||
// sign a proposal for first time
|
|
||||||
proposal := newProposal(height, round, block1)
|
|
||||||
err := privVal.SignProposal("mychainid", proposal)
|
|
||||||
assert.NoError(err, "expected no error signing proposal")
|
|
||||||
|
|
||||||
// try to sign the same proposal again; should be fine
|
|
||||||
err = privVal.SignProposal("mychainid", proposal)
|
|
||||||
assert.NoError(err, "expected no error on signing same proposal")
|
|
||||||
|
|
||||||
// now try some bad Proposals
|
|
||||||
cases := []*Proposal{
|
|
||||||
newProposal(height, round-1, block1), // round regression
|
|
||||||
newProposal(height-1, round, block1), // height regression
|
|
||||||
newProposal(height-2, round+4, block1), // height regression and different round
|
|
||||||
newProposal(height, round, block2), // different block
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
err = privVal.SignProposal("mychainid", c)
|
|
||||||
assert.Error(err, "expected error on signing conflicting proposal")
|
|
||||||
}
|
|
||||||
|
|
||||||
// try signing a proposal with a different time stamp
|
|
||||||
sig := proposal.Signature
|
|
||||||
proposal.Timestamp = proposal.Timestamp.Add(time.Duration(1000))
|
|
||||||
err = privVal.SignProposal("mychainid", proposal)
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.Equal(sig, proposal.Signature)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDifferByTimestamp(t *testing.T) {
|
|
||||||
_, tempFilePath := cmn.Tempfile("priv_validator_")
|
|
||||||
privVal := GenPrivValidatorFS(tempFilePath)
|
|
||||||
|
|
||||||
block1 := PartSetHeader{5, []byte{1, 2, 3}}
|
|
||||||
height, round := int64(10), 1
|
|
||||||
chainID := "mychainid"
|
|
||||||
|
|
||||||
// test proposal
|
|
||||||
{
|
|
||||||
proposal := newProposal(height, round, block1)
|
|
||||||
err := privVal.SignProposal(chainID, proposal)
|
|
||||||
assert.NoError(t, err, "expected no error signing proposal")
|
|
||||||
signBytes := proposal.SignBytes(chainID)
|
|
||||||
sig := proposal.Signature
|
|
||||||
timeStamp := clipToMS(proposal.Timestamp)
|
|
||||||
|
|
||||||
// manipulate the timestamp. should get changed back
|
|
||||||
proposal.Timestamp = proposal.Timestamp.Add(time.Millisecond)
|
|
||||||
var emptySig crypto.Signature
|
|
||||||
proposal.Signature = emptySig
|
|
||||||
err = privVal.SignProposal("mychainid", proposal)
|
|
||||||
assert.NoError(t, err, "expected no error on signing same proposal")
|
|
||||||
|
|
||||||
assert.Equal(t, timeStamp, proposal.Timestamp)
|
|
||||||
assert.Equal(t, signBytes, proposal.SignBytes(chainID))
|
|
||||||
assert.Equal(t, sig, proposal.Signature)
|
|
||||||
}
|
|
||||||
|
|
||||||
// test vote
|
|
||||||
{
|
|
||||||
voteType := VoteTypePrevote
|
|
||||||
blockID := BlockID{[]byte{1, 2, 3}, PartSetHeader{}}
|
|
||||||
vote := newVote(privVal.Address, 0, height, round, voteType, blockID)
|
|
||||||
err := privVal.SignVote("mychainid", vote)
|
|
||||||
assert.NoError(t, err, "expected no error signing vote")
|
|
||||||
|
|
||||||
signBytes := vote.SignBytes(chainID)
|
|
||||||
sig := vote.Signature
|
|
||||||
timeStamp := clipToMS(vote.Timestamp)
|
|
||||||
|
|
||||||
// manipulate the timestamp. should get changed back
|
|
||||||
vote.Timestamp = vote.Timestamp.Add(time.Millisecond)
|
|
||||||
var emptySig crypto.Signature
|
|
||||||
vote.Signature = emptySig
|
|
||||||
err = privVal.SignVote("mychainid", vote)
|
|
||||||
assert.NoError(t, err, "expected no error on signing same vote")
|
|
||||||
|
|
||||||
assert.Equal(t, timeStamp, vote.Timestamp)
|
|
||||||
assert.Equal(t, signBytes, vote.SignBytes(chainID))
|
|
||||||
assert.Equal(t, sig, vote.Signature)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newVote(addr Address, idx int, height int64, round int, typ byte, blockID BlockID) *Vote {
|
|
||||||
return &Vote{
|
|
||||||
ValidatorAddress: addr,
|
|
||||||
ValidatorIndex: idx,
|
|
||||||
Height: height,
|
|
||||||
Round: round,
|
|
||||||
Type: typ,
|
|
||||||
Timestamp: time.Now().UTC(),
|
|
||||||
BlockID: blockID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newProposal(height int64, round int, partsHeader PartSetHeader) *Proposal {
|
|
||||||
return &Proposal{
|
|
||||||
Height: height,
|
|
||||||
Round: round,
|
|
||||||
BlockPartsHeader: partsHeader,
|
|
||||||
Timestamp: time.Now().UTC(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func clipToMS(t time.Time) time.Time {
|
|
||||||
nano := t.UnixNano()
|
|
||||||
million := int64(1000000)
|
|
||||||
nano = (nano / million) * million
|
|
||||||
return time.Unix(0, nano).UTC()
|
|
||||||
}
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tendermint/go-crypto"
|
"github.com/tendermint/go-crypto"
|
||||||
"github.com/tendermint/tendermint/wire"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -51,10 +50,7 @@ func (p *Proposal) String() string {
|
||||||
|
|
||||||
// SignBytes returns the Proposal bytes for signing
|
// SignBytes returns the Proposal bytes for signing
|
||||||
func (p *Proposal) SignBytes(chainID string) []byte {
|
func (p *Proposal) SignBytes(chainID string) []byte {
|
||||||
bz, err := wire.MarshalJSON(CanonicalJSONOnceProposal{
|
bz, err := cdc.MarshalJSON(CanonicalProposal(chainID, p))
|
||||||
ChainID: chainID,
|
|
||||||
Proposal: CanonicalProposal(p),
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
wire "github.com/tendermint/tendermint/wire"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var testProposal *Proposal
|
var testProposal *Proposal
|
||||||
|
@ -29,7 +27,7 @@ func TestProposalSignable(t *testing.T) {
|
||||||
signBytes := testProposal.SignBytes("test_chain_id")
|
signBytes := testProposal.SignBytes("test_chain_id")
|
||||||
signStr := string(signBytes)
|
signStr := string(signBytes)
|
||||||
|
|
||||||
expected := `{"chain_id":"test_chain_id","proposal":{"block_parts_header":{"hash":"626C6F636B7061727473","total":111},"height":12345,"pol_block_id":{},"pol_round":-1,"round":23456,"timestamp":"2018-02-11T07:09:22.765Z"}}`
|
expected := `{"@chain_id":"test_chain_id","@type":"proposal","block_parts_header":{"hash":"626C6F636B7061727473","total":111},"height":12345,"pol_block_id":{},"pol_round":-1,"round":23456,"timestamp":"2018-02-11T07:09:22.765Z"}`
|
||||||
if signStr != expected {
|
if signStr != expected {
|
||||||
t.Errorf("Got unexpected sign string for Proposal. Expected:\n%v\nGot:\n%v", expected, signStr)
|
t.Errorf("Got unexpected sign string for Proposal. Expected:\n%v\nGot:\n%v", expected, signStr)
|
||||||
}
|
}
|
||||||
|
@ -37,38 +35,38 @@ func TestProposalSignable(t *testing.T) {
|
||||||
|
|
||||||
func TestProposalString(t *testing.T) {
|
func TestProposalString(t *testing.T) {
|
||||||
str := testProposal.String()
|
str := testProposal.String()
|
||||||
expected := `Proposal{12345/23456 111:626C6F636B70 (-1,:0:000000000000) {<nil>} @ 2018-02-11T07:09:22.765Z}`
|
expected := `Proposal{12345/23456 111:626C6F636B70 (-1,:0:000000000000) <nil> @ 2018-02-11T07:09:22.765Z}`
|
||||||
if str != expected {
|
if str != expected {
|
||||||
t.Errorf("Got unexpected string for Proposal. Expected:\n%v\nGot:\n%v", expected, str)
|
t.Errorf("Got unexpected string for Proposal. Expected:\n%v\nGot:\n%v", expected, str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProposalVerifySignature(t *testing.T) {
|
func TestProposalVerifySignature(t *testing.T) {
|
||||||
privVal := GenPrivValidatorFS("")
|
privVal := NewMockPV()
|
||||||
pubKey := privVal.GetPubKey()
|
pubKey := privVal.GetPubKey()
|
||||||
|
|
||||||
prop := NewProposal(4, 2, PartSetHeader{777, []byte("proper")}, 2, BlockID{})
|
prop := NewProposal(4, 2, PartSetHeader{777, []byte("proper")}, 2, BlockID{})
|
||||||
signBytes := prop.SignBytes("test_chain_id")
|
signBytes := prop.SignBytes("test_chain_id")
|
||||||
|
|
||||||
// sign it
|
// sign it
|
||||||
signature, err := privVal.Signer.Sign(signBytes)
|
err := privVal.SignProposal("test_chain_id", prop)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// verify the same proposal
|
// verify the same proposal
|
||||||
valid := pubKey.VerifyBytes(prop.SignBytes("test_chain_id"), signature)
|
valid := pubKey.VerifyBytes(signBytes, prop.Signature)
|
||||||
require.True(t, valid)
|
require.True(t, valid)
|
||||||
|
|
||||||
// serialize, deserialize and verify again....
|
// serialize, deserialize and verify again....
|
||||||
newProp := new(Proposal)
|
newProp := new(Proposal)
|
||||||
bs, err := wire.MarshalBinary(prop)
|
bs, err := cdc.MarshalBinary(prop)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = wire.UnmarshalBinary(bs, &newProp)
|
err = cdc.UnmarshalBinary(bs, &newProp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// verify the transmitted proposal
|
// verify the transmitted proposal
|
||||||
newSignBytes := newProp.SignBytes("test_chain_id")
|
newSignBytes := newProp.SignBytes("test_chain_id")
|
||||||
require.Equal(t, string(signBytes), string(newSignBytes))
|
require.Equal(t, string(signBytes), string(newSignBytes))
|
||||||
valid = pubKey.VerifyBytes(newSignBytes, signature)
|
valid = pubKey.VerifyBytes(newSignBytes, newProp.Signature)
|
||||||
require.True(t, valid)
|
require.True(t, valid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,9 +77,9 @@ func BenchmarkProposalWriteSignBytes(b *testing.B) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkProposalSign(b *testing.B) {
|
func BenchmarkProposalSign(b *testing.B) {
|
||||||
privVal := GenPrivValidatorFS("")
|
privVal := NewMockPV()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, err := privVal.Signer.Sign(testProposal.SignBytes("test_chain_id"))
|
err := privVal.SignProposal("test_chain_id", testProposal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Error(err)
|
b.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -89,12 +87,12 @@ func BenchmarkProposalSign(b *testing.B) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkProposalVerifySignature(b *testing.B) {
|
func BenchmarkProposalVerifySignature(b *testing.B) {
|
||||||
signBytes := testProposal.SignBytes("test_chain_id")
|
privVal := NewMockPV()
|
||||||
privVal := GenPrivValidatorFS("")
|
err := privVal.SignProposal("test_chain_id", testProposal)
|
||||||
signature, _ := privVal.Signer.Sign(signBytes)
|
require.Nil(b, err)
|
||||||
pubKey := privVal.GetPubKey()
|
pubKey := privVal.GetPubKey()
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
pubKey.VerifyBytes(testProposal.SignBytes("test_chain_id"), signature)
|
pubKey.VerifyBytes(testProposal.SignBytes("test_chain_id"), testProposal.Signature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
abci "github.com/tendermint/abci/types"
|
abci "github.com/tendermint/abci/types"
|
||||||
wire "github.com/tendermint/tendermint/wire"
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
"github.com/tendermint/tmlibs/merkle"
|
"github.com/tendermint/tmlibs/merkle"
|
||||||
)
|
)
|
||||||
|
@ -18,7 +17,8 @@ type ABCIResult struct {
|
||||||
|
|
||||||
// Hash returns the canonical hash of the ABCIResult
|
// Hash returns the canonical hash of the ABCIResult
|
||||||
func (a ABCIResult) Hash() []byte {
|
func (a ABCIResult) Hash() []byte {
|
||||||
return tmHash(a)
|
bz := aminoHash(a)
|
||||||
|
return bz
|
||||||
}
|
}
|
||||||
|
|
||||||
// ABCIResults wraps the deliver tx results to return a proof
|
// ABCIResults wraps the deliver tx results to return a proof
|
||||||
|
@ -42,7 +42,7 @@ func NewResultFromResponse(response *abci.ResponseDeliverTx) ABCIResult {
|
||||||
|
|
||||||
// Bytes serializes the ABCIResponse using wire
|
// Bytes serializes the ABCIResponse using wire
|
||||||
func (a ABCIResults) Bytes() []byte {
|
func (a ABCIResults) Bytes() []byte {
|
||||||
bz, err := wire.MarshalBinary(a)
|
bz, err := cdc.MarshalBinary(a)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestABCIResults(t *testing.T) {
|
func TestABCIResults(t *testing.T) {
|
||||||
|
@ -14,13 +15,15 @@ func TestABCIResults(t *testing.T) {
|
||||||
e := ABCIResult{Code: 14, Data: []byte("foo")}
|
e := ABCIResult{Code: 14, Data: []byte("foo")}
|
||||||
f := ABCIResult{Code: 14, Data: []byte("bar")}
|
f := ABCIResult{Code: 14, Data: []byte("bar")}
|
||||||
|
|
||||||
// nil and []byte{} should produce same hash
|
// Nil and []byte{} should not produce the same hash.
|
||||||
assert.Equal(t, a.Hash(), b.Hash())
|
require.Equal(t, a.Hash(), a.Hash())
|
||||||
|
require.Equal(t, b.Hash(), b.Hash())
|
||||||
|
require.NotEqual(t, a.Hash(), b.Hash())
|
||||||
|
|
||||||
// a and b should be the same, don't go in results
|
// a and b should be the same, don't go in results.
|
||||||
results := ABCIResults{a, c, d, e, f}
|
results := ABCIResults{a, c, d, e, f}
|
||||||
|
|
||||||
// make sure each result hashes properly
|
// Make sure each result hashes properly.
|
||||||
var last []byte
|
var last []byte
|
||||||
for i, res := range results {
|
for i, res := range results {
|
||||||
h := res.Hash()
|
h := res.Hash()
|
||||||
|
@ -28,8 +31,7 @@ func TestABCIResults(t *testing.T) {
|
||||||
last = h
|
last = h
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure that we can get a root hash from results
|
// Make sure that we can get a root hash from results and verify proofs.
|
||||||
// and verify proofs
|
|
||||||
root := results.Hash()
|
root := results.Hash()
|
||||||
assert.NotEmpty(t, root)
|
assert.NotEmpty(t, root)
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,3 @@ package types
|
||||||
type Signable interface {
|
type Signable interface {
|
||||||
SignBytes(chainID string) []byte
|
SignBytes(chainID string) []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashSignBytes is a convenience method for getting the hash of the bytes of a signable
|
|
||||||
func HashSignBytes(chainID string, o Signable) []byte {
|
|
||||||
return tmHash(o.SignBytes(chainID))
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import "time"
|
||||||
|
|
||||||
func MakeCommit(blockID BlockID, height int64, round int,
|
func MakeCommit(blockID BlockID, height int64, round int,
|
||||||
voteSet *VoteSet,
|
voteSet *VoteSet,
|
||||||
validators []*PrivValidatorFS) (*Commit, error) {
|
validators []PrivValidator) (*Commit, error) {
|
||||||
|
|
||||||
// all sign
|
// all sign
|
||||||
for i := 0; i < len(validators); i++ {
|
for i := 0; i < len(validators); i++ {
|
||||||
|
@ -28,8 +28,8 @@ func MakeCommit(blockID BlockID, height int64, round int,
|
||||||
return voteSet.MakeCommit(), nil
|
return voteSet.MakeCommit(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func signAddVote(privVal *PrivValidatorFS, vote *Vote, voteSet *VoteSet) (signed bool, err error) {
|
func signAddVote(privVal PrivValidator, vote *Vote, voteSet *VoteSet) (signed bool, err error) {
|
||||||
vote.Signature, err = privVal.Signer.Sign(vote.SignBytes(voteSet.ChainID()))
|
err = privVal.SignVote(voteSet.ChainID(), vote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
13
types/tx.go
13
types/tx.go
|
@ -18,7 +18,7 @@ type Tx []byte
|
||||||
|
|
||||||
// Hash computes the RIPEMD160 hash of the wire encoded transaction.
|
// Hash computes the RIPEMD160 hash of the wire encoded transaction.
|
||||||
func (tx Tx) Hash() []byte {
|
func (tx Tx) Hash() []byte {
|
||||||
return wireHasher(tx).Hash()
|
return aminoHasher(tx).Hash()
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the hex-encoded transaction as a string.
|
// String returns the hex-encoded transaction as a string.
|
||||||
|
@ -66,9 +66,7 @@ func (txs Txs) IndexByHash(hash []byte) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proof returns a simple merkle proof for this node.
|
// Proof returns a simple merkle proof for this node.
|
||||||
//
|
|
||||||
// Panics if i < 0 or i >= len(txs)
|
// Panics if i < 0 or i >= len(txs)
|
||||||
//
|
|
||||||
// TODO: optimize this!
|
// TODO: optimize this!
|
||||||
func (txs Txs) Proof(i int) TxProof {
|
func (txs Txs) Proof(i int) TxProof {
|
||||||
l := len(txs)
|
l := len(txs)
|
||||||
|
@ -95,7 +93,7 @@ type TxProof struct {
|
||||||
Proof merkle.SimpleProof
|
Proof merkle.SimpleProof
|
||||||
}
|
}
|
||||||
|
|
||||||
// LeadHash returns the hash of the transaction this proof refers to.
|
// LeadHash returns the hash of the this proof refers to.
|
||||||
func (tp TxProof) LeafHash() []byte {
|
func (tp TxProof) LeafHash() []byte {
|
||||||
return tp.Data.Hash()
|
return tp.Data.Hash()
|
||||||
}
|
}
|
||||||
|
@ -106,7 +104,12 @@ func (tp TxProof) Validate(dataHash []byte) error {
|
||||||
if !bytes.Equal(dataHash, tp.RootHash) {
|
if !bytes.Equal(dataHash, tp.RootHash) {
|
||||||
return errors.New("Proof matches different data hash")
|
return errors.New("Proof matches different data hash")
|
||||||
}
|
}
|
||||||
|
if tp.Index < 0 {
|
||||||
|
return errors.New("Proof index cannot be negative")
|
||||||
|
}
|
||||||
|
if tp.Total <= 0 {
|
||||||
|
return errors.New("Proof total must be positive")
|
||||||
|
}
|
||||||
valid := tp.Proof.Verify(tp.Index, tp.Total, tp.LeafHash(), tp.RootHash)
|
valid := tp.Proof.Verify(tp.Index, tp.Total, tp.LeafHash(), tp.RootHash)
|
||||||
if !valid {
|
if !valid {
|
||||||
return errors.New("Proof is not internally consistent")
|
return errors.New("Proof is not internally consistent")
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
wire "github.com/tendermint/tendermint/wire"
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
ctest "github.com/tendermint/tmlibs/test"
|
ctest "github.com/tendermint/tmlibs/test"
|
||||||
)
|
)
|
||||||
|
@ -69,9 +68,9 @@ func TestValidTxProof(t *testing.T) {
|
||||||
|
|
||||||
// read-write must also work
|
// read-write must also work
|
||||||
var p2 TxProof
|
var p2 TxProof
|
||||||
bin, err := wire.MarshalBinary(proof)
|
bin, err := cdc.MarshalBinary(proof)
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
err = wire.UnmarshalBinary(bin, &p2)
|
err = cdc.UnmarshalBinary(bin, &p2)
|
||||||
if assert.Nil(err, "%d: %d: %+v", h, i, err) {
|
if assert.Nil(err, "%d: %d: %+v", h, i, err) {
|
||||||
assert.Nil(p2.Validate(root), "%d: %d", h, i)
|
assert.Nil(p2.Validate(root), "%d: %d", h, i)
|
||||||
}
|
}
|
||||||
|
@ -97,7 +96,7 @@ func testTxProofUnchangable(t *testing.T) {
|
||||||
|
|
||||||
// make sure it is valid to start with
|
// make sure it is valid to start with
|
||||||
assert.Nil(proof.Validate(root))
|
assert.Nil(proof.Validate(root))
|
||||||
bin, err := wire.MarshalBinary(proof)
|
bin, err := cdc.MarshalBinary(proof)
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
|
|
||||||
// try mutating the data and make sure nothing breaks
|
// try mutating the data and make sure nothing breaks
|
||||||
|
@ -109,16 +108,17 @@ func testTxProofUnchangable(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this make sure the proof doesn't deserialize into something valid
|
// This makes sure that the proof doesn't deserialize into something valid.
|
||||||
func assertBadProof(t *testing.T, root []byte, bad []byte, good TxProof) {
|
func assertBadProof(t *testing.T, root []byte, bad []byte, good TxProof) {
|
||||||
var proof TxProof
|
var proof TxProof
|
||||||
err := wire.UnmarshalBinary(bad, &proof)
|
err := cdc.UnmarshalBinary(bad, &proof)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = proof.Validate(root)
|
err = proof.Validate(root)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// okay, this can happen if we have a slightly different total
|
// XXX Fix simple merkle proofs so the following is *not* OK.
|
||||||
// (where the path ends up the same), if it is something else, we have
|
// This can happen if we have a slightly different total (where the
|
||||||
// a real problem
|
// path ends up the same). If it is something else, we have a real
|
||||||
|
// problem.
|
||||||
assert.NotEqual(t, proof.Total, good.Total, "bad: %#v\ngood: %#v", proof, good)
|
assert.NotEqual(t, proof.Total, good.Total, "bad: %#v\ngood: %#v", proof, good)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ func (v *Validator) String() string {
|
||||||
// Hash computes the unique ID of a validator with a given voting power.
|
// Hash computes the unique ID of a validator with a given voting power.
|
||||||
// It excludes the Accum value, which changes with every round.
|
// It excludes the Accum value, which changes with every round.
|
||||||
func (v *Validator) Hash() []byte {
|
func (v *Validator) Hash() []byte {
|
||||||
return tmHash(struct {
|
return aminoHash(struct {
|
||||||
Address Address
|
Address Address
|
||||||
PubKey crypto.PubKey
|
PubKey crypto.PubKey
|
||||||
VotingPower int64
|
VotingPower int64
|
||||||
|
@ -81,14 +81,13 @@ func (v *Validator) Hash() []byte {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------
|
//----------------------------------------
|
||||||
// For testing...
|
// RandValidator
|
||||||
|
|
||||||
// RandValidator returns a randomized validator, useful for testing.
|
// RandValidator returns a randomized validator, useful for testing.
|
||||||
// UNSTABLE
|
// UNSTABLE
|
||||||
func RandValidator(randPower bool, minPower int64) (*Validator, *PrivValidatorFS) {
|
func RandValidator(randPower bool, minPower int64) (*Validator, PrivValidator) {
|
||||||
_, tempFilePath := cmn.Tempfile("priv_validator_")
|
privVal := NewMockPV()
|
||||||
privVal := GenPrivValidatorFS(tempFilePath)
|
|
||||||
votePower := minPower
|
votePower := minPower
|
||||||
if randPower {
|
if randPower {
|
||||||
votePower += int64(cmn.RandUint32())
|
votePower += int64(cmn.RandUint32())
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
"github.com/tendermint/tmlibs/merkle"
|
"github.com/tendermint/tmlibs/merkle"
|
||||||
)
|
)
|
||||||
|
@ -289,10 +288,10 @@ func (valSet *ValidatorSet) VerifyCommitAny(newSet *ValidatorSet, chainID string
|
||||||
blockID BlockID, height int64, commit *Commit) error {
|
blockID BlockID, height int64, commit *Commit) error {
|
||||||
|
|
||||||
if newSet.Size() != len(commit.Precommits) {
|
if newSet.Size() != len(commit.Precommits) {
|
||||||
return errors.Errorf("Invalid commit -- wrong set size: %v vs %v", newSet.Size(), len(commit.Precommits))
|
return cmn.NewError("Invalid commit -- wrong set size: %v vs %v", newSet.Size(), len(commit.Precommits))
|
||||||
}
|
}
|
||||||
if height != commit.Height() {
|
if height != commit.Height() {
|
||||||
return errors.Errorf("Invalid commit -- wrong height: %v vs %v", height, commit.Height())
|
return cmn.NewError("Invalid commit -- wrong height: %v vs %v", height, commit.Height())
|
||||||
}
|
}
|
||||||
|
|
||||||
oldVotingPower := int64(0)
|
oldVotingPower := int64(0)
|
||||||
|
@ -307,13 +306,13 @@ func (valSet *ValidatorSet) VerifyCommitAny(newSet *ValidatorSet, chainID string
|
||||||
}
|
}
|
||||||
if precommit.Height != height {
|
if precommit.Height != height {
|
||||||
// return certerr.ErrHeightMismatch(height, precommit.Height)
|
// return certerr.ErrHeightMismatch(height, precommit.Height)
|
||||||
return errors.Errorf("Blocks don't match - %d vs %d", round, precommit.Round)
|
return cmn.NewError("Blocks don't match - %d vs %d", round, precommit.Round)
|
||||||
}
|
}
|
||||||
if precommit.Round != round {
|
if precommit.Round != round {
|
||||||
return errors.Errorf("Invalid commit -- wrong round: %v vs %v", round, precommit.Round)
|
return cmn.NewError("Invalid commit -- wrong round: %v vs %v", round, precommit.Round)
|
||||||
}
|
}
|
||||||
if precommit.Type != VoteTypePrecommit {
|
if precommit.Type != VoteTypePrecommit {
|
||||||
return errors.Errorf("Invalid commit -- not precommit @ index %v", idx)
|
return cmn.NewError("Invalid commit -- not precommit @ index %v", idx)
|
||||||
}
|
}
|
||||||
if !blockID.Equals(precommit.BlockID) {
|
if !blockID.Equals(precommit.BlockID) {
|
||||||
continue // Not an error, but doesn't count
|
continue // Not an error, but doesn't count
|
||||||
|
@ -329,7 +328,7 @@ func (valSet *ValidatorSet) VerifyCommitAny(newSet *ValidatorSet, chainID string
|
||||||
// Validate signature old school
|
// Validate signature old school
|
||||||
precommitSignBytes := precommit.SignBytes(chainID)
|
precommitSignBytes := precommit.SignBytes(chainID)
|
||||||
if !ov.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) {
|
if !ov.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) {
|
||||||
return errors.Errorf("Invalid commit -- invalid signature: %v", precommit)
|
return cmn.NewError("Invalid commit -- invalid signature: %v", precommit)
|
||||||
}
|
}
|
||||||
// Good precommit!
|
// Good precommit!
|
||||||
oldVotingPower += ov.VotingPower
|
oldVotingPower += ov.VotingPower
|
||||||
|
@ -343,10 +342,10 @@ func (valSet *ValidatorSet) VerifyCommitAny(newSet *ValidatorSet, chainID string
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldVotingPower <= valSet.TotalVotingPower()*2/3 {
|
if oldVotingPower <= valSet.TotalVotingPower()*2/3 {
|
||||||
return errors.Errorf("Invalid commit -- insufficient old voting power: got %v, needed %v",
|
return cmn.NewError("Invalid commit -- insufficient old voting power: got %v, needed %v",
|
||||||
oldVotingPower, (valSet.TotalVotingPower()*2/3 + 1))
|
oldVotingPower, (valSet.TotalVotingPower()*2/3 + 1))
|
||||||
} else if newVotingPower <= newSet.TotalVotingPower()*2/3 {
|
} else if newVotingPower <= newSet.TotalVotingPower()*2/3 {
|
||||||
return errors.Errorf("Invalid commit -- insufficient cur voting power: got %v, needed %v",
|
return cmn.NewError("Invalid commit -- insufficient cur voting power: got %v, needed %v",
|
||||||
newVotingPower, (newSet.TotalVotingPower()*2/3 + 1))
|
newVotingPower, (newSet.TotalVotingPower()*2/3 + 1))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -416,9 +415,9 @@ func (ac accumComparable) Less(o interface{}) bool {
|
||||||
// RandValidatorSet returns a randomized validator set, useful for testing.
|
// RandValidatorSet returns a randomized validator set, useful for testing.
|
||||||
// NOTE: PrivValidator are in order.
|
// NOTE: PrivValidator are in order.
|
||||||
// UNSTABLE
|
// UNSTABLE
|
||||||
func RandValidatorSet(numValidators int, votingPower int64) (*ValidatorSet, []*PrivValidatorFS) {
|
func RandValidatorSet(numValidators int, votingPower int64) (*ValidatorSet, []PrivValidator) {
|
||||||
vals := make([]*Validator, numValidators)
|
vals := make([]*Validator, numValidators)
|
||||||
privValidators := make([]*PrivValidatorFS, numValidators)
|
privValidators := make([]PrivValidator, numValidators)
|
||||||
for i := 0; i < numValidators; i++ {
|
for i := 0; i < numValidators; i++ {
|
||||||
val, privValidator := RandValidator(false, votingPower)
|
val, privValidator := RandValidator(false, votingPower)
|
||||||
vals[i] = val
|
vals[i] = val
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
crypto "github.com/tendermint/go-crypto"
|
crypto "github.com/tendermint/go-crypto"
|
||||||
wire "github.com/tendermint/tendermint/wire"
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -198,7 +197,7 @@ func newValidator(address []byte, power int64) *Validator {
|
||||||
func randPubKey() crypto.PubKey {
|
func randPubKey() crypto.PubKey {
|
||||||
var pubKey [32]byte
|
var pubKey [32]byte
|
||||||
copy(pubKey[:], cmn.RandBytes(32))
|
copy(pubKey[:], cmn.RandBytes(32))
|
||||||
return crypto.PubKeyEd25519(pubKey).Wrap()
|
return crypto.PubKeyEd25519(pubKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func randValidator_() *Validator {
|
func randValidator_() *Validator {
|
||||||
|
@ -216,7 +215,7 @@ func randValidatorSet(numValidators int) *ValidatorSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (valSet *ValidatorSet) toBytes() []byte {
|
func (valSet *ValidatorSet) toBytes() []byte {
|
||||||
bz, err := wire.MarshalBinary(valSet)
|
bz, err := cdc.MarshalBinary(valSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -224,7 +223,7 @@ func (valSet *ValidatorSet) toBytes() []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (valSet *ValidatorSet) fromBytes(b []byte) {
|
func (valSet *ValidatorSet) fromBytes(b []byte) {
|
||||||
err := wire.UnmarshalBinary(b, &valSet)
|
err := cdc.UnmarshalBinary(b, &valSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
|
// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
crypto "github.com/tendermint/go-crypto"
|
crypto "github.com/tendermint/go-crypto"
|
||||||
"github.com/tendermint/tendermint/wire"
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -73,10 +72,7 @@ type Vote struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vote *Vote) SignBytes(chainID string) []byte {
|
func (vote *Vote) SignBytes(chainID string) []byte {
|
||||||
bz, err := wire.MarshalJSON(CanonicalJSONOnceVote{
|
bz, err := cdc.MarshalJSON(CanonicalVote(chainID, vote))
|
||||||
chainID,
|
|
||||||
CanonicalVote(vote),
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NOTE: privValidators are in order
|
// NOTE: privValidators are in order
|
||||||
func randVoteSet(height int64, round int, type_ byte, numValidators int, votingPower int64) (*VoteSet, *ValidatorSet, []*PrivValidatorFS) {
|
func randVoteSet(height int64, round int, type_ byte, numValidators int, votingPower int64) (*VoteSet, *ValidatorSet, []PrivValidator) {
|
||||||
valSet, privValidators := RandValidatorSet(numValidators, votingPower)
|
valSet, privValidators := RandValidatorSet(numValidators, votingPower)
|
||||||
return NewVoteSet("test_chain_id", height, round, type_, valSet), valSet, privValidators
|
return NewVoteSet("test_chain_id", height, round, type_, valSet), valSet, privValidators
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
wire "github.com/tendermint/tendermint/wire"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func examplePrevote() *Vote {
|
func examplePrevote() *Vote {
|
||||||
|
@ -45,7 +43,7 @@ func TestVoteSignable(t *testing.T) {
|
||||||
signBytes := vote.SignBytes("test_chain_id")
|
signBytes := vote.SignBytes("test_chain_id")
|
||||||
signStr := string(signBytes)
|
signStr := string(signBytes)
|
||||||
|
|
||||||
expected := `{"chain_id":"test_chain_id","vote":{"block_id":{"hash":"68617368","parts":{"hash":"70617274735F68617368","total":1000000}},"height":12345,"round":2,"timestamp":"2017-12-25T03:00:01.234Z","type":2}}`
|
expected := `{"@chain_id":"test_chain_id","@type":"vote","block_id":{"hash":"68617368","parts":{"hash":"70617274735F68617368","total":1000000}},"height":12345,"round":2,"timestamp":"2017-12-25T03:00:01.234Z","type":2}`
|
||||||
if signStr != expected {
|
if signStr != expected {
|
||||||
// NOTE: when this fails, you probably want to fix up consensus/replay_test too
|
// NOTE: when this fails, you probably want to fix up consensus/replay_test too
|
||||||
t.Errorf("Got unexpected sign string for Vote. Expected:\n%v\nGot:\n%v", expected, signStr)
|
t.Errorf("Got unexpected sign string for Vote. Expected:\n%v\nGot:\n%v", expected, signStr)
|
||||||
|
@ -58,8 +56,8 @@ func TestVoteString(t *testing.T) {
|
||||||
in string
|
in string
|
||||||
out string
|
out string
|
||||||
}{
|
}{
|
||||||
{"Precommit", examplePrecommit().String(), `Vote{56789:616464720000 12345/02/2(Precommit) 686173680000 {<nil>} @ 2017-12-25T03:00:01.234Z}`},
|
{"Precommit", examplePrecommit().String(), `Vote{56789:616464720000 12345/02/2(Precommit) 686173680000 <nil> @ 2017-12-25T03:00:01.234Z}`},
|
||||||
{"Prevote", examplePrevote().String(), `Vote{56789:616464720000 12345/02/1(Prevote) 686173680000 {<nil>} @ 2017-12-25T03:00:01.234Z}`},
|
{"Prevote", examplePrevote().String(), `Vote{56789:616464720000 12345/02/1(Prevote) 686173680000 <nil> @ 2017-12-25T03:00:01.234Z}`},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tc {
|
for _, tt := range tc {
|
||||||
|
@ -73,31 +71,31 @@ func TestVoteString(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVoteVerifySignature(t *testing.T) {
|
func TestVoteVerifySignature(t *testing.T) {
|
||||||
privVal := GenPrivValidatorFS("")
|
privVal := NewMockPV()
|
||||||
pubKey := privVal.GetPubKey()
|
pubKey := privVal.GetPubKey()
|
||||||
|
|
||||||
vote := examplePrecommit()
|
vote := examplePrecommit()
|
||||||
signBytes := vote.SignBytes("test_chain_id")
|
signBytes := vote.SignBytes("test_chain_id")
|
||||||
|
|
||||||
// sign it
|
// sign it
|
||||||
signature, err := privVal.Signer.Sign(signBytes)
|
err := privVal.SignVote("test_chain_id", vote)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// verify the same vote
|
// verify the same vote
|
||||||
valid := pubKey.VerifyBytes(vote.SignBytes("test_chain_id"), signature)
|
valid := pubKey.VerifyBytes(vote.SignBytes("test_chain_id"), vote.Signature)
|
||||||
require.True(t, valid)
|
require.True(t, valid)
|
||||||
|
|
||||||
// serialize, deserialize and verify again....
|
// serialize, deserialize and verify again....
|
||||||
precommit := new(Vote)
|
precommit := new(Vote)
|
||||||
bs, err := wire.MarshalBinary(vote)
|
bs, err := cdc.MarshalBinary(vote)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = wire.UnmarshalBinary(bs, &precommit)
|
err = cdc.UnmarshalBinary(bs, &precommit)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// verify the transmitted vote
|
// verify the transmitted vote
|
||||||
newSignBytes := precommit.SignBytes("test_chain_id")
|
newSignBytes := precommit.SignBytes("test_chain_id")
|
||||||
require.Equal(t, string(signBytes), string(newSignBytes))
|
require.Equal(t, string(signBytes), string(newSignBytes))
|
||||||
valid = pubKey.VerifyBytes(newSignBytes, signature)
|
valid = pubKey.VerifyBytes(newSignBytes, precommit.Signature)
|
||||||
require.True(t, valid)
|
require.True(t, valid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tendermint/go-amino"
|
||||||
|
"github.com/tendermint/go-crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cdc = amino.NewCodec()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
crypto.RegisterAmino(cdc)
|
||||||
|
}
|
Loading…
Reference in New Issue