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