Browse Source

Make types use Amino; Refactor PrivValidator* to FilePV/SocketPV

pull/1347/head
Jae Kwon 6 years ago
parent
commit
34974e3932
40 changed files with 849 additions and 1892 deletions
  1. +29
    -128
      docs/architecture/adr-008-priv-validator.md
  2. +19
    -20
      types/block.go
  3. +1
    -2
      types/block_test.go
  4. +28
    -34
      types/canonical_json.go
  5. +46
    -52
      types/evidence.go
  6. +13
    -11
      types/evidence_test.go
  7. +8
    -10
      types/genesis.go
  8. +18
    -19
      types/genesis_test.go
  9. +2
    -6
      types/heartbeat.go
  10. +5
    -4
      types/heartbeat_test.go
  11. +10
    -11
      types/params.go
  12. +39
    -422
      types/priv_validator.go
  13. +0
    -197
      types/priv_validator/json.go
  14. +345
    -0
      types/priv_validator/priv_validator.go
  15. +61
    -92
      types/priv_validator/priv_validator_test.go
  16. +0
    -238
      types/priv_validator/sign_info.go
  17. +90
    -116
      types/priv_validator/socket.go
  18. +1
    -1
      types/priv_validator/socket_tcp.go
  19. +1
    -1
      types/priv_validator/socket_tcp_test.go
  20. +33
    -37
      types/priv_validator/socket_test.go
  21. +0
    -66
      types/priv_validator/unencrypted.go
  22. +0
    -59
      types/priv_validator/upgrade.go
  23. +0
    -21
      types/priv_validator/upgrade_pv/main.go
  24. +13
    -0
      types/priv_validator/wire.go
  25. +0
    -255
      types/priv_validator_test.go
  26. +1
    -5
      types/proposal.go
  27. +14
    -16
      types/proposal_test.go
  28. +3
    -3
      types/results.go
  29. +8
    -6
      types/results_test.go
  30. +0
    -5
      types/signable.go
  31. +3
    -3
      types/test_util.go
  32. +8
    -5
      types/tx.go
  33. +9
    -9
      types/tx_test.go
  34. +5
    -6
      types/validator.go
  35. +10
    -11
      types/validator_set.go
  36. +3
    -4
      types/validator_set_test.go
  37. +1
    -5
      types/vote.go
  38. +1
    -1
      types/vote_set_test.go
  39. +9
    -11
      types/vote_test.go
  40. +12
    -0
      types/wire.go

+ 29
- 128
docs/architecture/adr-008-priv-validator.md View File

