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

This commit is contained in:
Jae Kwon 2018-03-31 00:18:43 +02:00
parent 901b456151
commit 34974e3932
40 changed files with 856 additions and 1899 deletions

View File

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

View File

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

View File

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

View File

@ -3,14 +3,14 @@ package types
import ( import (
"time" "time"
wire "github.com/tendermint/tendermint/wire" "github.com/tendermint/go-amino"
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
) )
// canonical json is wire's json for structs with fields in alphabetical order // Canonical json is amino's json for structs with fields in alphabetical order
// TimeFormat is used for generating the sigs // TimeFormat is used for generating the sigs
const TimeFormat = wire.RFC3339Millis const TimeFormat = amino.RFC3339Millis
type CanonicalJSONBlockID struct { type CanonicalJSONBlockID struct {
Hash cmn.HexBytes `json:"hash,omitempty"` Hash cmn.HexBytes `json:"hash,omitempty"`
@ -18,11 +18,13 @@ type CanonicalJSONBlockID struct {
} }
type CanonicalJSONPartSetHeader struct { type CanonicalJSONPartSetHeader struct {
Hash cmn.HexBytes `json:"hash"` Hash cmn.HexBytes `json:"hash,omitempty"`
Total int `json:"total"` Total int `json:"total,omitempty"`
} }
type CanonicalJSONProposal struct { type CanonicalJSONProposal struct {
ChainID string `json:"@chain_id"`
Type string `json:"@type"`
BlockPartsHeader CanonicalJSONPartSetHeader `json:"block_parts_header"` BlockPartsHeader CanonicalJSONPartSetHeader `json:"block_parts_header"`
Height int64 `json:"height"` Height int64 `json:"height"`
POLBlockID CanonicalJSONBlockID `json:"pol_block_id"` POLBlockID CanonicalJSONBlockID `json:"pol_block_id"`
@ -32,14 +34,18 @@ type CanonicalJSONProposal struct {
} }
type CanonicalJSONVote struct { type CanonicalJSONVote struct {
ChainID string `json:"@chain_id"`
Type string `json:"@type"`
BlockID CanonicalJSONBlockID `json:"block_id"` BlockID CanonicalJSONBlockID `json:"block_id"`
Height int64 `json:"height"` Height int64 `json:"height"`
Round int `json:"round"` Round int `json:"round"`
Timestamp string `json:"timestamp"` Timestamp string `json:"timestamp"`
Type byte `json:"type"` VoteType byte `json:"type"`
} }
type CanonicalJSONHeartbeat struct { type CanonicalJSONHeartbeat struct {
ChainID string `json:"@chain_id"`
Type string `json:"@type"`
Height int64 `json:"height"` Height int64 `json:"height"`
Round int `json:"round"` Round int `json:"round"`
Sequence int `json:"sequence"` Sequence int `json:"sequence"`
@ -47,24 +53,6 @@ type CanonicalJSONHeartbeat struct {
ValidatorIndex int `json:"validator_index"` ValidatorIndex int `json:"validator_index"`
} }
//------------------------------------
// Messages including a "chain id" can only be applied to one chain, hence "Once"
type CanonicalJSONOnceProposal struct {
ChainID string `json:"chain_id"`
Proposal CanonicalJSONProposal `json:"proposal"`
}
type CanonicalJSONOnceVote struct {
ChainID string `json:"chain_id"`
Vote CanonicalJSONVote `json:"vote"`
}
type CanonicalJSONOnceHeartbeat struct {
ChainID string `json:"chain_id"`
Heartbeat CanonicalJSONHeartbeat `json:"heartbeat"`
}
//----------------------------------- //-----------------------------------
// Canonicalize the structs // Canonicalize the structs
@ -82,8 +70,10 @@ func CanonicalPartSetHeader(psh PartSetHeader) CanonicalJSONPartSetHeader {
} }
} }
func CanonicalProposal(proposal *Proposal) CanonicalJSONProposal { func CanonicalProposal(chainID string, proposal *Proposal) CanonicalJSONProposal {
return CanonicalJSONProposal{ return CanonicalJSONProposal{
ChainID: chainID,
Type: "proposal",
BlockPartsHeader: CanonicalPartSetHeader(proposal.BlockPartsHeader), BlockPartsHeader: CanonicalPartSetHeader(proposal.BlockPartsHeader),
Height: proposal.Height, Height: proposal.Height,
Timestamp: CanonicalTime(proposal.Timestamp), Timestamp: CanonicalTime(proposal.Timestamp),
@ -93,28 +83,32 @@ func CanonicalProposal(proposal *Proposal) CanonicalJSONProposal {
} }
} }
func CanonicalVote(vote *Vote) CanonicalJSONVote { func CanonicalVote(chainID string, vote *Vote) CanonicalJSONVote {
return CanonicalJSONVote{ return CanonicalJSONVote{
ChainID: chainID,
Type: "vote",
BlockID: CanonicalBlockID(vote.BlockID), BlockID: CanonicalBlockID(vote.BlockID),
Height: vote.Height, Height: vote.Height,
Round: vote.Round, Round: vote.Round,
Timestamp: CanonicalTime(vote.Timestamp), Timestamp: CanonicalTime(vote.Timestamp),
Type: vote.Type, VoteType: vote.Type,
} }
} }
func CanonicalHeartbeat(heartbeat *Heartbeat) CanonicalJSONHeartbeat { func CanonicalHeartbeat(chainID string, heartbeat *Heartbeat) CanonicalJSONHeartbeat {
return CanonicalJSONHeartbeat{ return CanonicalJSONHeartbeat{
heartbeat.Height, ChainID: chainID,
heartbeat.Round, Type: "heartbeat",
heartbeat.Sequence, Height: heartbeat.Height,
heartbeat.ValidatorAddress, Round: heartbeat.Round,
heartbeat.ValidatorIndex, Sequence: heartbeat.Sequence,
ValidatorAddress: heartbeat.ValidatorAddress,
ValidatorIndex: heartbeat.ValidatorIndex,
} }
} }
func CanonicalTime(t time.Time) string { func CanonicalTime(t time.Time) string {
// note that sending time over wire resets it to // Note that sending time over amino resets it to
// local time, we need to force UTC here, so the // local time, we need to force UTC here, so the
// signatures match // signatures match
return t.UTC().Format(TimeFormat) return t.UTC().Format(TimeFormat)

View File

@ -4,8 +4,8 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/tendermint/go-amino"
"github.com/tendermint/go-crypto" "github.com/tendermint/go-crypto"
wire "github.com/tendermint/tendermint/wire"
"github.com/tendermint/tmlibs/merkle" "github.com/tendermint/tmlibs/merkle"
) )
@ -38,56 +38,11 @@ type Evidence interface {
String() string String() string
} }
//------------------------------------------- func RegisterEvidence(cdc *amino.Codec) {
cdc.RegisterInterface((*Evidence)(nil), nil)
// EvidenceList is a list of Evidence. Evidences is not a word. cdc.RegisterConcrete(&DuplicateVoteEvidence{}, "tendermint/Evidence", nil)
type EvidenceList []Evidence
// Hash returns the simple merkle root hash of the EvidenceList.
func (evl EvidenceList) Hash() []byte {
// Recursive impl.
// Copied from tmlibs/merkle to avoid allocations
switch len(evl) {
case 0:
return nil
case 1:
return evl[0].Hash()
default:
left := EvidenceList(evl[:(len(evl)+1)/2]).Hash()
right := EvidenceList(evl[(len(evl)+1)/2:]).Hash()
return merkle.SimpleHashFromTwoHashes(left, right)
}
} }
func (evl EvidenceList) String() string {
s := ""
for _, e := range evl {
s += fmt.Sprintf("%s\t\t", e)
}
return s
}
// Has returns true if the evidence is in the EvidenceList.
func (evl EvidenceList) Has(evidence Evidence) bool {
for _, ev := range evl {
if ev.Equal(evidence) {
return true
}
}
return false
}
//-------------------------------------------
const (
evidenceTypeDuplicateVote = byte(0x01)
)
var _ = wire.RegisterInterface(
struct{ Evidence }{},
wire.ConcreteType{&DuplicateVoteEvidence{}, evidenceTypeDuplicateVote},
)
//------------------------------------------- //-------------------------------------------
// DuplicateVoteEvidence contains evidence a validator signed two conflicting votes. // DuplicateVoteEvidence contains evidence a validator signed two conflicting votes.
@ -120,7 +75,7 @@ func (dve *DuplicateVoteEvidence) Index() int {
// Hash returns the hash of the evidence. // Hash returns the hash of the evidence.
func (dve *DuplicateVoteEvidence) Hash() []byte { func (dve *DuplicateVoteEvidence) Hash() []byte {
return wireHasher(dve).Hash() return aminoHasher(dve).Hash()
} }
// Verify returns an error if the two votes aren't conflicting. // Verify returns an error if the two votes aren't conflicting.
@ -165,8 +120,8 @@ func (dve *DuplicateVoteEvidence) Equal(ev Evidence) bool {
} }
// just check their hashes // just check their hashes
dveHash := wireHasher(dve).Hash() dveHash := aminoHasher(dve).Hash()
evHash := wireHasher(ev).Hash() evHash := aminoHasher(ev).Hash()
return bytes.Equal(dveHash, evHash) return bytes.Equal(dveHash, evHash)
} }
@ -216,3 +171,42 @@ func (e MockBadEvidence) Equal(ev Evidence) bool {
func (e MockBadEvidence) String() string { func (e MockBadEvidence) String() string {
return fmt.Sprintf("BadEvidence: %d/%s/%d", e.Height_, e.Address_, e.Index_) return fmt.Sprintf("BadEvidence: %d/%s/%d", e.Height_, e.Address_, e.Index_)
} }
//-------------------------------------------
// EvidenceList is a list of Evidence. Evidences is not a word.
type EvidenceList []Evidence
// Hash returns the simple merkle root hash of the EvidenceList.
func (evl EvidenceList) Hash() []byte {
// Recursive impl.
// Copied from tmlibs/merkle to avoid allocations
switch len(evl) {
case 0:
return nil
case 1:
return evl[0].Hash()
default:
left := EvidenceList(evl[:(len(evl)+1)/2]).Hash()
right := EvidenceList(evl[(len(evl)+1)/2:]).Hash()
return merkle.SimpleHashFromTwoHashes(left, right)
}
}
func (evl EvidenceList) String() string {
s := ""
for _, e := range evl {
s += fmt.Sprintf("%s\t\t", e)
}
return s
}
// Has returns true if the evidence is in the EvidenceList.
func (evl EvidenceList) Has(evidence Evidence) bool {
for _, ev := range evl {
if ev.Equal(evidence) {
return true
}
}
return false
}

View File

@ -4,7 +4,6 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
cmn "github.com/tendermint/tmlibs/common"
) )
type voteData struct { type voteData struct {
@ -13,25 +12,25 @@ type voteData struct {
valid bool valid bool
} }
func makeVote(val *PrivValidatorFS, chainID string, valIndex int, height int64, round, step int, blockID BlockID) *Vote { func makeVote(val PrivValidator, chainID string, valIndex int, height int64, round, step int, blockID BlockID) *Vote {
v := &Vote{ v := &Vote{
ValidatorAddress: val.PubKey.Address(), ValidatorAddress: val.GetAddress(),
ValidatorIndex: valIndex, ValidatorIndex: valIndex,
Height: height, Height: height,
Round: round, Round: round,
Type: byte(step), Type: byte(step),
BlockID: blockID, BlockID: blockID,
} }
sig := val.PrivKey.Sign(v.SignBytes(chainID)) err := val.SignVote(chainID, v)
v.Signature = sig if err != nil {
panic(err)
}
return v return v
} }
func TestEvidence(t *testing.T) { func TestEvidence(t *testing.T) {
_, tmpFilePath := cmn.Tempfile("priv_validator_") val := NewMockPV()
val := GenPrivValidatorFS(tmpFilePath) val2 := NewMockPV()
val2 := GenPrivValidatorFS(tmpFilePath)
blockID := makeBlockID("blockhash", 1000, "partshash") blockID := makeBlockID("blockhash", 1000, "partshash")
blockID2 := makeBlockID("blockhash2", 1000, "partshash") blockID2 := makeBlockID("blockhash2", 1000, "partshash")
blockID3 := makeBlockID("blockhash", 10000, "partshash") blockID3 := makeBlockID("blockhash", 10000, "partshash")
@ -41,7 +40,10 @@ func TestEvidence(t *testing.T) {
vote1 := makeVote(val, chainID, 0, 10, 2, 1, blockID) vote1 := makeVote(val, chainID, 0, 10, 2, 1, blockID)
badVote := makeVote(val, chainID, 0, 10, 2, 1, blockID) badVote := makeVote(val, chainID, 0, 10, 2, 1, blockID)
badVote.Signature = val2.PrivKey.Sign(badVote.SignBytes(chainID)) err := val2.SignVote(chainID, badVote)
if err != nil {
panic(err)
}
cases := []voteData{ cases := []voteData{
{vote1, makeVote(val, chainID, 0, 10, 2, 1, blockID2), true}, // different block ids {vote1, makeVote(val, chainID, 0, 10, 2, 1, blockID2), true}, // different block ids
@ -59,7 +61,7 @@ func TestEvidence(t *testing.T) {
for _, c := range cases { for _, c := range cases {
ev := &DuplicateVoteEvidence{ ev := &DuplicateVoteEvidence{
PubKey: val.PubKey, PubKey: val.GetPubKey(),
VoteA: c.vote1, VoteA: c.vote1,
VoteB: c.vote2, VoteB: c.vote2,
} }

View File

@ -5,9 +5,7 @@ import (
"io/ioutil" "io/ioutil"
"time" "time"
"github.com/pkg/errors" "github.com/tendermint/go-crypto"
crypto "github.com/tendermint/go-crypto"
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
) )
@ -33,7 +31,7 @@ type GenesisDoc struct {
// SaveAs is a utility method for saving GenensisDoc as a JSON file. // SaveAs is a utility method for saving GenensisDoc as a JSON file.
func (genDoc *GenesisDoc) SaveAs(file string) error { func (genDoc *GenesisDoc) SaveAs(file string) error {
genDocBytes, err := json.Marshal(genDoc) genDocBytes, err := cdc.MarshalJSON(genDoc)
if err != nil { if err != nil {
return err return err
} }
@ -55,7 +53,7 @@ func (genDoc *GenesisDoc) ValidatorHash() []byte {
func (genDoc *GenesisDoc) ValidateAndComplete() error { func (genDoc *GenesisDoc) ValidateAndComplete() error {
if genDoc.ChainID == "" { if genDoc.ChainID == "" {
return errors.Errorf("Genesis doc must include non-empty chain_id") return cmn.NewError("Genesis doc must include non-empty chain_id")
} }
if genDoc.ConsensusParams == nil { if genDoc.ConsensusParams == nil {
@ -67,12 +65,12 @@ func (genDoc *GenesisDoc) ValidateAndComplete() error {
} }
if len(genDoc.Validators) == 0 { if len(genDoc.Validators) == 0 {
return errors.Errorf("The genesis file must have at least one validator") return cmn.NewError("The genesis file must have at least one validator")
} }
for _, v := range genDoc.Validators { for _, v := range genDoc.Validators {
if v.Power == 0 { if v.Power == 0 {
return errors.Errorf("The genesis file cannot contain validators with no voting power: %v", v) return cmn.NewError("The genesis file cannot contain validators with no voting power: %v", v)
} }
} }
@ -89,7 +87,7 @@ func (genDoc *GenesisDoc) ValidateAndComplete() error {
// GenesisDocFromJSON unmarshalls JSON data into a GenesisDoc. // GenesisDocFromJSON unmarshalls JSON data into a GenesisDoc.
func GenesisDocFromJSON(jsonBlob []byte) (*GenesisDoc, error) { func GenesisDocFromJSON(jsonBlob []byte) (*GenesisDoc, error) {
genDoc := GenesisDoc{} genDoc := GenesisDoc{}
err := json.Unmarshal(jsonBlob, &genDoc) err := cdc.UnmarshalJSON(jsonBlob, &genDoc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -105,11 +103,11 @@ func GenesisDocFromJSON(jsonBlob []byte) (*GenesisDoc, error) {
func GenesisDocFromFile(genDocFile string) (*GenesisDoc, error) { func GenesisDocFromFile(genDocFile string) (*GenesisDoc, error) {
jsonBlob, err := ioutil.ReadFile(genDocFile) jsonBlob, err := ioutil.ReadFile(genDocFile)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "Couldn't read GenesisDoc file") return nil, cmn.ErrorWrap(err, "Couldn't read GenesisDoc file")
} }
genDoc, err := GenesisDocFromJSON(jsonBlob) genDoc, err := GenesisDocFromJSON(jsonBlob)
if err != nil { if err != nil {
return nil, errors.Wrap(err, cmn.Fmt("Error reading GenesisDoc at %v", genDocFile)) return nil, cmn.ErrorWrap(err, cmn.Fmt("Error reading GenesisDoc at %v", genDocFile))
} }
return genDoc, nil return genDoc, nil
} }

View File

@ -1,35 +1,34 @@
package types package types
import ( import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tendermint/go-crypto"
crypto "github.com/tendermint/go-crypto" "testing"
) )
func TestGenesis(t *testing.T) { func TestGenesisBad(t *testing.T) {
// test some bad ones from raw json // test some bad ones from raw json
testCases := [][]byte{ testCases := [][]byte{
[]byte{}, // empty []byte{}, // empty
[]byte{1, 1, 1, 1, 1}, // junk []byte{1, 1, 1, 1, 1}, // junk
[]byte(`{}`), // empty []byte(`{}`), // empty
[]byte(`{"chain_id": "mychain"}`), // missing validators []byte(`{"chain_id":"mychain"}`), // missing validators
[]byte(`{"chain_id": "mychain", "validators": []`), // missing validators []byte(`{"chain_id":"mychain","validators":[]}`), // missing validators
[]byte(`{"chain_id": "mychain", "validators": [{}]`), // missing validators []byte(`{"chain_id":"mychain","validators":[{}]}`), // missing validators
[]byte(`{"validators":[{"pub_key": []byte(`{"chain_id":"mychain","validators":null}`), // missing validators
{"type":"ed25519","data":"961EAB8752E51A03618502F55C2B6E09C38C65635C64CCF3173ED452CF86C957"}, []byte(`{"chain_id":"mychain"}`), // missing validators
"power":10,"name":""}]}`), // missing chain_id []byte(`{"validators":[{"pub_key":{"type":"AC26791624DE60","value":"AT/+aaL1eB0477Mud9JMm8Sh8BIvOYlPGC9KkIUmFaE="},"power":10,"name":""}]}`), // missing chain_id
} }
for _, testCase := range testCases { for _, testCase := range testCases {
_, err := GenesisDocFromJSON(testCase) _, err := GenesisDocFromJSON(testCase)
assert.Error(t, err, "expected error for empty genDoc json") assert.Error(t, err, "expected error for empty genDoc json")
} }
}
func TestGenesisGood(t *testing.T) {
// test a good one by raw json // test a good one by raw json
genDocBytes := []byte(`{"genesis_time":"0001-01-01T00:00:00Z","chain_id":"test-chain-QDKdJr","consensus_params":null,"validators":[{"pub_key":{"type":"ed25519","data":"961EAB8752E51A03618502F55C2B6E09C38C65635C64CCF3173ED452CF86C957"},"power":10,"name":""}],"app_hash":"","app_state":{"account_owner": "Bob"}}`) genDocBytes := []byte(`{"genesis_time":"0001-01-01T00:00:00Z","chain_id":"test-chain-QDKdJr","consensus_params":null,"validators":[{"pub_key":{"type":"AC26791624DE60","value":"AT/+aaL1eB0477Mud9JMm8Sh8BIvOYlPGC9KkIUmFaE="},"power":10,"name":""}],"app_hash":"","app_state":{"account_owner": "Bob"}}`)
_, err := GenesisDocFromJSON(genDocBytes) _, err := GenesisDocFromJSON(genDocBytes)
assert.NoError(t, err, "expected no error for good genDoc json") assert.NoError(t, err, "expected no error for good genDoc json")
@ -38,7 +37,7 @@ func TestGenesis(t *testing.T) {
ChainID: "abc", ChainID: "abc",
Validators: []GenesisValidator{{crypto.GenPrivKeyEd25519().PubKey(), 10, "myval"}}, Validators: []GenesisValidator{{crypto.GenPrivKeyEd25519().PubKey(), 10, "myval"}},
} }
genDocBytes, err = json.Marshal(baseGenDoc) genDocBytes, err = cdc.MarshalJSON(baseGenDoc)
assert.NoError(t, err, "error marshalling genDoc") assert.NoError(t, err, "error marshalling genDoc")
// test base gendoc and check consensus params were filled // test base gendoc and check consensus params were filled
@ -47,14 +46,14 @@ func TestGenesis(t *testing.T) {
assert.NotNil(t, genDoc.ConsensusParams, "expected consensus params to be filled in") assert.NotNil(t, genDoc.ConsensusParams, "expected consensus params to be filled in")
// create json with consensus params filled // create json with consensus params filled
genDocBytes, err = json.Marshal(genDoc) genDocBytes, err = cdc.MarshalJSON(genDoc)
assert.NoError(t, err, "error marshalling genDoc") assert.NoError(t, err, "error marshalling genDoc")
genDoc, err = GenesisDocFromJSON(genDocBytes) genDoc, err = GenesisDocFromJSON(genDocBytes)
assert.NoError(t, err, "expected no error for valid genDoc json") assert.NoError(t, err, "expected no error for valid genDoc json")
// test with invalid consensus params // test with invalid consensus params
genDoc.ConsensusParams.BlockSize.MaxBytes = 0 genDoc.ConsensusParams.BlockSize.MaxBytes = 0
genDocBytes, err = json.Marshal(genDoc) genDocBytes, err = cdc.MarshalJSON(genDoc)
assert.NoError(t, err, "error marshalling genDoc") assert.NoError(t, err, "error marshalling genDoc")
genDoc, err = GenesisDocFromJSON(genDocBytes) genDoc, err = GenesisDocFromJSON(genDocBytes)
assert.Error(t, err, "expected error for genDoc json with block size of 0") assert.Error(t, err, "expected error for genDoc json with block size of 0")

View File

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

View File

@ -25,22 +25,23 @@ func TestHeartbeatString(t *testing.T) {
require.Contains(t, nilHb.String(), "nil", "expecting a string and no panic") require.Contains(t, nilHb.String(), "nil", "expecting a string and no panic")
hb := &Heartbeat{ValidatorIndex: 1, Height: 11, Round: 2} hb := &Heartbeat{ValidatorIndex: 1, Height: 11, Round: 2}
require.Equal(t, hb.String(), "Heartbeat{1:000000000000 11/02 (0) {<nil>}}") require.Equal(t, hb.String(), "Heartbeat{1:000000000000 11/02 (0) <nil>}")
var key crypto.PrivKeyEd25519 var key crypto.PrivKeyEd25519
hb.Signature = key.Sign([]byte("Tendermint")) hb.Signature = key.Sign([]byte("Tendermint"))
require.Equal(t, hb.String(), "Heartbeat{1:000000000000 11/02 (0) {/FF41E371B9BF.../}}") require.Equal(t, hb.String(), "Heartbeat{1:000000000000 11/02 (0) /FF41E371B9BF.../}")
} }
func TestHeartbeatWriteSignBytes(t *testing.T) { func TestHeartbeatWriteSignBytes(t *testing.T) {
hb := &Heartbeat{ValidatorIndex: 1, Height: 10, Round: 1} hb := &Heartbeat{ValidatorIndex: 1, Height: 10, Round: 1}
bz := hb.SignBytes("0xdeadbeef") bz := hb.SignBytes("0xdeadbeef")
require.Equal(t, string(bz), `{"chain_id":"0xdeadbeef","heartbeat":{"height":10,"round":1,"sequence":0,"validator_address":"","validator_index":1}}`) // XXX HMMMMMMM
require.Equal(t, string(bz), `{"@chain_id":"0xdeadbeef","@type":"heartbeat","height":10,"round":1,"sequence":0,"validator_address":"","validator_index":1}`)
plainHb := &Heartbeat{} plainHb := &Heartbeat{}
bz = plainHb.SignBytes("0xdeadbeef") bz = plainHb.SignBytes("0xdeadbeef")
require.Equal(t, string(bz), `{"chain_id":"0xdeadbeef","heartbeat":{"height":0,"round":0,"sequence":0,"validator_address":"","validator_index":0}}`) require.Equal(t, string(bz), `{"@chain_id":"0xdeadbeef","@type":"heartbeat","height":0,"round":0,"sequence":0,"validator_address":"","validator_index":0}`)
require.Panics(t, func() { require.Panics(t, func() {
var nilHb *Heartbeat var nilHb *Heartbeat

View File

@ -1,9 +1,8 @@
package types package types
import ( import (
"github.com/pkg/errors"
abci "github.com/tendermint/abci/types" abci "github.com/tendermint/abci/types"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/merkle" "github.com/tendermint/tmlibs/merkle"
) )
@ -89,15 +88,15 @@ func DefaultEvidenceParams() EvidenceParams {
func (params *ConsensusParams) Validate() error { func (params *ConsensusParams) Validate() error {
// ensure some values are greater than 0 // ensure some values are greater than 0
if params.BlockSize.MaxBytes <= 0 { if params.BlockSize.MaxBytes <= 0 {
return errors.Errorf("BlockSize.MaxBytes must be greater than 0. Got %d", params.BlockSize.MaxBytes) return cmn.NewError("BlockSize.MaxBytes must be greater than 0. Got %d", params.BlockSize.MaxBytes)
} }
if params.BlockGossip.BlockPartSizeBytes <= 0 { if params.BlockGossip.BlockPartSizeBytes <= 0 {
return errors.Errorf("BlockGossip.BlockPartSizeBytes must be greater than 0. Got %d", params.BlockGossip.BlockPartSizeBytes) return cmn.NewError("BlockGossip.BlockPartSizeBytes must be greater than 0. Got %d", params.BlockGossip.BlockPartSizeBytes)
} }
// ensure blocks aren't too big // ensure blocks aren't too big
if params.BlockSize.MaxBytes > maxBlockSizeBytes { if params.BlockSize.MaxBytes > maxBlockSizeBytes {
return errors.Errorf("BlockSize.MaxBytes is too big. %d > %d", return cmn.NewError("BlockSize.MaxBytes is too big. %d > %d",
params.BlockSize.MaxBytes, maxBlockSizeBytes) params.BlockSize.MaxBytes, maxBlockSizeBytes)
} }
return nil return nil
@ -107,12 +106,12 @@ func (params *ConsensusParams) Validate() error {
// in the block header // in the block header
func (params *ConsensusParams) Hash() []byte { func (params *ConsensusParams) Hash() []byte {
return merkle.SimpleHashFromMap(map[string]merkle.Hasher{ return merkle.SimpleHashFromMap(map[string]merkle.Hasher{
"block_gossip_part_size_bytes": wireHasher(params.BlockGossip.BlockPartSizeBytes), "block_gossip_part_size_bytes": aminoHasher(params.BlockGossip.BlockPartSizeBytes),
"block_size_max_bytes": wireHasher(params.BlockSize.MaxBytes), "block_size_max_bytes": aminoHasher(params.BlockSize.MaxBytes),
"block_size_max_gas": wireHasher(params.BlockSize.MaxGas), "block_size_max_gas": aminoHasher(params.BlockSize.MaxGas),
"block_size_max_txs": wireHasher(params.BlockSize.MaxTxs), "block_size_max_txs": aminoHasher(params.BlockSize.MaxTxs),
"tx_size_max_bytes": wireHasher(params.TxSize.MaxBytes), "tx_size_max_bytes": aminoHasher(params.TxSize.MaxBytes),
"tx_size_max_gas": wireHasher(params.TxSize.MaxGas), "tx_size_max_gas": aminoHasher(params.TxSize.MaxGas),
}) })
} }

View File

@ -2,88 +2,11 @@ package types
import ( import (
"bytes" "bytes"
"encoding/json"
"errors"
"fmt" "fmt"
"io/ioutil"
"sync"
"time"
crypto "github.com/tendermint/go-crypto" "github.com/tendermint/go-crypto"
cmn "github.com/tendermint/tmlibs/common"
) )
// TODO: type ?
const (
stepNone int8 = 0 // Used to distinguish the initial state
stepPropose int8 = 1
stepPrevote int8 = 2
stepPrecommit int8 = 3
)
func voteToStep(vote *Vote) int8 {
switch vote.Type {
case VoteTypePrevote:
return stepPrevote
case VoteTypePrecommit:
return stepPrecommit
default:
cmn.PanicSanity("Unknown vote type")
return 0
}
}
//--------------------------------------------------------------
// PrivValidator is being upgraded! See types/priv_validator/
// ValidatorID contains the identity of the validator.
type ValidatorID struct {
Address cmn.HexBytes `json:"address"`
PubKey crypto.PubKey `json:"pub_key"`
}
// PrivValidator defines the functionality of a local Tendermint validator
// that signs votes, proposals, and heartbeats, and never double signs.
type PrivValidator2 interface {
Address() (Address, error) // redundant since .PubKey().Address()
PubKey() (crypto.PubKey, error)
SignVote(chainID string, vote *Vote) error
SignProposal(chainID string, proposal *Proposal) error
SignHeartbeat(chainID string, heartbeat *Heartbeat) error
}
type TestSigner interface {
Address() cmn.HexBytes
PubKey() crypto.PubKey
Sign([]byte) (crypto.Signature, error)
}
func GenSigner() TestSigner {
return &DefaultTestSigner{
crypto.GenPrivKeyEd25519().Wrap(),
}
}
type DefaultTestSigner struct {
crypto.PrivKey
}
func (ds *DefaultTestSigner) Address() cmn.HexBytes {
return ds.PubKey().Address()
}
func (ds *DefaultTestSigner) PubKey() crypto.PubKey {
return ds.PrivKey.PubKey()
}
func (ds *DefaultTestSigner) Sign(msg []byte) (crypto.Signature, error) {
return ds.PrivKey.Sign(msg), nil
}
//--------------------------------------------------------------
// TODO: Deprecate!
// PrivValidator defines the functionality of a local Tendermint validator // PrivValidator defines the functionality of a local Tendermint validator
// that signs votes, proposals, and heartbeats, and never double signs. // that signs votes, proposals, and heartbeats, and never double signs.
type PrivValidator interface { type PrivValidator interface {
@ -95,313 +18,10 @@ type PrivValidator interface {
SignHeartbeat(chainID string, heartbeat *Heartbeat) error SignHeartbeat(chainID string, heartbeat *Heartbeat) error
} }
// PrivValidatorFS implements PrivValidator using data persisted to disk //----------------------------------------
// to prevent double signing. The Signer itself can be mutated to use // Misc.
// something besides the default, for instance a hardware signer.
// NOTE: the directory containing the privVal.filePath must already exist.
type PrivValidatorFS struct {
Address Address `json:"address"`
PubKey crypto.PubKey `json:"pub_key"`
LastHeight int64 `json:"last_height"`
LastRound int `json:"last_round"`
LastStep int8 `json:"last_step"`
LastSignature crypto.Signature `json:"last_signature,omitempty"` // so we dont lose signatures
LastSignBytes cmn.HexBytes `json:"last_signbytes,omitempty"` // so we dont lose signatures
// PrivKey should be empty if a Signer other than the default is being used. type PrivValidatorsByAddress []PrivValidator
PrivKey crypto.PrivKey `json:"priv_key"`
Signer `json:"-"`
// For persistence.
// Overloaded for testing.
filePath string
mtx sync.Mutex
}
// Signer is an interface that defines how to sign messages.
// It is the caller's duty to verify the msg before calling Sign,
// eg. to avoid double signing.
// Currently, the only callers are SignVote, SignProposal, and SignHeartbeat.
type Signer interface {
Sign(msg []byte) (crypto.Signature, error)
}
// DefaultSigner implements Signer.
// It uses a standard, unencrypted crypto.PrivKey.
type DefaultSigner struct {
PrivKey crypto.PrivKey `json:"priv_key"`
}
// NewDefaultSigner returns an instance of DefaultSigner.
func NewDefaultSigner(priv crypto.PrivKey) *DefaultSigner {
return &DefaultSigner{
PrivKey: priv,
}
}
// Sign implements Signer. It signs the byte slice with a private key.
func (ds *DefaultSigner) Sign(msg []byte) (crypto.Signature, error) {
return ds.PrivKey.Sign(msg), nil
}
// GetAddress returns the address of the validator.
// Implements PrivValidator.
func (pv *PrivValidatorFS) GetAddress() Address {
return pv.Address
}
// GetPubKey returns the public key of the validator.
// Implements PrivValidator.
func (pv *PrivValidatorFS) GetPubKey() crypto.PubKey {
return pv.PubKey
}
// GenPrivValidatorFS generates a new validator with randomly generated private key
// and sets the filePath, but does not call Save().
func GenPrivValidatorFS(filePath string) *PrivValidatorFS {
privKey := crypto.GenPrivKeyEd25519().Wrap()
return &PrivValidatorFS{
Address: privKey.PubKey().Address(),
PubKey: privKey.PubKey(),
PrivKey: privKey,
LastStep: stepNone,
Signer: NewDefaultSigner(privKey),
filePath: filePath,
}
}
// LoadPrivValidatorFS loads a PrivValidatorFS from the filePath.
func LoadPrivValidatorFS(filePath string) *PrivValidatorFS {
return LoadPrivValidatorFSWithSigner(filePath, func(privVal PrivValidator) Signer {
return NewDefaultSigner(privVal.(*PrivValidatorFS).PrivKey)
})
}
// LoadOrGenPrivValidatorFS loads a PrivValidatorFS from the given filePath
// or else generates a new one and saves it to the filePath.
func LoadOrGenPrivValidatorFS(filePath string) *PrivValidatorFS {
var privVal *PrivValidatorFS
if cmn.FileExists(filePath) {
privVal = LoadPrivValidatorFS(filePath)
} else {
privVal = GenPrivValidatorFS(filePath)
privVal.Save()
}
return privVal
}
// LoadPrivValidatorWithSigner loads a PrivValidatorFS with a custom
// signer object. The PrivValidatorFS handles double signing prevention by persisting
// data to the filePath, while the Signer handles the signing.
// If the filePath does not exist, the PrivValidatorFS must be created manually and saved.
func LoadPrivValidatorFSWithSigner(filePath string, signerFunc func(PrivValidator) Signer) *PrivValidatorFS {
privValJSONBytes, err := ioutil.ReadFile(filePath)
if err != nil {
cmn.Exit(err.Error())
}
privVal := &PrivValidatorFS{}
err = json.Unmarshal(privValJSONBytes, &privVal)
if err != nil {
cmn.Exit(cmn.Fmt("Error reading PrivValidator from %v: %v\n", filePath, err))
}
privVal.filePath = filePath
privVal.Signer = signerFunc(privVal)
return privVal
}
// Save persists the PrivValidatorFS to disk.
func (privVal *PrivValidatorFS) Save() {
privVal.mtx.Lock()
defer privVal.mtx.Unlock()
privVal.save()
}
func (privVal *PrivValidatorFS) save() {
outFile := privVal.filePath
if outFile == "" {
panic("Cannot save PrivValidator: filePath not set")
}
jsonBytes, err := json.Marshal(privVal)
if err != nil {
panic(err)
}
err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600)
if err != nil {
panic(err)
}
}
// Reset resets all fields in the PrivValidatorFS.
// NOTE: Unsafe!
func (privVal *PrivValidatorFS) Reset() {
var sig crypto.Signature
privVal.LastHeight = 0
privVal.LastRound = 0
privVal.LastStep = 0
privVal.LastSignature = sig
privVal.LastSignBytes = nil
privVal.Save()
}
// SignVote signs a canonical representation of the vote, along with the
// chainID. Implements PrivValidator.
func (privVal *PrivValidatorFS) SignVote(chainID string, vote *Vote) error {
privVal.mtx.Lock()
defer privVal.mtx.Unlock()
if err := privVal.signVote(chainID, vote); err != nil {
return errors.New(cmn.Fmt("Error signing vote: %v", err))
}
return nil
}
// SignProposal signs a canonical representation of the proposal, along with
// the chainID. Implements PrivValidator.
func (privVal *PrivValidatorFS) SignProposal(chainID string, proposal *Proposal) error {
privVal.mtx.Lock()
defer privVal.mtx.Unlock()
if err := privVal.signProposal(chainID, proposal); err != nil {
return fmt.Errorf("Error signing proposal: %v", err)
}
return nil
}
// returns error if HRS regression or no LastSignBytes. returns true if HRS is unchanged
func (privVal *PrivValidatorFS) checkHRS(height int64, round int, step int8) (bool, error) {
if privVal.LastHeight > height {
return false, errors.New("Height regression")
}
if privVal.LastHeight == height {
if privVal.LastRound > round {
return false, errors.New("Round regression")
}
if privVal.LastRound == round {
if privVal.LastStep > step {
return false, errors.New("Step regression")
} else if privVal.LastStep == step {
if privVal.LastSignBytes != nil {
if privVal.LastSignature.Empty() {
panic("privVal: LastSignature is nil but LastSignBytes is not!")
}
return true, nil
}
return false, errors.New("No LastSignature found")
}
}
}
return false, nil
}
// signVote checks if the vote is good to sign and sets the vote signature.
// It may need to set the timestamp as well if the vote is otherwise the same as
// a previously signed vote (ie. we crashed after signing but before the vote hit the WAL).
func (privVal *PrivValidatorFS) signVote(chainID string, vote *Vote) error {
height, round, step := vote.Height, vote.Round, voteToStep(vote)
signBytes := vote.SignBytes(chainID)
sameHRS, err := privVal.checkHRS(height, round, step)
if err != nil {
return err
}
// We might crash before writing to the wal,
// causing us to try to re-sign for the same HRS.
// If signbytes are the same, use the last signature.
// If they only differ by timestamp, use last timestamp and signature
// Otherwise, return error
if sameHRS {
if bytes.Equal(signBytes, privVal.LastSignBytes) {
vote.Signature = privVal.LastSignature
} else if timestamp, ok := checkVotesOnlyDifferByTimestamp(privVal.LastSignBytes, signBytes); ok {
vote.Timestamp = timestamp
vote.Signature = privVal.LastSignature
} else {
err = fmt.Errorf("Conflicting data")
}
return err
}
// It passed the checks. Sign the vote
sig, err := privVal.Sign(signBytes)
if err != nil {
return err
}
privVal.saveSigned(height, round, step, signBytes, sig)
vote.Signature = sig
return nil
}
// signProposal checks if the proposal is good to sign and sets the proposal signature.
// It may need to set the timestamp as well if the proposal is otherwise the same as
// a previously signed proposal ie. we crashed after signing but before the proposal hit the WAL).
func (privVal *PrivValidatorFS) signProposal(chainID string, proposal *Proposal) error {
height, round, step := proposal.Height, proposal.Round, stepPropose
signBytes := proposal.SignBytes(chainID)
sameHRS, err := privVal.checkHRS(height, round, step)
if err != nil {
return err
}
// We might crash before writing to the wal,
// causing us to try to re-sign for the same HRS.
// If signbytes are the same, use the last signature.
// If they only differ by timestamp, use last timestamp and signature
// Otherwise, return error
if sameHRS {
if bytes.Equal(signBytes, privVal.LastSignBytes) {
proposal.Signature = privVal.LastSignature
} else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(privVal.LastSignBytes, signBytes); ok {
proposal.Timestamp = timestamp
proposal.Signature = privVal.LastSignature
} else {
err = fmt.Errorf("Conflicting data")
}
return err
}
// It passed the checks. Sign the proposal
sig, err := privVal.Sign(signBytes)
if err != nil {
return err
}
privVal.saveSigned(height, round, step, signBytes, sig)
proposal.Signature = sig
return nil
}
// Persist height/round/step and signature
func (privVal *PrivValidatorFS) saveSigned(height int64, round int, step int8,
signBytes []byte, sig crypto.Signature) {
privVal.LastHeight = height
privVal.LastRound = round
privVal.LastStep = step
privVal.LastSignature = sig
privVal.LastSignBytes = signBytes
privVal.save()
}
// SignHeartbeat signs a canonical representation of the heartbeat, along with the chainID.
// Implements PrivValidator.
func (privVal *PrivValidatorFS) SignHeartbeat(chainID string, heartbeat *Heartbeat) error {
privVal.mtx.Lock()
defer privVal.mtx.Unlock()
var err error
heartbeat.Signature, err = privVal.Sign(heartbeat.SignBytes(chainID))
return err
}
// String returns a string representation of the PrivValidatorFS.
func (privVal *PrivValidatorFS) String() string {
return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", privVal.GetAddress(), privVal.LastHeight, privVal.LastRound, privVal.LastStep)
}
//-------------------------------------
type PrivValidatorsByAddress []*PrivValidatorFS
func (pvs PrivValidatorsByAddress) Len() int { func (pvs PrivValidatorsByAddress) Len() int {
return len(pvs) return len(pvs)
@ -417,56 +37,53 @@ func (pvs PrivValidatorsByAddress) Swap(i, j int) {
pvs[j] = it pvs[j] = it
} }
//------------------------------------- //----------------------------------------
// MockPV
// returns the timestamp from the lastSignBytes. // MockPV implements PrivValidator without any safety or persistence.
// returns true if the only difference in the votes is their timestamp. // Only use it for testing.
func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) { type MockPV struct {
var lastVote, newVote CanonicalJSONOnceVote privKey crypto.PrivKey
if err := json.Unmarshal(lastSignBytes, &lastVote); err != nil {
panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into vote: %v", err))
}
if err := json.Unmarshal(newSignBytes, &newVote); err != nil {
panic(fmt.Sprintf("signBytes cannot be unmarshalled into vote: %v", err))
}
lastTime, err := time.Parse(TimeFormat, lastVote.Vote.Timestamp)
if err != nil {
panic(err)
}
// set the times to the same value and check equality
now := CanonicalTime(time.Now())
lastVote.Vote.Timestamp = now
newVote.Vote.Timestamp = now
lastVoteBytes, _ := json.Marshal(lastVote)
newVoteBytes, _ := json.Marshal(newVote)
return lastTime, bytes.Equal(newVoteBytes, lastVoteBytes)
} }
// returns the timestamp from the lastSignBytes. func NewMockPV() *MockPV {
// returns true if the only difference in the proposals is their timestamp return &MockPV{crypto.GenPrivKeyEd25519()}
func checkProposalsOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) { }
var lastProposal, newProposal CanonicalJSONOnceProposal
if err := json.Unmarshal(lastSignBytes, &lastProposal); err != nil { // Implements PrivValidator.
panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into proposal: %v", err)) func (pv *MockPV) GetAddress() Address {
} return pv.privKey.PubKey().Address()
if err := json.Unmarshal(newSignBytes, &newProposal); err != nil { }
panic(fmt.Sprintf("signBytes cannot be unmarshalled into proposal: %v", err))
} // Implements PrivValidator.
func (pv *MockPV) GetPubKey() crypto.PubKey {
lastTime, err := time.Parse(TimeFormat, lastProposal.Proposal.Timestamp) return pv.privKey.PubKey()
if err != nil { }
panic(err)
} // Implements PrivValidator.
func (pv *MockPV) SignVote(chainID string, vote *Vote) error {
// set the times to the same value and check equality signBytes := vote.SignBytes(chainID)
now := CanonicalTime(time.Now()) sig := pv.privKey.Sign(signBytes)
lastProposal.Proposal.Timestamp = now vote.Signature = sig
newProposal.Proposal.Timestamp = now return nil
lastProposalBytes, _ := json.Marshal(lastProposal) }
newProposalBytes, _ := json.Marshal(newProposal)
// Implements PrivValidator.
return lastTime, bytes.Equal(newProposalBytes, lastProposalBytes) func (pv *MockPV) SignProposal(chainID string, proposal *Proposal) error {
signBytes := proposal.SignBytes(chainID)
sig := pv.privKey.Sign(signBytes)
proposal.Signature = sig
return nil
}
// signHeartbeat signs the heartbeat without any checking.
func (pv *MockPV) SignHeartbeat(chainID string, heartbeat *Heartbeat) error {
sig := pv.privKey.Sign(heartbeat.SignBytes(chainID))
heartbeat.Signature = sig
return nil
}
// String returns a string representation of the MockPV.
func (pv *MockPV) String() string {
return fmt.Sprintf("MockPV{%v}", pv.GetAddress())
} }

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
}

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

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
}

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
}

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

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

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

View File

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

View File

@ -5,8 +5,6 @@ import (
"time" "time"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
wire "github.com/tendermint/tendermint/wire"
) )
var testProposal *Proposal var testProposal *Proposal
@ -29,7 +27,7 @@ func TestProposalSignable(t *testing.T) {
signBytes := testProposal.SignBytes("test_chain_id") signBytes := testProposal.SignBytes("test_chain_id")
signStr := string(signBytes) signStr := string(signBytes)
expected := `{"chain_id":"test_chain_id","proposal":{"block_parts_header":{"hash":"626C6F636B7061727473","total":111},"height":12345,"pol_block_id":{},"pol_round":-1,"round":23456,"timestamp":"2018-02-11T07:09:22.765Z"}}` expected := `{"@chain_id":"test_chain_id","@type":"proposal","block_parts_header":{"hash":"626C6F636B7061727473","total":111},"height":12345,"pol_block_id":{},"pol_round":-1,"round":23456,"timestamp":"2018-02-11T07:09:22.765Z"}`
if signStr != expected { if signStr != expected {
t.Errorf("Got unexpected sign string for Proposal. Expected:\n%v\nGot:\n%v", expected, signStr) t.Errorf("Got unexpected sign string for Proposal. Expected:\n%v\nGot:\n%v", expected, signStr)
} }
@ -37,38 +35,38 @@ func TestProposalSignable(t *testing.T) {
func TestProposalString(t *testing.T) { func TestProposalString(t *testing.T) {
str := testProposal.String() str := testProposal.String()
expected := `Proposal{12345/23456 111:626C6F636B70 (-1,:0:000000000000) {<nil>} @ 2018-02-11T07:09:22.765Z}` expected := `Proposal{12345/23456 111:626C6F636B70 (-1,:0:000000000000) <nil> @ 2018-02-11T07:09:22.765Z}`
if str != expected { if str != expected {
t.Errorf("Got unexpected string for Proposal. Expected:\n%v\nGot:\n%v", expected, str) t.Errorf("Got unexpected string for Proposal. Expected:\n%v\nGot:\n%v", expected, str)
} }
} }
func TestProposalVerifySignature(t *testing.T) { func TestProposalVerifySignature(t *testing.T) {
privVal := GenPrivValidatorFS("") privVal := NewMockPV()
pubKey := privVal.GetPubKey() pubKey := privVal.GetPubKey()
prop := NewProposal(4, 2, PartSetHeader{777, []byte("proper")}, 2, BlockID{}) prop := NewProposal(4, 2, PartSetHeader{777, []byte("proper")}, 2, BlockID{})
signBytes := prop.SignBytes("test_chain_id") signBytes := prop.SignBytes("test_chain_id")
// sign it // sign it
signature, err := privVal.Signer.Sign(signBytes) err := privVal.SignProposal("test_chain_id", prop)
require.NoError(t, err) require.NoError(t, err)
// verify the same proposal // verify the same proposal
valid := pubKey.VerifyBytes(prop.SignBytes("test_chain_id"), signature) valid := pubKey.VerifyBytes(signBytes, prop.Signature)
require.True(t, valid) require.True(t, valid)
// serialize, deserialize and verify again.... // serialize, deserialize and verify again....
newProp := new(Proposal) newProp := new(Proposal)
bs, err := wire.MarshalBinary(prop) bs, err := cdc.MarshalBinary(prop)
require.NoError(t, err) require.NoError(t, err)
err = wire.UnmarshalBinary(bs, &newProp) err = cdc.UnmarshalBinary(bs, &newProp)
require.NoError(t, err) require.NoError(t, err)
// verify the transmitted proposal // verify the transmitted proposal
newSignBytes := newProp.SignBytes("test_chain_id") newSignBytes := newProp.SignBytes("test_chain_id")
require.Equal(t, string(signBytes), string(newSignBytes)) require.Equal(t, string(signBytes), string(newSignBytes))
valid = pubKey.VerifyBytes(newSignBytes, signature) valid = pubKey.VerifyBytes(newSignBytes, newProp.Signature)
require.True(t, valid) require.True(t, valid)
} }
@ -79,9 +77,9 @@ func BenchmarkProposalWriteSignBytes(b *testing.B) {
} }
func BenchmarkProposalSign(b *testing.B) { func BenchmarkProposalSign(b *testing.B) {
privVal := GenPrivValidatorFS("") privVal := NewMockPV()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, err := privVal.Signer.Sign(testProposal.SignBytes("test_chain_id")) err := privVal.SignProposal("test_chain_id", testProposal)
if err != nil { if err != nil {
b.Error(err) b.Error(err)
} }
@ -89,12 +87,12 @@ func BenchmarkProposalSign(b *testing.B) {
} }
func BenchmarkProposalVerifySignature(b *testing.B) { func BenchmarkProposalVerifySignature(b *testing.B) {
signBytes := testProposal.SignBytes("test_chain_id") privVal := NewMockPV()
privVal := GenPrivValidatorFS("") err := privVal.SignProposal("test_chain_id", testProposal)
signature, _ := privVal.Signer.Sign(signBytes) require.Nil(b, err)
pubKey := privVal.GetPubKey() pubKey := privVal.GetPubKey()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
pubKey.VerifyBytes(testProposal.SignBytes("test_chain_id"), signature) pubKey.VerifyBytes(testProposal.SignBytes("test_chain_id"), testProposal.Signature)
} }
} }

View File

@ -2,7 +2,6 @@ package types
import ( import (
abci "github.com/tendermint/abci/types" abci "github.com/tendermint/abci/types"
wire "github.com/tendermint/tendermint/wire"
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/merkle" "github.com/tendermint/tmlibs/merkle"
) )
@ -18,7 +17,8 @@ type ABCIResult struct {
// Hash returns the canonical hash of the ABCIResult // Hash returns the canonical hash of the ABCIResult
func (a ABCIResult) Hash() []byte { func (a ABCIResult) Hash() []byte {
return tmHash(a) bz := aminoHash(a)
return bz
} }
// ABCIResults wraps the deliver tx results to return a proof // ABCIResults wraps the deliver tx results to return a proof
@ -42,7 +42,7 @@ func NewResultFromResponse(response *abci.ResponseDeliverTx) ABCIResult {
// Bytes serializes the ABCIResponse using wire // Bytes serializes the ABCIResponse using wire
func (a ABCIResults) Bytes() []byte { func (a ABCIResults) Bytes() []byte {
bz, err := wire.MarshalBinary(a) bz, err := cdc.MarshalBinary(a)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

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

View File

@ -9,8 +9,3 @@ package types
type Signable interface { type Signable interface {
SignBytes(chainID string) []byte SignBytes(chainID string) []byte
} }
// HashSignBytes is a convenience method for getting the hash of the bytes of a signable
func HashSignBytes(chainID string, o Signable) []byte {
return tmHash(o.SignBytes(chainID))
}

View File

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

View File

@ -18,7 +18,7 @@ type Tx []byte
// Hash computes the RIPEMD160 hash of the wire encoded transaction. // Hash computes the RIPEMD160 hash of the wire encoded transaction.
func (tx Tx) Hash() []byte { func (tx Tx) Hash() []byte {
return wireHasher(tx).Hash() return aminoHasher(tx).Hash()
} }
// String returns the hex-encoded transaction as a string. // String returns the hex-encoded transaction as a string.
@ -66,9 +66,7 @@ func (txs Txs) IndexByHash(hash []byte) int {
} }
// Proof returns a simple merkle proof for this node. // Proof returns a simple merkle proof for this node.
//
// Panics if i < 0 or i >= len(txs) // Panics if i < 0 or i >= len(txs)
//
// TODO: optimize this! // TODO: optimize this!
func (txs Txs) Proof(i int) TxProof { func (txs Txs) Proof(i int) TxProof {
l := len(txs) l := len(txs)
@ -95,7 +93,7 @@ type TxProof struct {
Proof merkle.SimpleProof Proof merkle.SimpleProof
} }
// LeadHash returns the hash of the transaction this proof refers to. // LeadHash returns the hash of the this proof refers to.
func (tp TxProof) LeafHash() []byte { func (tp TxProof) LeafHash() []byte {
return tp.Data.Hash() return tp.Data.Hash()
} }
@ -106,7 +104,12 @@ func (tp TxProof) Validate(dataHash []byte) error {
if !bytes.Equal(dataHash, tp.RootHash) { if !bytes.Equal(dataHash, tp.RootHash) {
return errors.New("Proof matches different data hash") return errors.New("Proof matches different data hash")
} }
if tp.Index < 0 {
return errors.New("Proof index cannot be negative")
}
if tp.Total <= 0 {
return errors.New("Proof total must be positive")
}
valid := tp.Proof.Verify(tp.Index, tp.Total, tp.LeafHash(), tp.RootHash) valid := tp.Proof.Verify(tp.Index, tp.Total, tp.LeafHash(), tp.RootHash)
if !valid { if !valid {
return errors.New("Proof is not internally consistent") return errors.New("Proof is not internally consistent")

View File

@ -6,7 +6,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
wire "github.com/tendermint/tendermint/wire"
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
ctest "github.com/tendermint/tmlibs/test" ctest "github.com/tendermint/tmlibs/test"
) )
@ -69,9 +68,9 @@ func TestValidTxProof(t *testing.T) {
// read-write must also work // read-write must also work
var p2 TxProof var p2 TxProof
bin, err := wire.MarshalBinary(proof) bin, err := cdc.MarshalBinary(proof)
assert.Nil(err) assert.Nil(err)
err = wire.UnmarshalBinary(bin, &p2) err = cdc.UnmarshalBinary(bin, &p2)
if assert.Nil(err, "%d: %d: %+v", h, i, err) { if assert.Nil(err, "%d: %d: %+v", h, i, err) {
assert.Nil(p2.Validate(root), "%d: %d", h, i) assert.Nil(p2.Validate(root), "%d: %d", h, i)
} }
@ -97,7 +96,7 @@ func testTxProofUnchangable(t *testing.T) {
// make sure it is valid to start with // make sure it is valid to start with
assert.Nil(proof.Validate(root)) assert.Nil(proof.Validate(root))
bin, err := wire.MarshalBinary(proof) bin, err := cdc.MarshalBinary(proof)
assert.Nil(err) assert.Nil(err)
// try mutating the data and make sure nothing breaks // try mutating the data and make sure nothing breaks
@ -109,16 +108,17 @@ func testTxProofUnchangable(t *testing.T) {
} }
} }
// this make sure the proof doesn't deserialize into something valid // This makes sure that the proof doesn't deserialize into something valid.
func assertBadProof(t *testing.T, root []byte, bad []byte, good TxProof) { func assertBadProof(t *testing.T, root []byte, bad []byte, good TxProof) {
var proof TxProof var proof TxProof
err := wire.UnmarshalBinary(bad, &proof) err := cdc.UnmarshalBinary(bad, &proof)
if err == nil { if err == nil {
err = proof.Validate(root) err = proof.Validate(root)
if err == nil { if err == nil {
// okay, this can happen if we have a slightly different total // XXX Fix simple merkle proofs so the following is *not* OK.
// (where the path ends up the same), if it is something else, we have // This can happen if we have a slightly different total (where the
// a real problem // path ends up the same). If it is something else, we have a real
// problem.
assert.NotEqual(t, proof.Total, good.Total, "bad: %#v\ngood: %#v", proof, good) assert.NotEqual(t, proof.Total, good.Total, "bad: %#v\ngood: %#v", proof, good)
} }
} }

View File

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

View File

@ -7,7 +7,6 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/pkg/errors"
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/merkle" "github.com/tendermint/tmlibs/merkle"
) )
@ -289,10 +288,10 @@ func (valSet *ValidatorSet) VerifyCommitAny(newSet *ValidatorSet, chainID string
blockID BlockID, height int64, commit *Commit) error { blockID BlockID, height int64, commit *Commit) error {
if newSet.Size() != len(commit.Precommits) { if newSet.Size() != len(commit.Precommits) {
return errors.Errorf("Invalid commit -- wrong set size: %v vs %v", newSet.Size(), len(commit.Precommits)) return cmn.NewError("Invalid commit -- wrong set size: %v vs %v", newSet.Size(), len(commit.Precommits))
} }
if height != commit.Height() { if height != commit.Height() {
return errors.Errorf("Invalid commit -- wrong height: %v vs %v", height, commit.Height()) return cmn.NewError("Invalid commit -- wrong height: %v vs %v", height, commit.Height())
} }
oldVotingPower := int64(0) oldVotingPower := int64(0)
@ -307,13 +306,13 @@ func (valSet *ValidatorSet) VerifyCommitAny(newSet *ValidatorSet, chainID string
} }
if precommit.Height != height { if precommit.Height != height {
// return certerr.ErrHeightMismatch(height, precommit.Height) // return certerr.ErrHeightMismatch(height, precommit.Height)
return errors.Errorf("Blocks don't match - %d vs %d", round, precommit.Round) return cmn.NewError("Blocks don't match - %d vs %d", round, precommit.Round)
} }
if precommit.Round != round { if precommit.Round != round {
return errors.Errorf("Invalid commit -- wrong round: %v vs %v", round, precommit.Round) return cmn.NewError("Invalid commit -- wrong round: %v vs %v", round, precommit.Round)
} }
if precommit.Type != VoteTypePrecommit { if precommit.Type != VoteTypePrecommit {
return errors.Errorf("Invalid commit -- not precommit @ index %v", idx) return cmn.NewError("Invalid commit -- not precommit @ index %v", idx)
} }
if !blockID.Equals(precommit.BlockID) { if !blockID.Equals(precommit.BlockID) {
continue // Not an error, but doesn't count continue // Not an error, but doesn't count
@ -329,7 +328,7 @@ func (valSet *ValidatorSet) VerifyCommitAny(newSet *ValidatorSet, chainID string
// Validate signature old school // Validate signature old school
precommitSignBytes := precommit.SignBytes(chainID) precommitSignBytes := precommit.SignBytes(chainID)
if !ov.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) { if !ov.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) {
return errors.Errorf("Invalid commit -- invalid signature: %v", precommit) return cmn.NewError("Invalid commit -- invalid signature: %v", precommit)
} }
// Good precommit! // Good precommit!
oldVotingPower += ov.VotingPower oldVotingPower += ov.VotingPower
@ -343,10 +342,10 @@ func (valSet *ValidatorSet) VerifyCommitAny(newSet *ValidatorSet, chainID string
} }
if oldVotingPower <= valSet.TotalVotingPower()*2/3 { if oldVotingPower <= valSet.TotalVotingPower()*2/3 {
return errors.Errorf("Invalid commit -- insufficient old voting power: got %v, needed %v", return cmn.NewError("Invalid commit -- insufficient old voting power: got %v, needed %v",
oldVotingPower, (valSet.TotalVotingPower()*2/3 + 1)) oldVotingPower, (valSet.TotalVotingPower()*2/3 + 1))
} else if newVotingPower <= newSet.TotalVotingPower()*2/3 { } else if newVotingPower <= newSet.TotalVotingPower()*2/3 {
return errors.Errorf("Invalid commit -- insufficient cur voting power: got %v, needed %v", return cmn.NewError("Invalid commit -- insufficient cur voting power: got %v, needed %v",
newVotingPower, (newSet.TotalVotingPower()*2/3 + 1)) newVotingPower, (newSet.TotalVotingPower()*2/3 + 1))
} }
return nil return nil
@ -416,9 +415,9 @@ func (ac accumComparable) Less(o interface{}) bool {
// RandValidatorSet returns a randomized validator set, useful for testing. // RandValidatorSet returns a randomized validator set, useful for testing.
// NOTE: PrivValidator are in order. // NOTE: PrivValidator are in order.
// UNSTABLE // UNSTABLE
func RandValidatorSet(numValidators int, votingPower int64) (*ValidatorSet, []*PrivValidatorFS) { func RandValidatorSet(numValidators int, votingPower int64) (*ValidatorSet, []PrivValidator) {
vals := make([]*Validator, numValidators) vals := make([]*Validator, numValidators)
privValidators := make([]*PrivValidatorFS, numValidators) privValidators := make([]PrivValidator, numValidators)
for i := 0; i < numValidators; i++ { for i := 0; i < numValidators; i++ {
val, privValidator := RandValidator(false, votingPower) val, privValidator := RandValidator(false, votingPower)
vals[i] = val vals[i] = val

View File

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

View File

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

View File

@ -11,7 +11,7 @@ import (
) )
// NOTE: privValidators are in order // NOTE: privValidators are in order
func randVoteSet(height int64, round int, type_ byte, numValidators int, votingPower int64) (*VoteSet, *ValidatorSet, []*PrivValidatorFS) { func randVoteSet(height int64, round int, type_ byte, numValidators int, votingPower int64) (*VoteSet, *ValidatorSet, []PrivValidator) {
valSet, privValidators := RandValidatorSet(numValidators, votingPower) valSet, privValidators := RandValidatorSet(numValidators, votingPower)
return NewVoteSet("test_chain_id", height, round, type_, valSet), valSet, privValidators return NewVoteSet("test_chain_id", height, round, type_, valSet), valSet, privValidators
} }

View File

@ -5,8 +5,6 @@ import (
"time" "time"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
wire "github.com/tendermint/tendermint/wire"
) )
func examplePrevote() *Vote { func examplePrevote() *Vote {
@ -45,7 +43,7 @@ func TestVoteSignable(t *testing.T) {
signBytes := vote.SignBytes("test_chain_id") signBytes := vote.SignBytes("test_chain_id")
signStr := string(signBytes) signStr := string(signBytes)
expected := `{"chain_id":"test_chain_id","vote":{"block_id":{"hash":"68617368","parts":{"hash":"70617274735F68617368","total":1000000}},"height":12345,"round":2,"timestamp":"2017-12-25T03:00:01.234Z","type":2}}` expected := `{"@chain_id":"test_chain_id","@type":"vote","block_id":{"hash":"68617368","parts":{"hash":"70617274735F68617368","total":1000000}},"height":12345,"round":2,"timestamp":"2017-12-25T03:00:01.234Z","type":2}`
if signStr != expected { if signStr != expected {
// NOTE: when this fails, you probably want to fix up consensus/replay_test too // NOTE: when this fails, you probably want to fix up consensus/replay_test too
t.Errorf("Got unexpected sign string for Vote. Expected:\n%v\nGot:\n%v", expected, signStr) t.Errorf("Got unexpected sign string for Vote. Expected:\n%v\nGot:\n%v", expected, signStr)
@ -58,8 +56,8 @@ func TestVoteString(t *testing.T) {
in string in string
out string out string
}{ }{
{"Precommit", examplePrecommit().String(), `Vote{56789:616464720000 12345/02/2(Precommit) 686173680000 {<nil>} @ 2017-12-25T03:00:01.234Z}`}, {"Precommit", examplePrecommit().String(), `Vote{56789:616464720000 12345/02/2(Precommit) 686173680000 <nil> @ 2017-12-25T03:00:01.234Z}`},
{"Prevote", examplePrevote().String(), `Vote{56789:616464720000 12345/02/1(Prevote) 686173680000 {<nil>} @ 2017-12-25T03:00:01.234Z}`}, {"Prevote", examplePrevote().String(), `Vote{56789:616464720000 12345/02/1(Prevote) 686173680000 <nil> @ 2017-12-25T03:00:01.234Z}`},
} }
for _, tt := range tc { for _, tt := range tc {
@ -73,31 +71,31 @@ func TestVoteString(t *testing.T) {
} }
func TestVoteVerifySignature(t *testing.T) { func TestVoteVerifySignature(t *testing.T) {
privVal := GenPrivValidatorFS("") privVal := NewMockPV()
pubKey := privVal.GetPubKey() pubKey := privVal.GetPubKey()
vote := examplePrecommit() vote := examplePrecommit()
signBytes := vote.SignBytes("test_chain_id") signBytes := vote.SignBytes("test_chain_id")
// sign it // sign it
signature, err := privVal.Signer.Sign(signBytes) err := privVal.SignVote("test_chain_id", vote)
require.NoError(t, err) require.NoError(t, err)
// verify the same vote // verify the same vote
valid := pubKey.VerifyBytes(vote.SignBytes("test_chain_id"), signature) valid := pubKey.VerifyBytes(vote.SignBytes("test_chain_id"), vote.Signature)
require.True(t, valid) require.True(t, valid)
// serialize, deserialize and verify again.... // serialize, deserialize and verify again....
precommit := new(Vote) precommit := new(Vote)
bs, err := wire.MarshalBinary(vote) bs, err := cdc.MarshalBinary(vote)
require.NoError(t, err) require.NoError(t, err)
err = wire.UnmarshalBinary(bs, &precommit) err = cdc.UnmarshalBinary(bs, &precommit)
require.NoError(t, err) require.NoError(t, err)
// verify the transmitted vote // verify the transmitted vote
newSignBytes := precommit.SignBytes("test_chain_id") newSignBytes := precommit.SignBytes("test_chain_id")
require.Equal(t, string(signBytes), string(newSignBytes)) require.Equal(t, string(signBytes), string(newSignBytes))
valid = pubKey.VerifyBytes(newSignBytes, signature) valid = pubKey.VerifyBytes(newSignBytes, precommit.Signature)
require.True(t, valid) require.True(t, valid)
} }

12
types/wire.go Normal file
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)
}