@ -1,128 +1,29 @@
# ADR 008: PrivValidator
## Context
The current PrivValidator is monolithic and isn't easily reuseable by alternative signers.
For instance, see https://github.com/tendermint/tendermint/issues/673
The goal is to have a clean PrivValidator interface like:
```
type PrivValidator interface {
Address() data.Bytes
PubKey() crypto.PubKey
SignVote(chainID string, vote *types.Vote) error
SignProposal(chainID string, proposal *types.Proposal) error
SignHeartbeat(chainID string, heartbeat *types.Heartbeat) error
}
```
It should also be easy to re-use the LastSignedInfo logic to avoid double signing.
## Decision
Tendermint node's should support only two in-process PrivValidator implementations:
- PrivValidatorUnencrypted uses an unencrypted private key in a "priv_validator.json" file - no configuration required (just `tendermint init`).
- PrivValidatorSocket uses a socket to send signing requests to another process - user is responsible for starting that process themselves.
The PrivValidatorSocket address can be provided via flags at the command line -
doing so will cause Tendermint to ignore any "priv_validator.json" file and to listen
on the given address for incoming connections from an external priv_validator process.
It will halt any operation until at least one external process succesfully
connected.
The external priv_validator process will dial the address to connect to Tendermint,
and then Tendermint will send requests on the ensuing connection to sign votes and proposals.
Thus the external process initiates the connection, but the Tendermint process makes all requests.
In a later stage we're going to support multiple validators for fault
tolerance. To prevent double signing they need to be synced, which is deferred
to an external solution (see #1185).
In addition, Tendermint will provide implementations that can be run in that external process.
These include:
- PrivValidatorEncrypted uses an encrypted private key persisted to disk - user must enter password to decrypt key when process is started.
- PrivValidatorLedger uses a Ledger Nano S to handle all signing.
What follows are descriptions of useful types
### Signer
```
type Signer interface {
Sign(msg []byte) (crypto.Signature, error)
}
```
Signer signs a message. It can also return an error.
### ValidatorID
ValidatorID is just the Address and PubKey
```
type ValidatorID struct {
Address data.Bytes `json:"address"`
PubKey crypto.PubKey `json:"pub_key"`
}
```
### LastSignedInfo
LastSignedInfo tracks the last thing we signed:
```
type LastSignedInfo struct {
Height int64 `json:"height"`
Round int `json:"round"`
Step int8 `json:"step"`
Signature crypto.Signature `json:"signature,omitempty"` // so we dont lose signatures
SignBytes data.Bytes `json:"signbytes,omitempty"` // so we dont lose signatures
}
```
It exposes methods for signing votes and proposals using a `Signer`.
This allows it to easily be reused by developers implemented their own PrivValidator.
### PrivValidatorUnencrypted
```
type PrivValidatorUnencrypted struct {
ID types.ValidatorID `json:"id"`
PrivKey PrivKey `json:"priv_key"`
LastSignedInfo *LastSignedInfo `json:"last_signed_info"`
}
```
Has the same structure as currently, but broken up into sub structs.
Note the LastSignedInfo is mutated in place every time we sign.
### PrivValidatorJSON
The "priv_validator.json" file supports only the PrivValidatorUnencrypted type.
It unmarshals into PrivValidatorJSON, which is used as the default PrivValidator type.
It wraps the PrivValidatorUnencrypted and persists it to disk after every signature.
## Status
Accepted.
## Consequences
### Positive
- Cleaner separation of components enabling re-use.
### Negative
- More files - led to creation of new directory.
### Neutral
# ADR 008: SocketPV
Tendermint node's should support only two in-process PrivValidator
implementations:
- 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.
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 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:
- 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.

+ 19
- 20
types/block.go View File

@ -7,7 +7,6 @@ import (
"strings"
"time"
wire "github.com/tendermint/tendermint/wire"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/merkle"
"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.
// This is the form in which the block is gossipped to peers.
func (b *Block) MakePartSet(partSize int) *PartSet {
bz, err := wire.MarshalBinary(b)
bz, err := cdc.MarshalBinary(b)
if err != nil {
panic(err)
}
@ -183,19 +182,19 @@ func (h *Header) Hash() cmn.HexBytes {
return nil
}
return merkle.SimpleHashFromMap(map[string]merkle.Hasher{
"ChainID": wireHasher(h.ChainID),
"Height": wireHasher(h.Height),
"Time": wireHasher(h.Time),
"NumTxs": wireHasher(h.NumTxs),
"TotalTxs": wireHasher(h.TotalTxs),
"LastBlockID": wireHasher(h.LastBlockID),
"LastCommit": wireHasher(h.LastCommitHash),
"Data": wireHasher(h.DataHash),
"Validators": wireHasher(h.ValidatorsHash),
"App": wireHasher(h.AppHash),
"Consensus": wireHasher(h.ConsensusHash),
"Results": wireHasher(h.LastResultsHash),
"Evidence": wireHasher(h.EvidenceHash),
"ChainID": aminoHasher(h.ChainID),
"Height": aminoHasher(h.Height),
"Time": aminoHasher(h.Time),
"NumTxs": aminoHasher(h.NumTxs),
"TotalTxs": aminoHasher(h.TotalTxs),
"LastBlockID": aminoHasher(h.LastBlockID),
"LastCommit": aminoHasher(h.LastCommitHash),
"Data": aminoHasher(h.DataHash),
"Validators": aminoHasher(h.ValidatorsHash),
"App": aminoHasher(h.AppHash),
"Consensus": aminoHasher(h.ConsensusHash),
"Results": aminoHasher(h.LastResultsHash),
"Evidence": aminoHasher(h.EvidenceHash),
})
}
@ -364,7 +363,7 @@ func (commit *Commit) Hash() cmn.HexBytes {
if commit.hash == nil {
bs := make([]merkle.Hasher, len(commit.Precommits))
for i, precommit := range commit.Precommits {
bs[i] = wireHasher(precommit)
bs[i] = aminoHasher(precommit)
}
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
func (blockID BlockID) Key() string {
bz, err := wire.MarshalBinary(blockID.PartsHeader)
bz, err := cdc.MarshalBinary(blockID.PartsHeader)
if err != nil {
panic(err)
}
@ -519,7 +518,7 @@ type hasher struct {
func (h hasher) Hash() []byte {
hasher := ripemd160.New()
bz, err := wire.MarshalBinary(h.item)
bz, err := cdc.MarshalBinaryBare(h.item)
if err != nil {
panic(err)
}
@ -531,11 +530,11 @@ func (h hasher) Hash() []byte {
}
func tmHash(item interface{}) []byte {
func aminoHash(item interface{}) []byte {
h := hasher{item}
return h.Hash()
}
func wireHasher(item interface{}) merkle.Hasher {
func aminoHasher(item interface{}) merkle.Hasher {
return hasher{item}
}

+ 1
- 2
types/block_test.go View File

@ -13,8 +13,7 @@ func TestValidateBlock(t *testing.T) {
lastID := makeBlockIDRandom()
h := int64(3)
voteSet, _, vals := randVoteSet(h-1, 1, VoteTypePrecommit,
10, 1)
voteSet, _, vals := randVoteSet(h-1, 1, VoteTypePrecommit, 10, 1)
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals)
require.NoError(t, err)


+ 28
- 34
types/canonical_json.go View File

@ -3,14 +3,14 @@ package types
import (
"time"
wire "github.com/tendermint/tendermint/wire"
"github.com/tendermint/go-amino"
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
const TimeFormat = wire.RFC3339Millis
const TimeFormat = amino.RFC3339Millis
type CanonicalJSONBlockID struct {
Hash cmn.HexBytes `json:"hash,omitempty"`
@ -18,11 +18,13 @@ type CanonicalJSONBlockID struct {
}
type CanonicalJSONPartSetHeader struct {
Hash cmn.HexBytes `json:"hash"`
Total int `json:"total"`
Hash cmn.HexBytes `json:"hash,omitempty"`
Total int `json:"total,omitempty"`
}
type CanonicalJSONProposal struct {
ChainID string `json:"@chain_id"`
Type string `json:"@type"`
BlockPartsHeader CanonicalJSONPartSetHeader `json:"block_parts_header"`
Height int64 `json:"height"`
POLBlockID CanonicalJSONBlockID `json:"pol_block_id"`
@ -32,14 +34,18 @@ type CanonicalJSONProposal struct {
}
type CanonicalJSONVote struct {
ChainID string `json:"@chain_id"`
Type string `json:"@type"`
BlockID CanonicalJSONBlockID `json:"block_id"`
Height int64 `json:"height"`
Round int `json:"round"`
Timestamp string `json:"timestamp"`
Type byte `json:"type"`
VoteType byte `json:"type"`
}
type CanonicalJSONHeartbeat struct {
ChainID string `json:"@chain_id"`
Type string `json:"@type"`
Height int64 `json:"height"`
Round int `json:"round"`
Sequence int `json:"sequence"`
@ -47,24 +53,6 @@ type CanonicalJSONHeartbeat struct {
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
@ -82,8 +70,10 @@ func CanonicalPartSetHeader(psh PartSetHeader) CanonicalJSONPartSetHeader {
}
}
func CanonicalProposal(proposal *Proposal) CanonicalJSONProposal {
func CanonicalProposal(chainID string, proposal *Proposal) CanonicalJSONProposal {
return CanonicalJSONProposal{
ChainID: chainID,
Type: "proposal",
BlockPartsHeader: CanonicalPartSetHeader(proposal.BlockPartsHeader),
Height: proposal.Height,
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{
ChainID: chainID,
Type: "vote",
BlockID: CanonicalBlockID(vote.BlockID),
Height: vote.Height,
Round: vote.Round,
Timestamp: CanonicalTime(vote.Timestamp),
Type: vote.Type,
VoteType: vote.Type,
}
}
func CanonicalHeartbeat(heartbeat *Heartbeat) CanonicalJSONHeartbeat {
func CanonicalHeartbeat(chainID string, heartbeat *Heartbeat) CanonicalJSONHeartbeat {
return CanonicalJSONHeartbeat{
heartbeat.Height,
heartbeat.Round,
heartbeat.Sequence,
heartbeat.ValidatorAddress,
heartbeat.ValidatorIndex,
ChainID: chainID,
Type: "heartbeat",
Height: heartbeat.Height,
Round: heartbeat.Round,
Sequence: heartbeat.Sequence,
ValidatorAddress: heartbeat.ValidatorAddress,
ValidatorIndex: heartbeat.ValidatorIndex,
}
}
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
// signatures match
return t.UTC().Format(TimeFormat)


+ 46
- 52
types/evidence.go View File

@ -4,8 +4,8 @@ import (
"bytes"
"fmt"
"github.com/tendermint/go-amino"
"github.com/tendermint/go-crypto"
wire "github.com/tendermint/tendermint/wire"
"github.com/tendermint/tmlibs/merkle"
)
@ -38,56 +38,11 @@ type Evidence interface {
String() string
}
//-------------------------------------------
// 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 RegisterEvidence(cdc *amino.Codec) {
cdc.RegisterInterface((*Evidence)(nil), nil)
cdc.RegisterConcrete(&DuplicateVoteEvidence{}, "tendermint/Evidence", nil)
}
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.
@ -120,7 +75,7 @@ func (dve *DuplicateVoteEvidence) Index() int {
// Hash returns the hash of the evidence.
func (dve *DuplicateVoteEvidence) Hash() []byte {
return wireHasher(dve).Hash()
return aminoHasher(dve).Hash()
}
// 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
dveHash := wireHasher(dve).Hash()
evHash := wireHasher(ev).Hash()
dveHash := aminoHasher(dve).Hash()
evHash := aminoHasher(ev).Hash()
return bytes.Equal(dveHash, evHash)
}
@ -216,3 +171,42 @@ func (e MockBadEvidence) Equal(ev Evidence) bool {
func (e MockBadEvidence) String() string {
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
}

+ 13
- 11
types/evidence_test.go View File

@ -4,7 +4,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
cmn "github.com/tendermint/tmlibs/common"
)
type voteData struct {
@ -13,25 +12,25 @@ type voteData struct {
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{
ValidatorAddress: val.PubKey.Address(),
ValidatorAddress: val.GetAddress(),
ValidatorIndex: valIndex,
Height: height,
Round: round,
Type: byte(step),
BlockID: blockID,
}
sig := val.PrivKey.Sign(v.SignBytes(chainID))
v.Signature = sig
err := val.SignVote(chainID, v)
if err != nil {
panic(err)
}
return v
}
func TestEvidence(t *testing.T) {
_, tmpFilePath := cmn.Tempfile("priv_validator_")
val := GenPrivValidatorFS(tmpFilePath)
val2 := GenPrivValidatorFS(tmpFilePath)
val := NewMockPV()
val2 := NewMockPV()
blockID := makeBlockID("blockhash", 1000, "partshash")
blockID2 := makeBlockID("blockhash2", 1000, "partshash")
blockID3 := makeBlockID("blockhash", 10000, "partshash")
@ -41,7 +40,10 @@ func TestEvidence(t *testing.T) {
vote1 := 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{
{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 {
ev := &DuplicateVoteEvidence{
PubKey: val.PubKey,
PubKey: val.GetPubKey(),
VoteA: c.vote1,
VoteB: c.vote2,
}


+ 8
- 10
types/genesis.go View File

@ -5,9 +5,7 @@ import (
"io/ioutil"
"time"
"github.com/pkg/errors"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-crypto"
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.
func (genDoc *GenesisDoc) SaveAs(file string) error {
genDocBytes, err := json.Marshal(genDoc)
genDocBytes, err := cdc.MarshalJSON(genDoc)
if err != nil {
return err
}
@ -55,7 +53,7 @@ func (genDoc *GenesisDoc) ValidatorHash() []byte {
func (genDoc *GenesisDoc) ValidateAndComplete() error {
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 {
@ -67,12 +65,12 @@ func (genDoc *GenesisDoc) ValidateAndComplete() error {
}
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 {
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.
func GenesisDocFromJSON(jsonBlob []byte) (*GenesisDoc, error) {
genDoc := GenesisDoc{}
err := json.Unmarshal(jsonBlob, &genDoc)
err := cdc.UnmarshalJSON(jsonBlob, &genDoc)
if err != nil {
return nil, err
}
@ -105,11 +103,11 @@ func GenesisDocFromJSON(jsonBlob []byte) (*GenesisDoc, error) {
func GenesisDocFromFile(genDocFile string) (*GenesisDoc, error) {
jsonBlob, err := ioutil.ReadFile(genDocFile)
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)
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
}

+ 18
- 19
types/genesis_test.go View File

@ -1,35 +1,34 @@
package types
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-crypto"
"testing"
)
func TestGenesis(t *testing.T) {
func TestGenesisBad(t *testing.T) {
// test some bad ones from raw json
testCases := [][]byte{
[]byte{}, // empty
[]byte{1, 1, 1, 1, 1}, // junk
[]byte(`{}`), // empty
[]byte(`{"chain_id": "mychain"}`), // missing validators
[]byte(`{"chain_id": "mychain", "validators": []`), // missing validators
[]byte(`{"chain_id": "mychain", "validators": [{}]`), // missing validators
[]byte(`{"validators":[{"pub_key":
{"type":"ed25519","data":"961EAB8752E51A03618502F55C2B6E09C38C65635C64CCF3173ED452CF86C957"},
"power":10,"name":""}]}`), // missing chain_id
[]byte{}, // empty
[]byte{1, 1, 1, 1, 1}, // junk
[]byte(`{}`), // empty
[]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":null}`), // missing validators
[]byte(`{"chain_id":"mychain"}`), // missing validators
[]byte(`{"validators":[{"pub_key":{"type":"AC26791624DE60","value":"AT/+aaL1eB0477Mud9JMm8Sh8BIvOYlPGC9KkIUmFaE="},"power":10,"name":""}]}`), // missing chain_id
}
for _, testCase := range testCases {
_, err := GenesisDocFromJSON(testCase)
assert.Error(t, err, "expected error for empty genDoc json")
}
}
func TestGenesisGood(t *testing.T) {
// 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)
assert.NoError(t, err, "expected no error for good genDoc json")
@ -38,7 +37,7 @@ func TestGenesis(t *testing.T) {
ChainID: "abc",
Validators: []GenesisValidator{{crypto.GenPrivKeyEd25519().PubKey(), 10, "myval"}},
}
genDocBytes, err = json.Marshal(baseGenDoc)
genDocBytes, err = cdc.MarshalJSON(baseGenDoc)
assert.NoError(t, err, "error marshalling genDoc")
// 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")
// create json with consensus params filled
genDocBytes, err = json.Marshal(genDoc)
genDocBytes, err = cdc.MarshalJSON(genDoc)
assert.NoError(t, err, "error marshalling genDoc")
genDoc, err = GenesisDocFromJSON(genDocBytes)
assert.NoError(t, err, "expected no error for valid genDoc json")
// test with invalid consensus params
genDoc.ConsensusParams.BlockSize.MaxBytes = 0
genDocBytes, err = json.Marshal(genDoc)
genDocBytes, err = cdc.MarshalJSON(genDoc)
assert.NoError(t, err, "error marshalling genDoc")
genDoc, err = GenesisDocFromJSON(genDocBytes)
assert.Error(t, err, "expected error for genDoc json with block size of 0")


+ 2
- 6
types/heartbeat.go View File

@ -4,7 +4,6 @@ import (
"fmt"
"github.com/tendermint/go-crypto"
"github.com/tendermint/tendermint/wire"
cmn "github.com/tendermint/tmlibs/common"
)
@ -14,7 +13,7 @@ import (
// json field tags because we always want the JSON
// representation to be in its canonical form.
type Heartbeat struct {
ValidatorAddress Address `json:"validator_address"`
ValidatorAddress Address `json:"validator_address"`
ValidatorIndex int `json:"validator_index"`
Height int64 `json:"height"`
Round int `json:"round"`
@ -25,10 +24,7 @@ type Heartbeat struct {
// SignBytes returns the Heartbeat bytes for signing.
// It panics if the Heartbeat is nil.
func (heartbeat *Heartbeat) SignBytes(chainID string) []byte {
bz, err := wire.MarshalJSON(CanonicalJSONOnceHeartbeat{
chainID,
CanonicalHeartbeat(heartbeat),
})
bz, err := cdc.MarshalJSON(CanonicalHeartbeat(chainID, heartbeat))
if err != nil {
panic(err)
}


+ 5
- 4
types/heartbeat_test.go View File

@ -25,22 +25,23 @@ func TestHeartbeatString(t *testing.T) {
require.Contains(t, nilHb.String(), "nil", "expecting a string and no panic")
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
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) {
hb := &Heartbeat{ValidatorIndex: 1, Height: 10, Round: 1}
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{}
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() {
var nilHb *Heartbeat


+ 10
- 11
types/params.go View File

@ -1,9 +1,8 @@
package types
import (
"github.com/pkg/errors"
abci "github.com/tendermint/abci/types"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/merkle"
)
@ -89,15 +88,15 @@ func DefaultEvidenceParams() EvidenceParams {
func (params *ConsensusParams) Validate() error {
// ensure some values are greater than 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 {
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
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)
}
return nil
@ -107,12 +106,12 @@ func (params *ConsensusParams) Validate() error {
// in the block header
func (params *ConsensusParams) Hash() []byte {
return merkle.SimpleHashFromMap(map[string]merkle.Hasher{
"block_gossip_part_size_bytes": wireHasher(params.BlockGossip.BlockPartSizeBytes),
"block_size_max_bytes": wireHasher(params.BlockSize.MaxBytes),
"block_size_max_gas": wireHasher(params.BlockSize.MaxGas),
"block_size_max_txs": wireHasher(params.BlockSize.MaxTxs),
"tx_size_max_bytes": wireHasher(params.TxSize.MaxBytes),
"tx_size_max_gas": wireHasher(params.TxSize.MaxGas),
"block_gossip_part_size_bytes": aminoHasher(params.BlockGossip.BlockPartSizeBytes),
"block_size_max_bytes": aminoHasher(params.BlockSize.MaxBytes),
"block_size_max_gas": aminoHasher(params.BlockSize.MaxGas),
"block_size_max_txs": aminoHasher(params.BlockSize.MaxTxs),
"tx_size_max_bytes": aminoHasher(params.TxSize.MaxBytes),
"tx_size_max_gas": aminoHasher(params.TxSize.MaxGas),
})
}


+ 39
- 422
types/priv_validator.go View File

@ -2,88 +2,11 @@ package types
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"sync"
"time"
crypto "github.com/tendermint/go-crypto"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/go-crypto"
)
// 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
// that signs votes, proposals, and heartbeats, and never double signs.
type PrivValidator interface {
@ -95,378 +18,72 @@ type PrivValidator interface {
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
// 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
//----------------------------------------
// Misc.
// PrivKey should be empty if a Signer other than the default is being used.
PrivKey crypto.PrivKey `json:"priv_key"`
Signer `json:"-"`
type PrivValidatorsByAddress []PrivValidator
// For persistence.
// Overloaded for testing.
filePath string
mtx sync.Mutex
func (pvs PrivValidatorsByAddress) Len() int {
return len(pvs)
}
// 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)
func (pvs PrivValidatorsByAddress) Less(i, j int) bool {
return bytes.Compare(pvs[i].GetAddress(), pvs[j].GetAddress()) == -1
}
// DefaultSigner implements Signer.
// It uses a standard, unencrypted crypto.PrivKey.
type DefaultSigner struct {
PrivKey crypto.PrivKey `json:"priv_key"`
func (pvs PrivValidatorsByAddress) Swap(i, j int) {
it := pvs[i]
pvs[i] = pvs[j]
pvs[j] = it
}
// NewDefaultSigner returns an instance of DefaultSigner.
func NewDefaultSigner(priv crypto.PrivKey) *DefaultSigner {
return &DefaultSigner{
PrivKey: priv,
}
}
//----------------------------------------
// MockPV
// 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
// MockPV implements PrivValidator without any safety or persistence.
// Only use it for testing.
type MockPV struct {
privKey crypto.PrivKey
}
// GetAddress returns the address of the validator.
// Implements PrivValidator.
func (pv *PrivValidatorFS) GetAddress() Address {
return pv.Address
func NewMockPV() *MockPV {
return &MockPV{crypto.GenPrivKeyEd25519()}
}
// 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)
}
func (pv *MockPV) GetAddress() Address {
return pv.privKey.PubKey().Address()
}
// 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
// Implements PrivValidator.
func (pv *MockPV) GetPubKey() crypto.PubKey {
return pv.privKey.PubKey()
}
// 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)
// Implements PrivValidator.
func (pv *MockPV) SignVote(chainID string, vote *Vote) error {
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)
sig := pv.privKey.Sign(signBytes)
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
// Implements PrivValidator.
func (pv *MockPV) SignProposal(chainID string, proposal *Proposal) error {
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)
sig := pv.privKey.Sign(signBytes)
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 {
return len(pvs)
}
func (pvs PrivValidatorsByAddress) Less(i, j int) bool {
return bytes.Compare(pvs[i].GetAddress(), pvs[j].GetAddress()) == -1
}
func (pvs PrivValidatorsByAddress) Swap(i, j int) {
it := pvs[i]
pvs[i] = pvs[j]
pvs[j] = it
}
//-------------------------------------
// 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 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(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)
// 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
}
// 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 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(TimeFormat, lastProposal.Proposal.Timestamp)
if err != nil {
panic(err)
}
// set the times to the same value and check equality
now := 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)
// String returns a string representation of the MockPV.
func (pv *MockPV) String() string {
return fmt.Sprintf("MockPV{%v}", pv.GetAddress())
}

+ 0
- 197
types/priv_validator/json.go View File

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

+ 345
- 0
types/priv_validator/priv_validator.go View File

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

+ 61
- 92
types/priv_validator/priv_validator_test.go View File

@ -1,8 +1,7 @@
package types
package privval
import (
"encoding/hex"
"encoding/json"
"encoding/base64"
"fmt"
"os"
"testing"
@ -10,113 +9,89 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
crypto "github.com/tendermint/go-crypto"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/go-crypto"
"github.com/tendermint/tendermint/types"
cmn "github.com/tendermint/tmlibs/common"
)
func TestGenLoadValidator(t *testing.T) {
assert, require := assert.New(t), require.New(t)
assert := assert.New(t)
_, tempFilePath := cmn.Tempfile("priv_validator_")
privVal := GenPrivValidatorJSON(tempFilePath)
privVal := GenFilePV(tempFilePath)
height := int64(100)
privVal.LastSignedInfo.Height = height
privVal.LastHeight = height
privVal.Save()
addr, err := privVal.Address()
require.Nil(err)
privVal = LoadPrivValidatorJSON(tempFilePath)
pAddr, err := privVal.Address()
require.Nil(err)
addr := privVal.GetAddress()
assert.Equal(addr, pAddr, "expected privval addr to be the same")
assert.Equal(height, privVal.LastSignedInfo.Height, "expected privval.LastHeight to have been saved")
privVal = LoadFilePV(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, require := assert.New(t), require.New(t)
assert := assert.New(t)
_, tempFilePath := cmn.Tempfile("priv_validator_")
if err := os.Remove(tempFilePath); err != nil {
t.Error(err)
}
privVal := LoadOrGenPrivValidatorJSON(tempFilePath)
addr, err := privVal.Address()
require.Nil(err)
privVal = LoadOrGenPrivValidatorJSON(tempFilePath)
pAddr, err := privVal.Address()
require.Nil(err)
assert.Equal(addr, pAddr, "expected privval addr to be the same")
privVal := LoadOrGenFilePV(tempFilePath)
addr := privVal.GetAddress()
privVal = LoadOrGenFilePV(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)
privKey := crypto.GenPrivKeyEd25519()
pubKey := privKey.PubKey()
addr := pubKey.Address()
pubArray := [32]byte(pubKey.(crypto.PubKeyEd25519))
pubBytes := pubArray[:]
privArray := [64]byte(privKey)
privBytes := privArray[:]
pubB64 := base64.StdEncoding.EncodeToString(pubBytes)
privB64 := base64.StdEncoding.EncodeToString(privBytes)
serialized := fmt.Sprintf(`{
"id": {
"address": "%s",
"pub_key": {
"type": "ed25519",
"data": "%s"
}
},
"priv_key": {
"type": "ed25519",
"data": "%s"
},
"last_signed_info": {
"height": 0,
"round": 0,
"step": 0,
"signature": null
}
}`, addrStr, pubStr, privStr)
val := PrivValidatorJSON{}
err = json.Unmarshal([]byte(serialized), &val)
"address": "%s",
"pub_key": {
"type": "AC26791624DE60",
"value": "%s"
},
"last_height": 0,
"last_round": 0,
"last_step": 0,
"priv_key": {
"type": "954568A3288910",
"value": "%s"
}
}`, addr, pubB64, privB64)
val := FilePV{}
err := cdc.UnmarshalJSON([]byte(serialized), &val)
require.Nil(err, "%+v", err)
// make sure the values match
vAddr, err := val.Address()
require.Nil(err)
pKey, err := val.PubKey()
require.Nil(err)
assert.EqualValues(addrBytes, vAddr)
assert.EqualValues(pubKey, pKey)
assert.EqualValues(addr, 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)
out, err := cdc.MarshalJSON(val)
require.Nil(err, "%+v", err)
assert.JSONEq(serialized, string(out))
}
func TestSignVote(t *testing.T) {
assert, require := assert.New(t), require.New(t)
assert := assert.New(t)
_, tempFilePath := cmn.Tempfile("priv_validator_")
privVal := GenPrivValidatorJSON(tempFilePath)
privVal := GenFilePV(tempFilePath)
block1 := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}}
block2 := types.BlockID{[]byte{3, 2, 1}, types.PartSetHeader{}}
@ -124,11 +99,8 @@ func TestSignVote(t *testing.T) {
voteType := types.VoteTypePrevote
// sign a vote for first time
addr, err := privVal.Address()
require.Nil(err)
vote := newVote(addr, 0, height, round, voteType, block1)
err = privVal.SignVote("mychainid", vote)
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
@ -137,10 +109,10 @@ func TestSignVote(t *testing.T) {
// now try some bad votes
cases := []*types.Vote{
newVote(addr, 0, height, round-1, voteType, block1), // round regression
newVote(addr, 0, height-1, round, voteType, block1), // height regression
newVote(addr, 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-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 {
@ -160,7 +132,7 @@ func TestSignProposal(t *testing.T) {
assert := assert.New(t)
_, tempFilePath := cmn.Tempfile("priv_validator_")
privVal := GenPrivValidatorJSON(tempFilePath)
privVal := GenFilePV(tempFilePath)
block1 := types.PartSetHeader{5, []byte{1, 2, 3}}
block2 := types.PartSetHeader{10, []byte{3, 2, 1}}
@ -197,10 +169,8 @@ func TestSignProposal(t *testing.T) {
}
func TestDifferByTimestamp(t *testing.T) {
require := require.New(t)
_, tempFilePath := cmn.Tempfile("priv_validator_")
privVal := GenPrivValidatorJSON(tempFilePath)
privVal := GenFilePV(tempFilePath)
block1 := types.PartSetHeader{5, []byte{1, 2, 3}}
height, round := int64(10), 1
@ -217,7 +187,8 @@ func TestDifferByTimestamp(t *testing.T) {
// manipulate the timestamp. should get changed back
proposal.Timestamp = proposal.Timestamp.Add(time.Millisecond)
proposal.Signature = crypto.Signature{}
var emptySig crypto.Signature
proposal.Signature = emptySig
err = privVal.SignProposal("mychainid", proposal)
assert.NoError(t, err, "expected no error on signing same proposal")
@ -228,13 +199,10 @@ func TestDifferByTimestamp(t *testing.T) {
// test vote
{
addr, err := privVal.Address()
require.Nil(err)
voteType := types.VoteTypePrevote
blockID := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}}
vote := newVote(addr, 0, height, round, voteType, blockID)
err = privVal.SignVote("mychainid", vote)
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)
@ -243,7 +211,8 @@ func TestDifferByTimestamp(t *testing.T) {
// manipulate the timestamp. should get changed back
vote.Timestamp = vote.Timestamp.Add(time.Millisecond)
vote.Signature = crypto.Signature{}
var emptySig crypto.Signature
vote.Signature = emptySig
err = privVal.SignVote("mychainid", 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{
ValidatorAddress: addr,
ValidatorIndex: idx,


+ 0
- 238
types/priv_validator/sign_info.go View File

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

+ 90
- 116
types/priv_validator/socket.go View File

@ -1,14 +1,14 @@
package types
package privval
import (
"errors"
"fmt"
"io"
"net"
"time"
"github.com/pkg/errors"
crypto "github.com/tendermint/go-crypto"
wire "github.com/tendermint/go-wire"
"github.com/tendermint/go-amino"
"github.com/tendermint/go-crypto"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
@ -37,36 +37,36 @@ var (
connHeartbeat = time.Second * defaultConnHeartBeatSeconds
)
// SocketClientOption sets an optional parameter on the SocketClient.
type SocketClientOption func(*SocketClient)
// SocketPVOption sets an optional parameter on the SocketPV.
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.
func SocketClientAcceptDeadline(deadline time.Duration) SocketClientOption {
return func(sc *SocketClient) { sc.acceptDeadline = deadline }
func SocketPVAcceptDeadline(deadline time.Duration) SocketPVOption {
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.
func SocketClientConnDeadline(deadline time.Duration) SocketClientOption {
return func(sc *SocketClient) { sc.connDeadline = deadline }
func SocketPVConnDeadline(deadline time.Duration) SocketPVOption {
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.
func SocketClientHeartbeat(period time.Duration) SocketClientOption {
return func(sc *SocketClient) { sc.connHeartbeat = period }
func SocketPVHeartbeat(period time.Duration) SocketPVOption {
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.
func SocketClientConnWait(timeout time.Duration) SocketClientOption {
return func(sc *SocketClient) { sc.connWaitTimeout = timeout }
func SocketPVConnWait(timeout time.Duration) SocketPVOption {
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.
type SocketClient struct {
type SocketPV struct {
cmn.BaseService
addr string
@ -80,16 +80,16 @@ type SocketClient struct {
listener net.Listener
}
// Check that SocketClient implements PrivValidator2.
var _ types.PrivValidator2 = (*SocketClient)(nil)
// Check that SocketPV implements PrivValidator.
var _ types.PrivValidator = (*SocketPV)(nil)
// NewSocketClient returns an instance of SocketClient.
func NewSocketClient(
// NewSocketPV returns an instance of SocketPV.
func NewSocketPV(
logger log.Logger,
socketAddr string,
privKey crypto.PrivKeyEd25519,
) *SocketClient {
sc := &SocketClient{
) *SocketPV {
sc := &SocketPV{
addr: socketAddr,
acceptDeadline: acceptDeadline,
connDeadline: connDeadline,
@ -98,15 +98,14 @@ func NewSocketClient(
privKey: privKey,
}
sc.BaseService = *cmn.NewBaseService(logger, "SocketClient", sc)
sc.BaseService = *cmn.NewBaseService(logger, "SocketPV", sc)
return sc
}
// GetAddress implements PrivValidator.
// TODO(xla): Remove when PrivValidator2 replaced PrivValidator.
func (sc *SocketClient) GetAddress() types.Address {
addr, err := sc.Address()
func (sc *SocketPV) GetAddress() types.Address {
addr, err := sc.getAddress()
if err != nil {
panic(err)
}
@ -115,8 +114,8 @@ func (sc *SocketClient) GetAddress() types.Address {
}
// Address is an alias for PubKey().Address().
func (sc *SocketClient) Address() (cmn.HexBytes, error) {
p, err := sc.PubKey()
func (sc *SocketPV) getAddress() (cmn.HexBytes, error) {
p, err := sc.getPubKey()
if err != nil {
return nil, err
}
@ -125,9 +124,8 @@ func (sc *SocketClient) Address() (cmn.HexBytes, error) {
}
// GetPubKey implements PrivValidator.
// TODO(xla): Remove when PrivValidator2 replaced PrivValidator.
func (sc *SocketClient) GetPubKey() crypto.PubKey {
pubKey, err := sc.PubKey()
func (sc *SocketPV) GetPubKey() crypto.PubKey {
pubKey, err := sc.getPubKey()
if err != nil {
panic(err)
}
@ -135,23 +133,22 @@ func (sc *SocketClient) GetPubKey() crypto.PubKey {
return pubKey
}
// PubKey implements PrivValidator2.
func (sc *SocketClient) PubKey() (crypto.PubKey, error) {
func (sc *SocketPV) getPubKey() (crypto.PubKey, error) {
err := writeMsg(sc.conn, &PubKeyMsg{})
if err != nil {
return crypto.PubKey{}, err
return nil, err
}
res, err := readMsg(sc.conn)
if err != nil {
return crypto.PubKey{}, err
return nil, err
}
return res.(*PubKeyMsg).PubKey, nil
}
// SignVote implements PrivValidator2.
func (sc *SocketClient) SignVote(chainID string, vote *types.Vote) error {
// SignVote implements PrivValidator.
func (sc *SocketPV) SignVote(chainID string, vote *types.Vote) error {
err := writeMsg(sc.conn, &SignVoteMsg{Vote: vote})
if err != nil {
return err
@ -167,8 +164,8 @@ func (sc *SocketClient) SignVote(chainID string, vote *types.Vote) error {
return nil
}
// SignProposal implements PrivValidator2.
func (sc *SocketClient) SignProposal(
// SignProposal implements PrivValidator.
func (sc *SocketPV) SignProposal(
chainID string,
proposal *types.Proposal,
) error {
@ -187,8 +184,8 @@ func (sc *SocketClient) SignProposal(
return nil
}
// SignHeartbeat implements PrivValidator2.
func (sc *SocketClient) SignHeartbeat(
// SignHeartbeat implements PrivValidator.
func (sc *SocketPV) SignHeartbeat(
chainID string,
heartbeat *types.Heartbeat,
) error {
@ -208,21 +205,22 @@ func (sc *SocketClient) SignHeartbeat(
}
// OnStart implements cmn.Service.
func (sc *SocketClient) OnStart() error {
func (sc *SocketPV) OnStart() error {
if err := sc.listen(); err != nil {
err = cmn.ErrorWrap(err, "failed to listen")
sc.Logger.Error(
"OnStart",
"err", errors.Wrap(err, "failed to listen"),
"err", err,
)
return err
}
conn, err := sc.waitConnection()
if err != nil {
err = cmn.ErrorWrap(err, "failed to accept connection")
sc.Logger.Error(
"OnStart",
"err", errors.Wrap(err, "failed to accept connection"),
"err", err,
)
return err
@ -234,27 +232,29 @@ func (sc *SocketClient) OnStart() error {
}
// OnStop implements cmn.Service.
func (sc *SocketClient) OnStop() {
func (sc *SocketPV) OnStop() {
if sc.conn != nil {
if err := sc.conn.Close(); err != nil {
err = cmn.ErrorWrap(err, "failed to close connection")
sc.Logger.Error(
"OnStop",
"err", errors.Wrap(err, "failed to close connection"),
"err", err,
)
}
}
if sc.listener != nil {
if err := sc.listener.Close(); err != nil {
err = cmn.ErrorWrap(err, "failed to close listener")
sc.Logger.Error(
"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()
if err != nil {
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 {
return nil, err
}
@ -272,7 +272,7 @@ func (sc *SocketClient) acceptConnection() (net.Conn, error) {
return conn, nil
}
func (sc *SocketClient) listen() error {
func (sc *SocketPV) listen() error {
ln, err := net.Listen(cmn.ProtocolAndAddress(sc.addr))
if err != nil {
return err
@ -290,7 +290,7 @@ func (sc *SocketClient) listen() error {
// waitConnection uses the configured wait timeout to error if no external
// process connects in the time period.
func (sc *SocketClient) waitConnection() (net.Conn, error) {
func (sc *SocketPV) waitConnection() (net.Conn, error) {
var (
connc = make(chan net.Conn, 1)
errc = make(chan error, 1)
@ -311,7 +311,7 @@ func (sc *SocketClient) waitConnection() (net.Conn, error) {
return conn, nil
case err := <-errc:
if _, ok := err.(timeoutError); ok {
return nil, errors.Wrap(ErrConnWaitTimeout, err.Error())
return nil, cmn.ErrorWrap(ErrConnWaitTimeout, err.Error())
}
return nil, err
case <-time.After(sc.connWaitTimeout):
@ -344,7 +344,7 @@ type RemoteSigner struct {
connDeadline time.Duration
connRetries int
privKey crypto.PrivKeyEd25519
privVal PrivValidator
privVal types.PrivValidator
conn net.Conn
}
@ -353,7 +353,7 @@ type RemoteSigner struct {
func NewRemoteSigner(
logger log.Logger,
chainID, socketAddr string,
privVal PrivValidator,
privVal types.PrivValidator,
privKey crypto.PrivKeyEd25519,
) *RemoteSigner {
rs := &RemoteSigner{
@ -374,8 +374,8 @@ func NewRemoteSigner(
func (rs *RemoteSigner) OnStart() error {
conn, err := rs.connect()
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
}
@ -391,7 +391,7 @@ func (rs *RemoteSigner) OnStop() {
}
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)
if err != nil {
err = cmn.ErrorWrap(err, "connection failed")
rs.Logger.Error(
"connect",
"addr", rs.addr,
"err", errors.Wrap(err, "connection failed"),
"err", err,
)
continue
}
if err := conn.SetDeadline(time.Now().Add(connDeadline)); err != nil {
err = cmn.ErrorWrap(err, "setting connection timeout failed")
rs.Logger.Error(
"connect",
"err", errors.Wrap(err, "setting connection timeout failed"),
"err", err,
)
continue
}
conn, err = p2pconn.MakeSecretConnection(conn, rs.privKey.Wrap())
conn, err = p2pconn.MakeSecretConnection(conn, rs.privKey)
if err != nil {
err = cmn.ErrorWrap(err, "encrypting connection failed")
rs.Logger.Error(
"connect",
"err", errors.Wrap(err, "encrypting connection failed"),
"err", err,
)
continue
@ -451,13 +454,12 @@ func (rs *RemoteSigner) handleConnection(conn net.Conn) {
return
}
var res PrivValMsg
var res SocketPVMsg
switch r := req.(type) {
case *PubKeyMsg:
var p crypto.PubKey
p, err = rs.privVal.PubKey()
p = rs.privVal.GetPubKey()
res = &PubKeyMsg{p}
case *SignVoteMsg:
err = rs.privVal.SignVote(rs.chainID, r.Vote)
@ -487,23 +489,16 @@ func (rs *RemoteSigner) handleConnection(conn net.Conn) {
//---------------------------------------------------------
const (
msgTypePubKey = byte(0x01)
msgTypeSignVote = byte(0x10)
msgTypeSignProposal = byte(0x11)
msgTypeSignHeartbeat = byte(0x12)
)
// SocketPVMsg is sent between RemoteSigner and SocketPV.
type SocketPVMsg interface{}
// PrivValMsg is sent between RemoteSigner and SocketClient.
type PrivValMsg interface{}
var _ = wire.RegisterInterface(
struct{ PrivValMsg }{},
wire.ConcreteType{&PubKeyMsg{}, msgTypePubKey},
wire.ConcreteType{&SignVoteMsg{}, msgTypeSignVote},
wire.ConcreteType{&SignProposalMsg{}, msgTypeSignProposal},
wire.ConcreteType{&SignHeartbeatMsg{}, msgTypeSignHeartbeat},
)
func RegisterSocketPVMsg(cdc *amino.Codec) {
cdc.RegisterInterface((*SocketPVMsg)(nil), nil)
cdc.RegisterConcrete(&PubKeyMsg{}, "tendermint/socketpv/PubKeyMsg", nil)
cdc.RegisterConcrete(&SignVoteMsg{}, "tendermint/socketpv/SignVoteMsg", nil)
cdc.RegisterConcrete(&SignProposalMsg{}, "tendermint/socketpv/SignProposalMsg", nil)
cdc.RegisterConcrete(&SignHeartbeatMsg{}, "tendermint/socketpv/SignHeartbeatMsg", nil)
}
// PubKeyMsg is a PrivValidatorSocket message containing the public key.
type PubKeyMsg struct {
@ -525,40 +520,19 @@ type SignHeartbeatMsg struct {
Heartbeat *types.Heartbeat
}
func readMsg(r io.Reader) (PrivValMsg, error) {
var (
n int
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")
func readMsg(r io.Reader) (msg SocketPVMsg, err error) {
const maxSocketPVMsgSize = 1024 * 10
_, err = cdc.UnmarshalBinaryReader(r, &msg, maxSocketPVMsgSize)
if _, ok := err.(timeoutError); ok {
err = cmn.ErrorWrap(ErrConnTimeout, err.Error())
}
return w.PrivValMsg, nil
return
}
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)
func writeMsg(w io.Writer, msg interface{}) (err error) {
_, err = cdc.MarshalBinaryWriter(w, msg)
if _, ok := err.(timeoutError); ok {
return errors.Wrap(ErrConnTimeout, err.Error())
err = cmn.ErrorWrap(ErrConnTimeout, err.Error())
}
return err
return
}

+ 1
- 1
types/priv_validator/socket_tcp.go View File

@ -1,4 +1,4 @@
package types
package privval
import (
"net"


+ 1
- 1
types/priv_validator/socket_tcp_test.go View File

@ -1,4 +1,4 @@
package types
package privval
import (
"net"


+ 33
- 37
types/priv_validator/socket_test.go View File

@ -1,4 +1,4 @@
package types
package privval
import (
"fmt"
@ -6,11 +6,10 @@ import (
"testing"
"time"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"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/tmlibs/log"
@ -18,7 +17,7 @@ import (
"github.com/tendermint/tendermint/types"
)
func TestSocketClientAddress(t *testing.T) {
func TestSocketPVAddress(t *testing.T) {
var (
chainID = cmn.RandStr(12)
sc, rs = testSetupSocketPair(t, chainID)
@ -26,10 +25,9 @@ func TestSocketClientAddress(t *testing.T) {
defer sc.Stop()
defer rs.Stop()
serverAddr, err := rs.privVal.Address()
require.NoError(t, err)
serverAddr := rs.privVal.GetAddress()
clientAddr, err := sc.Address()
clientAddr, err := sc.getAddress()
require.NoError(t, err)
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 (
chainID = cmn.RandStr(12)
sc, rs = testSetupSocketPair(t, chainID)
@ -47,11 +45,10 @@ func TestSocketClientPubKey(t *testing.T) {
defer sc.Stop()
defer rs.Stop()
clientKey, err := sc.PubKey()
clientKey, err := sc.getPubKey()
require.NoError(t, err)
privKey, err := rs.privVal.PubKey()
require.NoError(t, err)
privKey := rs.privVal.GetPubKey()
assert.Equal(t, privKey, clientKey)
@ -59,7 +56,7 @@ func TestSocketClientPubKey(t *testing.T) {
assert.Equal(t, privKey, sc.GetPubKey())
}
func TestSocketClientProposal(t *testing.T) {
func TestSocketPVProposal(t *testing.T) {
var (
chainID = cmn.RandStr(12)
sc, rs = testSetupSocketPair(t, chainID)
@ -76,7 +73,7 @@ func TestSocketClientProposal(t *testing.T) {
assert.Equal(t, privProposal.Signature, clientProposal.Signature)
}
func TestSocketClientVote(t *testing.T) {
func TestSocketPVVote(t *testing.T) {
var (
chainID = cmn.RandStr(12)
sc, rs = testSetupSocketPair(t, chainID)
@ -94,7 +91,7 @@ func TestSocketClientVote(t *testing.T) {
assert.Equal(t, want.Signature, have.Signature)
}
func TestSocketClientHeartbeat(t *testing.T) {
func TestSocketPVHeartbeat(t *testing.T) {
var (
chainID = cmn.RandStr(12)
sc, rs = testSetupSocketPair(t, chainID)
@ -110,9 +107,9 @@ func TestSocketClientHeartbeat(t *testing.T) {
assert.Equal(t, want.Signature, have.Signature)
}
func TestSocketClientAcceptDeadline(t *testing.T) {
func TestSocketPVAcceptDeadline(t *testing.T) {
var (
sc = NewSocketClient(
sc = NewSocketPV(
log.TestingLogger(),
"127.0.0.1:0",
crypto.GenPrivKeyEd25519(),
@ -120,26 +117,26 @@ func TestSocketClientAcceptDeadline(t *testing.T) {
)
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 (
addr = testFreeAddr(t)
listenc = make(chan struct{})
sc = NewSocketClient(
sc = NewSocketPV(
log.TestingLogger(),
addr,
crypto.GenPrivKeyEd25519(),
)
)
SocketClientConnDeadline(10 * time.Millisecond)(sc)
SocketClientConnWait(500 * time.Millisecond)(sc)
SocketPVConnDeadline(10 * time.Millisecond)(sc)
SocketPVConnWait(500 * time.Millisecond)(sc)
go func(sc *SocketClient) {
go func(sc *SocketPV) {
defer close(listenc)
require.NoError(t, sc.Start())
@ -155,7 +152,7 @@ func TestSocketClientDeadline(t *testing.T) {
_, err = p2pconn.MakeSecretConnection(
conn,
crypto.GenPrivKeyEd25519().Wrap(),
crypto.GenPrivKeyEd25519(),
)
if err == nil {
break
@ -167,21 +164,21 @@ func TestSocketClientDeadline(t *testing.T) {
// Sleep to guarantee deadline has been hit.
time.Sleep(20 * time.Microsecond)
_, err := sc.PubKey()
assert.Equal(t, errors.Cause(err), ErrConnTimeout)
_, err := sc.getPubKey()
assert.Equal(t, err.(cmn.Error).Cause(), ErrConnTimeout)
}
func TestSocketClientWait(t *testing.T) {
sc := NewSocketClient(
func TestSocketPVWait(t *testing.T) {
sc := NewSocketPV(
log.TestingLogger(),
"127.0.0.1:0",
crypto.GenPrivKeyEd25519(),
)
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) {
@ -216,7 +213,7 @@ func TestRemoteSignerRetry(t *testing.T) {
log.TestingLogger(),
cmn.RandStr(12),
ln.Addr().String(),
NewTestPrivValidator(types.GenSigner()),
types.NewMockPV(),
crypto.GenPrivKeyEd25519(),
)
defer rs.Stop()
@ -224,7 +221,7 @@ func TestRemoteSignerRetry(t *testing.T) {
RemoteSignerConnDeadline(time.Millisecond)(rs)
RemoteSignerConnRetries(retries)(rs)
assert.Equal(t, errors.Cause(rs.Start()), ErrDialRetryMax)
assert.Equal(t, rs.Start().(cmn.Error).Cause(), ErrDialRetryMax)
select {
case attempts := <-attemptc:
@ -237,12 +234,11 @@ func TestRemoteSignerRetry(t *testing.T) {
func testSetupSocketPair(
t *testing.T,
chainID string,
) (*SocketClient, *RemoteSigner) {
) (*SocketPV, *RemoteSigner) {
var (
addr = testFreeAddr(t)
logger = log.TestingLogger()
signer = types.GenSigner()
privVal = NewTestPrivValidator(signer)
privVal = types.NewMockPV()
readyc = make(chan struct{})
rs = NewRemoteSigner(
logger,
@ -251,14 +247,14 @@ func testSetupSocketPair(
privVal,
crypto.GenPrivKeyEd25519(),
)
sc = NewSocketClient(
sc = NewSocketPV(
logger,
addr,
crypto.GenPrivKeyEd25519(),
)
)
go func(sc *SocketClient) {
go func(sc *SocketPV) {
require.NoError(t, sc.Start())
assert.True(t, sc.IsRunning())


+ 0
- 66
types/priv_validator/unencrypted.go View File

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

+ 0
- 59
types/priv_validator/upgrade.go View File

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

+ 0
- 21
types/priv_validator/upgrade_pv/main.go View File

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

+ 13
- 0
types/priv_validator/wire.go View File

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

+ 0
- 255
types/priv_validator_test.go View File

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

+ 1
- 5
types/proposal.go View File

@ -6,7 +6,6 @@ import (
"time"
"github.com/tendermint/go-crypto"
"github.com/tendermint/tendermint/wire"
)
var (
@ -51,10 +50,7 @@ func (p *Proposal) String() string {
// SignBytes returns the Proposal bytes for signing
func (p *Proposal) SignBytes(chainID string) []byte {
bz, err := wire.MarshalJSON(CanonicalJSONOnceProposal{
ChainID: chainID,
Proposal: CanonicalProposal(p),
})
bz, err := cdc.MarshalJSON(CanonicalProposal(chainID, p))
if err != nil {
panic(err)
}


+ 14
- 16
types/proposal_test.go View File

@ -5,8 +5,6 @@ import (
"time"
"github.com/stretchr/testify/require"
wire "github.com/tendermint/tendermint/wire"
)
var testProposal *Proposal
@ -29,7 +27,7 @@ func TestProposalSignable(t *testing.T) {
signBytes := testProposal.SignBytes("test_chain_id")
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 {
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) {
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 {
t.Errorf("Got unexpected string for Proposal. Expected:\n%v\nGot:\n%v", expected, str)
}
}
func TestProposalVerifySignature(t *testing.T) {
privVal := GenPrivValidatorFS("")
privVal := NewMockPV()
pubKey := privVal.GetPubKey()
prop := NewProposal(4, 2, PartSetHeader{777, []byte("proper")}, 2, BlockID{})
signBytes := prop.SignBytes("test_chain_id")
// sign it
signature, err := privVal.Signer.Sign(signBytes)
err := privVal.SignProposal("test_chain_id", prop)
require.NoError(t, err)
// verify the same proposal
valid := pubKey.VerifyBytes(prop.SignBytes("test_chain_id"), signature)
valid := pubKey.VerifyBytes(signBytes, prop.Signature)
require.True(t, valid)
// serialize, deserialize and verify again....
newProp := new(Proposal)
bs, err := wire.MarshalBinary(prop)
bs, err := cdc.MarshalBinary(prop)
require.NoError(t, err)
err = wire.UnmarshalBinary(bs, &newProp)
err = cdc.UnmarshalBinary(bs, &newProp)
require.NoError(t, err)
// verify the transmitted proposal
newSignBytes := newProp.SignBytes("test_chain_id")
require.Equal(t, string(signBytes), string(newSignBytes))
valid = pubKey.VerifyBytes(newSignBytes, signature)
valid = pubKey.VerifyBytes(newSignBytes, newProp.Signature)
require.True(t, valid)
}
@ -79,9 +77,9 @@ func BenchmarkProposalWriteSignBytes(b *testing.B) {
}
func BenchmarkProposalSign(b *testing.B) {
privVal := GenPrivValidatorFS("")
privVal := NewMockPV()
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 {
b.Error(err)
}
@ -89,12 +87,12 @@ func BenchmarkProposalSign(b *testing.B) {
}
func BenchmarkProposalVerifySignature(b *testing.B) {
signBytes := testProposal.SignBytes("test_chain_id")
privVal := GenPrivValidatorFS("")
signature, _ := privVal.Signer.Sign(signBytes)
privVal := NewMockPV()
err := privVal.SignProposal("test_chain_id", testProposal)
require.Nil(b, err)
pubKey := privVal.GetPubKey()
for i := 0; i < b.N; i++ {
pubKey.VerifyBytes(testProposal.SignBytes("test_chain_id"), signature)
pubKey.VerifyBytes(testProposal.SignBytes("test_chain_id"), testProposal.Signature)
}
}

+ 3
- 3
types/results.go View File

@ -2,7 +2,6 @@ package types
import (
abci "github.com/tendermint/abci/types"
wire "github.com/tendermint/tendermint/wire"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/merkle"
)
@ -18,7 +17,8 @@ type ABCIResult struct {
// Hash returns the canonical hash of the ABCIResult
func (a ABCIResult) Hash() []byte {
return tmHash(a)
bz := aminoHash(a)
return bz
}
// 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
func (a ABCIResults) Bytes() []byte {
bz, err := wire.MarshalBinary(a)
bz, err := cdc.MarshalBinary(a)
if err != nil {
panic(err)
}


+ 8
- 6
types/results_test.go View File

@ -4,6 +4,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestABCIResults(t *testing.T) {
@ -14,13 +15,15 @@ func TestABCIResults(t *testing.T) {
e := ABCIResult{Code: 14, Data: []byte("foo")}
f := ABCIResult{Code: 14, Data: []byte("bar")}
// nil and []byte{} should produce same hash
assert.Equal(t, a.Hash(), b.Hash())
// Nil and []byte{} should not produce the same 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}
// make sure each result hashes properly
// Make sure each result hashes properly.
var last []byte
for i, res := range results {
h := res.Hash()
@ -28,8 +31,7 @@ func TestABCIResults(t *testing.T) {
last = h
}
// make sure that we can get a root hash from results
// and verify proofs
// Make sure that we can get a root hash from results and verify proofs.
root := results.Hash()
assert.NotEmpty(t, root)


+ 0
- 5
types/signable.go View File

@ -9,8 +9,3 @@ package types
type Signable interface {
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))
}

+ 3
- 3
types/test_util.go View File

@ -4,7 +4,7 @@ import "time"
func MakeCommit(blockID BlockID, height int64, round int,
voteSet *VoteSet,
validators []*PrivValidatorFS) (*Commit, error) {
validators []PrivValidator) (*Commit, error) {
// all sign
for i := 0; i < len(validators); i++ {
@ -28,8 +28,8 @@ func MakeCommit(blockID BlockID, height int64, round int,
return voteSet.MakeCommit(), nil
}
func signAddVote(privVal *PrivValidatorFS, vote *Vote, voteSet *VoteSet) (signed bool, err error) {
vote.Signature, err = privVal.Signer.Sign(vote.SignBytes(voteSet.ChainID()))
func signAddVote(privVal PrivValidator, vote *Vote, voteSet *VoteSet) (signed bool, err error) {
err = privVal.SignVote(voteSet.ChainID(), vote)
if err != nil {
return false, err
}


+ 8
- 5
types/tx.go View File

@ -18,7 +18,7 @@ type Tx []byte
// Hash computes the RIPEMD160 hash of the wire encoded transaction.
func (tx Tx) Hash() []byte {
return wireHasher(tx).Hash()
return aminoHasher(tx).Hash()
}
// 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.
//
// Panics if i < 0 or i >= len(txs)
//
// TODO: optimize this!
func (txs Txs) Proof(i int) TxProof {
l := len(txs)
@ -95,7 +93,7 @@ type TxProof struct {
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 {
return tp.Data.Hash()
}
@ -106,7 +104,12 @@ func (tp TxProof) Validate(dataHash []byte) error {
if !bytes.Equal(dataHash, tp.RootHash) {
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)
if !valid {
return errors.New("Proof is not internally consistent")


+ 9
- 9
types/tx_test.go View File

@ -6,7 +6,6 @@ import (
"github.com/stretchr/testify/assert"
wire "github.com/tendermint/tendermint/wire"
cmn "github.com/tendermint/tmlibs/common"
ctest "github.com/tendermint/tmlibs/test"
)
@ -69,9 +68,9 @@ func TestValidTxProof(t *testing.T) {
// read-write must also work
var p2 TxProof
bin, err := wire.MarshalBinary(proof)
bin, err := cdc.MarshalBinary(proof)
assert.Nil(err)
err = wire.UnmarshalBinary(bin, &p2)
err = cdc.UnmarshalBinary(bin, &p2)
if assert.Nil(err, "%d: %d: %+v", h, i, err) {
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
assert.Nil(proof.Validate(root))
bin, err := wire.MarshalBinary(proof)
bin, err := cdc.MarshalBinary(proof)
assert.Nil(err)
// 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) {
var proof TxProof
err := wire.UnmarshalBinary(bad, &proof)
err := cdc.UnmarshalBinary(bad, &proof)
if err == nil {
err = proof.Validate(root)
if err == nil {
// okay, this can happen if we have a slightly different total
// (where the path ends up the same), if it is something else, we have
// a real problem
// XXX Fix simple merkle proofs so the following is *not* OK.
// This can happen if we have a slightly different total (where the
// 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)
}
}


+ 5
- 6
types/validator.go View File

@ -70,7 +70,7 @@ func (v *Validator) String() string {
// Hash computes the unique ID of a validator with a given voting power.
// It excludes the Accum value, which changes with every round.
func (v *Validator) Hash() []byte {
return tmHash(struct {
return aminoHash(struct {
Address Address
PubKey crypto.PubKey
VotingPower int64
@ -81,14 +81,13 @@ func (v *Validator) Hash() []byte {
})
}
//--------------------------------------------------------------------------------
// For testing...
//----------------------------------------
// RandValidator
// RandValidator returns a randomized validator, useful for testing.
// UNSTABLE
func RandValidator(randPower bool, minPower int64) (*Validator, *PrivValidatorFS) {
_, tempFilePath := cmn.Tempfile("priv_validator_")
privVal := GenPrivValidatorFS(tempFilePath)
func RandValidator(randPower bool, minPower int64) (*Validator, PrivValidator) {
privVal := NewMockPV()
votePower := minPower
if randPower {
votePower += int64(cmn.RandUint32())


+ 10
- 11
types/validator_set.go View File

@ -7,7 +7,6 @@ import (
"sort"
"strings"
"github.com/pkg/errors"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/merkle"
)
@ -289,10 +288,10 @@ func (valSet *ValidatorSet) VerifyCommitAny(newSet *ValidatorSet, chainID string
blockID BlockID, height int64, commit *Commit) error {
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() {
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)
@ -307,13 +306,13 @@ func (valSet *ValidatorSet) VerifyCommitAny(newSet *ValidatorSet, chainID string
}
if precommit.Height != 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 {
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 {
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) {
continue // Not an error, but doesn't count
@ -329,7 +328,7 @@ func (valSet *ValidatorSet) VerifyCommitAny(newSet *ValidatorSet, chainID string
// Validate signature old school
precommitSignBytes := precommit.SignBytes(chainID)
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!
oldVotingPower += ov.VotingPower
@ -343,10 +342,10 @@ func (valSet *ValidatorSet) VerifyCommitAny(newSet *ValidatorSet, chainID string
}
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))
} 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))
}
return nil
@ -416,9 +415,9 @@ func (ac accumComparable) Less(o interface{}) bool {
// RandValidatorSet returns a randomized validator set, useful for testing.
// NOTE: PrivValidator are in order.
// UNSTABLE
func RandValidatorSet(numValidators int, votingPower int64) (*ValidatorSet, []*PrivValidatorFS) {
func RandValidatorSet(numValidators int, votingPower int64) (*ValidatorSet, []PrivValidator) {
vals := make([]*Validator, numValidators)
privValidators := make([]*PrivValidatorFS, numValidators)
privValidators := make([]PrivValidator, numValidators)
for i := 0; i < numValidators; i++ {
val, privValidator := RandValidator(false, votingPower)
vals[i] = val


+ 3
- 4
types/validator_set_test.go View File

@ -11,7 +11,6 @@ import (
"github.com/stretchr/testify/assert"
crypto "github.com/tendermint/go-crypto"
wire "github.com/tendermint/tendermint/wire"
cmn "github.com/tendermint/tmlibs/common"
)
@ -198,7 +197,7 @@ func newValidator(address []byte, power int64) *Validator {
func randPubKey() crypto.PubKey {
var pubKey [32]byte
copy(pubKey[:], cmn.RandBytes(32))
return crypto.PubKeyEd25519(pubKey).Wrap()
return crypto.PubKeyEd25519(pubKey)
}
func randValidator_() *Validator {
@ -216,7 +215,7 @@ func randValidatorSet(numValidators int) *ValidatorSet {
}
func (valSet *ValidatorSet) toBytes() []byte {
bz, err := wire.MarshalBinary(valSet)
bz, err := cdc.MarshalBinary(valSet)
if err != nil {
panic(err)
}
@ -224,7 +223,7 @@ func (valSet *ValidatorSet) toBytes() []byte {
}
func (valSet *ValidatorSet) fromBytes(b []byte) {
err := wire.UnmarshalBinary(b, &valSet)
err := cdc.UnmarshalBinary(b, &valSet)
if err != nil {
// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
panic(err)


+ 1
- 5
types/vote.go View File

@ -7,7 +7,6 @@ import (
"time"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/tendermint/wire"
cmn "github.com/tendermint/tmlibs/common"
)
@ -73,10 +72,7 @@ type Vote struct {
}
func (vote *Vote) SignBytes(chainID string) []byte {
bz, err := wire.MarshalJSON(CanonicalJSONOnceVote{
chainID,
CanonicalVote(vote),
})
bz, err := cdc.MarshalJSON(CanonicalVote(chainID, vote))
if err != nil {
panic(err)
}


+ 1
- 1
types/vote_set_test.go View File

@ -11,7 +11,7 @@ import (
)
// 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)
return NewVoteSet("test_chain_id", height, round, type_, valSet), valSet, privValidators
}


+ 9
- 11
types/vote_test.go View File

@ -5,8 +5,6 @@ import (
"time"
"github.com/stretchr/testify/require"
wire "github.com/tendermint/tendermint/wire"
)
func examplePrevote() *Vote {
@ -45,7 +43,7 @@ func TestVoteSignable(t *testing.T) {
signBytes := vote.SignBytes("test_chain_id")
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 {
// 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)
@ -58,8 +56,8 @@ func TestVoteString(t *testing.T) {
in string
out string
}{
{"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}`},
{"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}`},
}
for _, tt := range tc {
@ -73,31 +71,31 @@ func TestVoteString(t *testing.T) {
}
func TestVoteVerifySignature(t *testing.T) {
privVal := GenPrivValidatorFS("")
privVal := NewMockPV()
pubKey := privVal.GetPubKey()
vote := examplePrecommit()
signBytes := vote.SignBytes("test_chain_id")
// sign it
signature, err := privVal.Signer.Sign(signBytes)
err := privVal.SignVote("test_chain_id", vote)
require.NoError(t, err)
// 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)
// serialize, deserialize and verify again....
precommit := new(Vote)
bs, err := wire.MarshalBinary(vote)
bs, err := cdc.MarshalBinary(vote)
require.NoError(t, err)
err = wire.UnmarshalBinary(bs, &precommit)
err = cdc.UnmarshalBinary(bs, &precommit)
require.NoError(t, err)
// verify the transmitted vote
newSignBytes := precommit.SignBytes("test_chain_id")
require.Equal(t, string(signBytes), string(newSignBytes))
valid = pubKey.VerifyBytes(newSignBytes, signature)
valid = pubKey.VerifyBytes(newSignBytes, precommit.Signature)
require.True(t, valid)
}


+ 12
- 0
types/wire.go View File

@ -0,0 +1,12 @@
package types
import (
"github.com/tendermint/go-amino"
"github.com/tendermint/go-crypto"
)
var cdc = amino.NewCodec()
func init() {
crypto.RegisterAmino(cdc)
}

Loading…
Cancel
Save