diff --git a/docs/architecture/adr-008-priv-validator.md b/docs/architecture/adr-008-priv-validator.md index 0861e9415..4c1d87bed 100644 --- a/docs/architecture/adr-008-priv-validator.md +++ b/docs/architecture/adr-008-priv-validator.md @@ -1,128 +1,29 @@ -# ADR 008: PrivValidator - -## Context - -The current PrivValidator is monolithic and isn't easily reuseable by alternative signers. - -For instance, see https://github.com/tendermint/tendermint/issues/673 - -The goal is to have a clean PrivValidator interface like: - -``` -type PrivValidator interface { - Address() data.Bytes - PubKey() crypto.PubKey - - SignVote(chainID string, vote *types.Vote) error - SignProposal(chainID string, proposal *types.Proposal) error - SignHeartbeat(chainID string, heartbeat *types.Heartbeat) error -} -``` - -It should also be easy to re-use the LastSignedInfo logic to avoid double signing. - -## Decision - -Tendermint node's should support only two in-process PrivValidator implementations: - -- PrivValidatorUnencrypted uses an unencrypted private key in a "priv_validator.json" file - no configuration required (just `tendermint init`). -- PrivValidatorSocket uses a socket to send signing requests to another process - user is responsible for starting that process themselves. - -The PrivValidatorSocket address can be provided via flags at the command line - -doing so will cause Tendermint to ignore any "priv_validator.json" file and to listen -on the given address for incoming connections from an external priv_validator process. -It will halt any operation until at least one external process succesfully -connected. - -The external priv_validator process will dial the address to connect to Tendermint, -and then Tendermint will send requests on the ensuing connection to sign votes and proposals. -Thus the external process initiates the connection, but the Tendermint process makes all requests. -In a later stage we're going to support multiple validators for fault -tolerance. To prevent double signing they need to be synced, which is deferred -to an external solution (see #1185). - -In addition, Tendermint will provide implementations that can be run in that external process. -These include: - -- PrivValidatorEncrypted uses an encrypted private key persisted to disk - user must enter password to decrypt key when process is started. -- PrivValidatorLedger uses a Ledger Nano S to handle all signing. - -What follows are descriptions of useful types - -### Signer - -``` -type Signer interface { - Sign(msg []byte) (crypto.Signature, error) -} -``` - -Signer signs a message. It can also return an error. - -### ValidatorID - - -ValidatorID is just the Address and PubKey - -``` -type ValidatorID struct { - Address data.Bytes `json:"address"` - PubKey crypto.PubKey `json:"pub_key"` -} -``` - -### LastSignedInfo - -LastSignedInfo tracks the last thing we signed: - -``` -type LastSignedInfo struct { - Height int64 `json:"height"` - Round int `json:"round"` - Step int8 `json:"step"` - Signature crypto.Signature `json:"signature,omitempty"` // so we dont lose signatures - SignBytes data.Bytes `json:"signbytes,omitempty"` // so we dont lose signatures -} -``` - -It exposes methods for signing votes and proposals using a `Signer`. - -This allows it to easily be reused by developers implemented their own PrivValidator. - -### PrivValidatorUnencrypted - -``` -type PrivValidatorUnencrypted struct { - ID types.ValidatorID `json:"id"` - PrivKey PrivKey `json:"priv_key"` - LastSignedInfo *LastSignedInfo `json:"last_signed_info"` -} -``` - -Has the same structure as currently, but broken up into sub structs. - -Note the LastSignedInfo is mutated in place every time we sign. - -### PrivValidatorJSON - -The "priv_validator.json" file supports only the PrivValidatorUnencrypted type. - -It unmarshals into PrivValidatorJSON, which is used as the default PrivValidator type. -It wraps the PrivValidatorUnencrypted and persists it to disk after every signature. - -## Status - -Accepted. - -## Consequences - -### Positive - -- Cleaner separation of components enabling re-use. - -### Negative - -- More files - led to creation of new directory. - -### Neutral - +# ADR 008: SocketPV + +Tendermint node's should support only two in-process PrivValidator +implementations: + +- FilePV uses an unencrypted private key in a "priv_validator.json" file - no + configuration required (just `tendermint init`). +- SocketPV uses a socket to send signing requests to another process - user is + responsible for starting that process themselves. + +The SocketPV address can be provided via flags at the command line - doing so +will cause Tendermint to ignore any "priv_validator.json" file and to listen on +the given address for incoming connections from an external priv_validator +process. It will halt any operation until at least one external process +succesfully connected. + +The external priv_validator process will dial the address to connect to +Tendermint, and then Tendermint will send requests on the ensuing connection to +sign votes and proposals. Thus the external process initiates the connection, +but the Tendermint process makes all requests. In a later stage we're going to +support multiple validators for fault tolerance. To prevent double signing they +need to be synced, which is deferred to an external solution (see #1185). + +In addition, Tendermint will provide implementations that can be run in that +external process. These include: + +- FilePV will encrypt the private key, and the user must enter password to + decrypt key when process is started. +- LedgerPV uses a Ledger Nano S to handle all signing. diff --git a/types/block.go b/types/block.go index 53fc6a811..9d8bb42ad 100644 --- a/types/block.go +++ b/types/block.go @@ -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} } diff --git a/types/block_test.go b/types/block_test.go index 1fcfa469b..d50980b3d 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -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) diff --git a/types/canonical_json.go b/types/canonical_json.go index 4eeeb2064..95ade9c67 100644 --- a/types/canonical_json.go +++ b/types/canonical_json.go @@ -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) diff --git a/types/evidence.go b/types/evidence.go index 9e1f6af0e..658c40227 100644 --- a/types/evidence.go +++ b/types/evidence.go @@ -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 +} diff --git a/types/evidence_test.go b/types/evidence_test.go index 84811514a..f2b1f91bb 100644 --- a/types/evidence_test.go +++ b/types/evidence_test.go @@ -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, } diff --git a/types/genesis.go b/types/genesis.go index ef2d16791..36d2a46f1 100644 --- a/types/genesis.go +++ b/types/genesis.go @@ -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 } diff --git a/types/genesis_test.go b/types/genesis_test.go index aa713289f..17ebf1cfc 100644 --- a/types/genesis_test.go +++ b/types/genesis_test.go @@ -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") diff --git a/types/heartbeat.go b/types/heartbeat.go index fc5f8ad7f..097dd22db 100644 --- a/types/heartbeat.go +++ b/types/heartbeat.go @@ -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) } diff --git a/types/heartbeat_test.go b/types/heartbeat_test.go index 206636166..3c4536028 100644 --- a/types/heartbeat_test.go +++ b/types/heartbeat_test.go @@ -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) {}}") + require.Equal(t, hb.String(), "Heartbeat{1:000000000000 11/02 (0) }") 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 diff --git a/types/params.go b/types/params.go index 0e8ac577f..0626663e5 100644 --- a/types/params.go +++ b/types/params.go @@ -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), }) } diff --git a/types/priv_validator.go b/types/priv_validator.go index daa456bc0..06a024215 100644 --- a/types/priv_validator.go +++ b/types/priv_validator.go @@ -2,88 +2,11 @@ package types import ( "bytes" - "encoding/json" - "errors" "fmt" - "io/ioutil" - "sync" - "time" - crypto "github.com/tendermint/go-crypto" - cmn "github.com/tendermint/tmlibs/common" + "github.com/tendermint/go-crypto" ) -// TODO: type ? -const ( - stepNone int8 = 0 // Used to distinguish the initial state - stepPropose int8 = 1 - stepPrevote int8 = 2 - stepPrecommit int8 = 3 -) - -func voteToStep(vote *Vote) int8 { - switch vote.Type { - case VoteTypePrevote: - return stepPrevote - case VoteTypePrecommit: - return stepPrecommit - default: - cmn.PanicSanity("Unknown vote type") - return 0 - } -} - -//-------------------------------------------------------------- -// PrivValidator is being upgraded! See types/priv_validator/ - -// ValidatorID contains the identity of the validator. -type ValidatorID struct { - Address cmn.HexBytes `json:"address"` - PubKey crypto.PubKey `json:"pub_key"` -} - -// PrivValidator defines the functionality of a local Tendermint validator -// that signs votes, proposals, and heartbeats, and never double signs. -type PrivValidator2 interface { - Address() (Address, error) // redundant since .PubKey().Address() - PubKey() (crypto.PubKey, error) - - SignVote(chainID string, vote *Vote) error - SignProposal(chainID string, proposal *Proposal) error - SignHeartbeat(chainID string, heartbeat *Heartbeat) error -} - -type TestSigner interface { - Address() cmn.HexBytes - PubKey() crypto.PubKey - Sign([]byte) (crypto.Signature, error) -} - -func GenSigner() TestSigner { - return &DefaultTestSigner{ - crypto.GenPrivKeyEd25519().Wrap(), - } -} - -type DefaultTestSigner struct { - crypto.PrivKey -} - -func (ds *DefaultTestSigner) Address() cmn.HexBytes { - return ds.PubKey().Address() -} - -func (ds *DefaultTestSigner) PubKey() crypto.PubKey { - return ds.PrivKey.PubKey() -} - -func (ds *DefaultTestSigner) Sign(msg []byte) (crypto.Signature, error) { - return ds.PrivKey.Sign(msg), nil -} - -//-------------------------------------------------------------- -// TODO: Deprecate! - // PrivValidator defines the functionality of a local Tendermint validator // that signs votes, proposals, and heartbeats, and never double signs. type PrivValidator interface { @@ -95,378 +18,72 @@ type PrivValidator interface { SignHeartbeat(chainID string, heartbeat *Heartbeat) error } -// PrivValidatorFS implements PrivValidator using data persisted to disk -// to prevent double signing. The Signer itself can be mutated to use -// something besides the default, for instance a hardware signer. -// NOTE: the directory containing the privVal.filePath must already exist. -type PrivValidatorFS struct { - Address Address `json:"address"` - PubKey crypto.PubKey `json:"pub_key"` - LastHeight int64 `json:"last_height"` - LastRound int `json:"last_round"` - LastStep int8 `json:"last_step"` - LastSignature crypto.Signature `json:"last_signature,omitempty"` // so we dont lose signatures - LastSignBytes cmn.HexBytes `json:"last_signbytes,omitempty"` // so we dont lose signatures +//---------------------------------------- +// Misc. - // PrivKey should be empty if a Signer other than the default is being used. - PrivKey crypto.PrivKey `json:"priv_key"` - Signer `json:"-"` +type PrivValidatorsByAddress []PrivValidator - // For persistence. - // Overloaded for testing. - filePath string - mtx sync.Mutex +func (pvs PrivValidatorsByAddress) Len() int { + return len(pvs) } -// Signer is an interface that defines how to sign messages. -// It is the caller's duty to verify the msg before calling Sign, -// eg. to avoid double signing. -// Currently, the only callers are SignVote, SignProposal, and SignHeartbeat. -type Signer interface { - Sign(msg []byte) (crypto.Signature, error) +func (pvs PrivValidatorsByAddress) Less(i, j int) bool { + return bytes.Compare(pvs[i].GetAddress(), pvs[j].GetAddress()) == -1 } -// DefaultSigner implements Signer. -// It uses a standard, unencrypted crypto.PrivKey. -type DefaultSigner struct { - PrivKey crypto.PrivKey `json:"priv_key"` +func (pvs PrivValidatorsByAddress) Swap(i, j int) { + it := pvs[i] + pvs[i] = pvs[j] + pvs[j] = it } -// NewDefaultSigner returns an instance of DefaultSigner. -func NewDefaultSigner(priv crypto.PrivKey) *DefaultSigner { - return &DefaultSigner{ - PrivKey: priv, - } -} +//---------------------------------------- +// MockPV -// Sign implements Signer. It signs the byte slice with a private key. -func (ds *DefaultSigner) Sign(msg []byte) (crypto.Signature, error) { - return ds.PrivKey.Sign(msg), nil +// MockPV implements PrivValidator without any safety or persistence. +// Only use it for testing. +type MockPV struct { + privKey crypto.PrivKey } -// GetAddress returns the address of the validator. -// Implements PrivValidator. -func (pv *PrivValidatorFS) GetAddress() Address { - return pv.Address +func NewMockPV() *MockPV { + return &MockPV{crypto.GenPrivKeyEd25519()} } -// GetPubKey returns the public key of the validator. // Implements PrivValidator. -func (pv *PrivValidatorFS) GetPubKey() crypto.PubKey { - return pv.PubKey -} - -// GenPrivValidatorFS generates a new validator with randomly generated private key -// and sets the filePath, but does not call Save(). -func GenPrivValidatorFS(filePath string) *PrivValidatorFS { - privKey := crypto.GenPrivKeyEd25519().Wrap() - return &PrivValidatorFS{ - Address: privKey.PubKey().Address(), - PubKey: privKey.PubKey(), - PrivKey: privKey, - LastStep: stepNone, - Signer: NewDefaultSigner(privKey), - filePath: filePath, - } -} - -// LoadPrivValidatorFS loads a PrivValidatorFS from the filePath. -func LoadPrivValidatorFS(filePath string) *PrivValidatorFS { - return LoadPrivValidatorFSWithSigner(filePath, func(privVal PrivValidator) Signer { - return NewDefaultSigner(privVal.(*PrivValidatorFS).PrivKey) - }) -} - -// LoadOrGenPrivValidatorFS loads a PrivValidatorFS from the given filePath -// or else generates a new one and saves it to the filePath. -func LoadOrGenPrivValidatorFS(filePath string) *PrivValidatorFS { - var privVal *PrivValidatorFS - if cmn.FileExists(filePath) { - privVal = LoadPrivValidatorFS(filePath) - } else { - privVal = GenPrivValidatorFS(filePath) - privVal.Save() - } - return privVal -} - -// LoadPrivValidatorWithSigner loads a PrivValidatorFS with a custom -// signer object. The PrivValidatorFS handles double signing prevention by persisting -// data to the filePath, while the Signer handles the signing. -// If the filePath does not exist, the PrivValidatorFS must be created manually and saved. -func LoadPrivValidatorFSWithSigner(filePath string, signerFunc func(PrivValidator) Signer) *PrivValidatorFS { - privValJSONBytes, err := ioutil.ReadFile(filePath) - if err != nil { - cmn.Exit(err.Error()) - } - privVal := &PrivValidatorFS{} - err = json.Unmarshal(privValJSONBytes, &privVal) - if err != nil { - cmn.Exit(cmn.Fmt("Error reading PrivValidator from %v: %v\n", filePath, err)) - } - - privVal.filePath = filePath - privVal.Signer = signerFunc(privVal) - return privVal -} - -// Save persists the PrivValidatorFS to disk. -func (privVal *PrivValidatorFS) Save() { - privVal.mtx.Lock() - defer privVal.mtx.Unlock() - privVal.save() -} - -func (privVal *PrivValidatorFS) save() { - outFile := privVal.filePath - if outFile == "" { - panic("Cannot save PrivValidator: filePath not set") - } - jsonBytes, err := json.Marshal(privVal) - if err != nil { - panic(err) - } - err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600) - if err != nil { - panic(err) - } +func (pv *MockPV) GetAddress() Address { + return pv.privKey.PubKey().Address() } -// Reset resets all fields in the PrivValidatorFS. -// NOTE: Unsafe! -func (privVal *PrivValidatorFS) Reset() { - var sig crypto.Signature - privVal.LastHeight = 0 - privVal.LastRound = 0 - privVal.LastStep = 0 - privVal.LastSignature = sig - privVal.LastSignBytes = nil - privVal.Save() -} - -// SignVote signs a canonical representation of the vote, along with the -// chainID. Implements PrivValidator. -func (privVal *PrivValidatorFS) SignVote(chainID string, vote *Vote) error { - privVal.mtx.Lock() - defer privVal.mtx.Unlock() - if err := privVal.signVote(chainID, vote); err != nil { - return errors.New(cmn.Fmt("Error signing vote: %v", err)) - } - return nil -} - -// SignProposal signs a canonical representation of the proposal, along with -// the chainID. Implements PrivValidator. -func (privVal *PrivValidatorFS) SignProposal(chainID string, proposal *Proposal) error { - privVal.mtx.Lock() - defer privVal.mtx.Unlock() - if err := privVal.signProposal(chainID, proposal); err != nil { - return fmt.Errorf("Error signing proposal: %v", err) - } - return nil -} - -// returns error if HRS regression or no LastSignBytes. returns true if HRS is unchanged -func (privVal *PrivValidatorFS) checkHRS(height int64, round int, step int8) (bool, error) { - if privVal.LastHeight > height { - return false, errors.New("Height regression") - } - - if privVal.LastHeight == height { - if privVal.LastRound > round { - return false, errors.New("Round regression") - } - - if privVal.LastRound == round { - if privVal.LastStep > step { - return false, errors.New("Step regression") - } else if privVal.LastStep == step { - if privVal.LastSignBytes != nil { - if privVal.LastSignature.Empty() { - panic("privVal: LastSignature is nil but LastSignBytes is not!") - } - return true, nil - } - return false, errors.New("No LastSignature found") - } - } - } - return false, nil +// Implements PrivValidator. +func (pv *MockPV) GetPubKey() crypto.PubKey { + return pv.privKey.PubKey() } -// signVote checks if the vote is good to sign and sets the vote signature. -// It may need to set the timestamp as well if the vote is otherwise the same as -// a previously signed vote (ie. we crashed after signing but before the vote hit the WAL). -func (privVal *PrivValidatorFS) signVote(chainID string, vote *Vote) error { - height, round, step := vote.Height, vote.Round, voteToStep(vote) +// Implements PrivValidator. +func (pv *MockPV) SignVote(chainID string, vote *Vote) error { signBytes := vote.SignBytes(chainID) - - sameHRS, err := privVal.checkHRS(height, round, step) - if err != nil { - return err - } - - // We might crash before writing to the wal, - // causing us to try to re-sign for the same HRS. - // If signbytes are the same, use the last signature. - // If they only differ by timestamp, use last timestamp and signature - // Otherwise, return error - if sameHRS { - if bytes.Equal(signBytes, privVal.LastSignBytes) { - vote.Signature = privVal.LastSignature - } else if timestamp, ok := checkVotesOnlyDifferByTimestamp(privVal.LastSignBytes, signBytes); ok { - vote.Timestamp = timestamp - vote.Signature = privVal.LastSignature - } else { - err = fmt.Errorf("Conflicting data") - } - return err - } - - // It passed the checks. Sign the vote - sig, err := privVal.Sign(signBytes) - if err != nil { - return err - } - privVal.saveSigned(height, round, step, signBytes, sig) + sig := pv.privKey.Sign(signBytes) vote.Signature = sig return nil } -// signProposal checks if the proposal is good to sign and sets the proposal signature. -// It may need to set the timestamp as well if the proposal is otherwise the same as -// a previously signed proposal ie. we crashed after signing but before the proposal hit the WAL). -func (privVal *PrivValidatorFS) signProposal(chainID string, proposal *Proposal) error { - height, round, step := proposal.Height, proposal.Round, stepPropose +// Implements PrivValidator. +func (pv *MockPV) SignProposal(chainID string, proposal *Proposal) error { signBytes := proposal.SignBytes(chainID) - - sameHRS, err := privVal.checkHRS(height, round, step) - if err != nil { - return err - } - - // We might crash before writing to the wal, - // causing us to try to re-sign for the same HRS. - // If signbytes are the same, use the last signature. - // If they only differ by timestamp, use last timestamp and signature - // Otherwise, return error - if sameHRS { - if bytes.Equal(signBytes, privVal.LastSignBytes) { - proposal.Signature = privVal.LastSignature - } else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(privVal.LastSignBytes, signBytes); ok { - proposal.Timestamp = timestamp - proposal.Signature = privVal.LastSignature - } else { - err = fmt.Errorf("Conflicting data") - } - return err - } - - // It passed the checks. Sign the proposal - sig, err := privVal.Sign(signBytes) - if err != nil { - return err - } - privVal.saveSigned(height, round, step, signBytes, sig) + sig := pv.privKey.Sign(signBytes) proposal.Signature = sig return nil } -// Persist height/round/step and signature -func (privVal *PrivValidatorFS) saveSigned(height int64, round int, step int8, - signBytes []byte, sig crypto.Signature) { - - privVal.LastHeight = height - privVal.LastRound = round - privVal.LastStep = step - privVal.LastSignature = sig - privVal.LastSignBytes = signBytes - privVal.save() -} - -// SignHeartbeat signs a canonical representation of the heartbeat, along with the chainID. -// Implements PrivValidator. -func (privVal *PrivValidatorFS) SignHeartbeat(chainID string, heartbeat *Heartbeat) error { - privVal.mtx.Lock() - defer privVal.mtx.Unlock() - var err error - heartbeat.Signature, err = privVal.Sign(heartbeat.SignBytes(chainID)) - return err -} - -// String returns a string representation of the PrivValidatorFS. -func (privVal *PrivValidatorFS) String() string { - return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", privVal.GetAddress(), privVal.LastHeight, privVal.LastRound, privVal.LastStep) -} - -//------------------------------------- - -type PrivValidatorsByAddress []*PrivValidatorFS - -func (pvs PrivValidatorsByAddress) Len() int { - return len(pvs) -} - -func (pvs PrivValidatorsByAddress) Less(i, j int) bool { - return bytes.Compare(pvs[i].GetAddress(), pvs[j].GetAddress()) == -1 -} - -func (pvs PrivValidatorsByAddress) Swap(i, j int) { - it := pvs[i] - pvs[i] = pvs[j] - pvs[j] = it -} - -//------------------------------------- - -// returns the timestamp from the lastSignBytes. -// returns true if the only difference in the votes is their timestamp. -func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) { - var lastVote, newVote CanonicalJSONOnceVote - if err := json.Unmarshal(lastSignBytes, &lastVote); err != nil { - panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into vote: %v", err)) - } - if err := json.Unmarshal(newSignBytes, &newVote); err != nil { - panic(fmt.Sprintf("signBytes cannot be unmarshalled into vote: %v", err)) - } - - lastTime, err := time.Parse(TimeFormat, lastVote.Vote.Timestamp) - if err != nil { - panic(err) - } - - // set the times to the same value and check equality - now := CanonicalTime(time.Now()) - lastVote.Vote.Timestamp = now - newVote.Vote.Timestamp = now - lastVoteBytes, _ := json.Marshal(lastVote) - newVoteBytes, _ := json.Marshal(newVote) - - return lastTime, bytes.Equal(newVoteBytes, lastVoteBytes) +// signHeartbeat signs the heartbeat without any checking. +func (pv *MockPV) SignHeartbeat(chainID string, heartbeat *Heartbeat) error { + sig := pv.privKey.Sign(heartbeat.SignBytes(chainID)) + heartbeat.Signature = sig + return nil } -// returns the timestamp from the lastSignBytes. -// returns true if the only difference in the proposals is their timestamp -func checkProposalsOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) { - var lastProposal, newProposal CanonicalJSONOnceProposal - if err := json.Unmarshal(lastSignBytes, &lastProposal); err != nil { - panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into proposal: %v", err)) - } - if err := json.Unmarshal(newSignBytes, &newProposal); err != nil { - panic(fmt.Sprintf("signBytes cannot be unmarshalled into proposal: %v", err)) - } - - lastTime, err := time.Parse(TimeFormat, lastProposal.Proposal.Timestamp) - if err != nil { - panic(err) - } - - // set the times to the same value and check equality - now := CanonicalTime(time.Now()) - lastProposal.Proposal.Timestamp = now - newProposal.Proposal.Timestamp = now - lastProposalBytes, _ := json.Marshal(lastProposal) - newProposalBytes, _ := json.Marshal(newProposal) - - return lastTime, bytes.Equal(newProposalBytes, lastProposalBytes) +// String returns a string representation of the MockPV. +func (pv *MockPV) String() string { + return fmt.Sprintf("MockPV{%v}", pv.GetAddress()) } diff --git a/types/priv_validator/json.go b/types/priv_validator/json.go deleted file mode 100644 index 5c0849ebd..000000000 --- a/types/priv_validator/json.go +++ /dev/null @@ -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 -} diff --git a/types/priv_validator/priv_validator.go b/types/priv_validator/priv_validator.go new file mode 100644 index 000000000..baff28f64 --- /dev/null +++ b/types/priv_validator/priv_validator.go @@ -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) +} diff --git a/types/priv_validator/priv_validator_test.go b/types/priv_validator/priv_validator_test.go index 120a0c86e..c7212d594 100644 --- a/types/priv_validator/priv_validator_test.go +++ b/types/priv_validator/priv_validator_test.go @@ -1,8 +1,7 @@ -package types +package privval import ( - "encoding/hex" - "encoding/json" + "encoding/base64" "fmt" "os" "testing" @@ -10,113 +9,89 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - crypto "github.com/tendermint/go-crypto" - cmn "github.com/tendermint/tmlibs/common" - + "github.com/tendermint/go-crypto" "github.com/tendermint/tendermint/types" + cmn "github.com/tendermint/tmlibs/common" ) func TestGenLoadValidator(t *testing.T) { - assert, require := assert.New(t), require.New(t) + assert := assert.New(t) _, tempFilePath := cmn.Tempfile("priv_validator_") - privVal := GenPrivValidatorJSON(tempFilePath) + privVal := GenFilePV(tempFilePath) height := int64(100) - privVal.LastSignedInfo.Height = height + privVal.LastHeight = height privVal.Save() - addr, err := privVal.Address() - require.Nil(err) - - privVal = LoadPrivValidatorJSON(tempFilePath) - pAddr, err := privVal.Address() - require.Nil(err) + addr := privVal.GetAddress() - assert.Equal(addr, pAddr, "expected privval addr to be the same") - assert.Equal(height, privVal.LastSignedInfo.Height, "expected privval.LastHeight to have been saved") + privVal = LoadFilePV(tempFilePath) + assert.Equal(addr, privVal.GetAddress(), "expected privval addr to be the same") + assert.Equal(height, privVal.LastHeight, "expected privval.LastHeight to have been saved") } func TestLoadOrGenValidator(t *testing.T) { - assert, require := assert.New(t), require.New(t) + assert := assert.New(t) _, tempFilePath := cmn.Tempfile("priv_validator_") if err := os.Remove(tempFilePath); err != nil { t.Error(err) } - privVal := LoadOrGenPrivValidatorJSON(tempFilePath) - addr, err := privVal.Address() - require.Nil(err) - - privVal = LoadOrGenPrivValidatorJSON(tempFilePath) - pAddr, err := privVal.Address() - require.Nil(err) - - assert.Equal(addr, pAddr, "expected privval addr to be the same") + privVal := LoadOrGenFilePV(tempFilePath) + addr := privVal.GetAddress() + privVal = LoadOrGenFilePV(tempFilePath) + assert.Equal(addr, privVal.GetAddress(), "expected privval addr to be the same") } func TestUnmarshalValidator(t *testing.T) { assert, require := assert.New(t), require.New(t) // create some fixed values - addrStr := "D028C9981F7A87F3093672BF0D5B0E2A1B3ED456" - pubStr := "3B3069C422E19688B45CBFAE7BB009FC0FA1B1EA86593519318B7214853803C8" - privStr := "27F82582AEFAE7AB151CFB01C48BB6C1A0DA78F9BDDA979A9F70A84D074EB07D3B3069C422E19688B45CBFAE7BB009FC0FA1B1EA86593519318B7214853803C8" - addrBytes, _ := hex.DecodeString(addrStr) - pubBytes, _ := hex.DecodeString(pubStr) - privBytes, _ := hex.DecodeString(privStr) - - // prepend type byte - pubKey, err := crypto.PubKeyFromBytes(append([]byte{1}, pubBytes...)) - require.Nil(err, "%+v", err) - privKey, err := crypto.PrivKeyFromBytes(append([]byte{1}, privBytes...)) - require.Nil(err, "%+v", err) + privKey := crypto.GenPrivKeyEd25519() + pubKey := privKey.PubKey() + addr := pubKey.Address() + pubArray := [32]byte(pubKey.(crypto.PubKeyEd25519)) + pubBytes := pubArray[:] + privArray := [64]byte(privKey) + privBytes := privArray[:] + pubB64 := base64.StdEncoding.EncodeToString(pubBytes) + privB64 := base64.StdEncoding.EncodeToString(privBytes) serialized := fmt.Sprintf(`{ - "id": { - "address": "%s", - "pub_key": { - "type": "ed25519", - "data": "%s" - } - }, - "priv_key": { - "type": "ed25519", - "data": "%s" - }, - "last_signed_info": { - "height": 0, - "round": 0, - "step": 0, - "signature": null - } -}`, addrStr, pubStr, privStr) - - val := PrivValidatorJSON{} - err = json.Unmarshal([]byte(serialized), &val) + "address": "%s", + "pub_key": { + "type": "AC26791624DE60", + "value": "%s" + }, + "last_height": 0, + "last_round": 0, + "last_step": 0, + "priv_key": { + "type": "954568A3288910", + "value": "%s" + } +}`, addr, pubB64, privB64) + + val := FilePV{} + err := cdc.UnmarshalJSON([]byte(serialized), &val) require.Nil(err, "%+v", err) // make sure the values match - vAddr, err := val.Address() - require.Nil(err) - - pKey, err := val.PubKey() - require.Nil(err) - - assert.EqualValues(addrBytes, vAddr) - assert.EqualValues(pubKey, pKey) + assert.EqualValues(addr, val.GetAddress()) + assert.EqualValues(pubKey, val.GetPubKey()) assert.EqualValues(privKey, val.PrivKey) // export it and make sure it is the same - out, err := json.Marshal(val) + out, err := cdc.MarshalJSON(val) require.Nil(err, "%+v", err) assert.JSONEq(serialized, string(out)) } func TestSignVote(t *testing.T) { - assert, require := assert.New(t), require.New(t) + assert := assert.New(t) _, tempFilePath := cmn.Tempfile("priv_validator_") - privVal := GenPrivValidatorJSON(tempFilePath) + privVal := GenFilePV(tempFilePath) block1 := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}} block2 := types.BlockID{[]byte{3, 2, 1}, types.PartSetHeader{}} @@ -124,11 +99,8 @@ func TestSignVote(t *testing.T) { voteType := types.VoteTypePrevote // sign a vote for first time - addr, err := privVal.Address() - require.Nil(err) - - vote := newVote(addr, 0, height, round, voteType, block1) - err = privVal.SignVote("mychainid", vote) + vote := newVote(privVal.Address, 0, height, round, voteType, block1) + err := privVal.SignVote("mychainid", vote) assert.NoError(err, "expected no error signing vote") // try to sign the same vote again; should be fine @@ -137,10 +109,10 @@ func TestSignVote(t *testing.T) { // now try some bad votes cases := []*types.Vote{ - newVote(addr, 0, height, round-1, voteType, block1), // round regression - newVote(addr, 0, height-1, round, voteType, block1), // height regression - newVote(addr, 0, height-2, round+4, voteType, block1), // height regression and different round - newVote(addr, 0, height, round, voteType, block2), // different block + newVote(privVal.Address, 0, height, round-1, voteType, block1), // round regression + newVote(privVal.Address, 0, height-1, round, voteType, block1), // height regression + newVote(privVal.Address, 0, height-2, round+4, voteType, block1), // height regression and different round + newVote(privVal.Address, 0, height, round, voteType, block2), // different block } for _, c := range cases { @@ -160,7 +132,7 @@ func TestSignProposal(t *testing.T) { assert := assert.New(t) _, tempFilePath := cmn.Tempfile("priv_validator_") - privVal := GenPrivValidatorJSON(tempFilePath) + privVal := GenFilePV(tempFilePath) block1 := types.PartSetHeader{5, []byte{1, 2, 3}} block2 := types.PartSetHeader{10, []byte{3, 2, 1}} @@ -197,10 +169,8 @@ func TestSignProposal(t *testing.T) { } func TestDifferByTimestamp(t *testing.T) { - require := require.New(t) - _, tempFilePath := cmn.Tempfile("priv_validator_") - privVal := GenPrivValidatorJSON(tempFilePath) + privVal := GenFilePV(tempFilePath) block1 := types.PartSetHeader{5, []byte{1, 2, 3}} height, round := int64(10), 1 @@ -217,7 +187,8 @@ func TestDifferByTimestamp(t *testing.T) { // manipulate the timestamp. should get changed back proposal.Timestamp = proposal.Timestamp.Add(time.Millisecond) - proposal.Signature = crypto.Signature{} + var emptySig crypto.Signature + proposal.Signature = emptySig err = privVal.SignProposal("mychainid", proposal) assert.NoError(t, err, "expected no error on signing same proposal") @@ -228,13 +199,10 @@ func TestDifferByTimestamp(t *testing.T) { // test vote { - addr, err := privVal.Address() - require.Nil(err) - voteType := types.VoteTypePrevote blockID := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}} - vote := newVote(addr, 0, height, round, voteType, blockID) - err = privVal.SignVote("mychainid", vote) + vote := newVote(privVal.Address, 0, height, round, voteType, blockID) + err := privVal.SignVote("mychainid", vote) assert.NoError(t, err, "expected no error signing vote") signBytes := vote.SignBytes(chainID) @@ -243,7 +211,8 @@ func TestDifferByTimestamp(t *testing.T) { // manipulate the timestamp. should get changed back vote.Timestamp = vote.Timestamp.Add(time.Millisecond) - vote.Signature = crypto.Signature{} + var emptySig crypto.Signature + vote.Signature = emptySig err = privVal.SignVote("mychainid", vote) assert.NoError(t, err, "expected no error on signing same vote") @@ -253,7 +222,7 @@ func TestDifferByTimestamp(t *testing.T) { } } -func newVote(addr cmn.HexBytes, idx int, height int64, round int, typ byte, blockID types.BlockID) *types.Vote { +func newVote(addr types.Address, idx int, height int64, round int, typ byte, blockID types.BlockID) *types.Vote { return &types.Vote{ ValidatorAddress: addr, ValidatorIndex: idx, diff --git a/types/priv_validator/sign_info.go b/types/priv_validator/sign_info.go deleted file mode 100644 index 746131a96..000000000 --- a/types/priv_validator/sign_info.go +++ /dev/null @@ -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) -} diff --git a/types/priv_validator/socket.go b/types/priv_validator/socket.go index 26cab72b9..9f59a8152 100644 --- a/types/priv_validator/socket.go +++ b/types/priv_validator/socket.go @@ -1,14 +1,14 @@ -package types +package privval import ( + "errors" "fmt" "io" "net" "time" - "github.com/pkg/errors" - crypto "github.com/tendermint/go-crypto" - wire "github.com/tendermint/go-wire" + "github.com/tendermint/go-amino" + "github.com/tendermint/go-crypto" cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" @@ -37,36 +37,36 @@ var ( connHeartbeat = time.Second * defaultConnHeartBeatSeconds ) -// SocketClientOption sets an optional parameter on the SocketClient. -type SocketClientOption func(*SocketClient) +// SocketPVOption sets an optional parameter on the SocketPV. +type SocketPVOption func(*SocketPV) -// SocketClientAcceptDeadline sets the deadline for the SocketClient listener. +// SocketPVAcceptDeadline sets the deadline for the SocketPV listener. // A zero time value disables the deadline. -func SocketClientAcceptDeadline(deadline time.Duration) SocketClientOption { - return func(sc *SocketClient) { sc.acceptDeadline = deadline } +func SocketPVAcceptDeadline(deadline time.Duration) SocketPVOption { + return func(sc *SocketPV) { sc.acceptDeadline = deadline } } -// SocketClientConnDeadline sets the read and write deadline for connections +// SocketPVConnDeadline sets the read and write deadline for connections // from external signing processes. -func SocketClientConnDeadline(deadline time.Duration) SocketClientOption { - return func(sc *SocketClient) { sc.connDeadline = deadline } +func SocketPVConnDeadline(deadline time.Duration) SocketPVOption { + return func(sc *SocketPV) { sc.connDeadline = deadline } } -// SocketClientHeartbeat sets the period on which to check the liveness of the +// SocketPVHeartbeat sets the period on which to check the liveness of the // connected Signer connections. -func SocketClientHeartbeat(period time.Duration) SocketClientOption { - return func(sc *SocketClient) { sc.connHeartbeat = period } +func SocketPVHeartbeat(period time.Duration) SocketPVOption { + return func(sc *SocketPV) { sc.connHeartbeat = period } } -// SocketClientConnWait sets the timeout duration before connection of external +// SocketPVConnWait sets the timeout duration before connection of external // signing processes are considered to be unsuccessful. -func SocketClientConnWait(timeout time.Duration) SocketClientOption { - return func(sc *SocketClient) { sc.connWaitTimeout = timeout } +func SocketPVConnWait(timeout time.Duration) SocketPVOption { + return func(sc *SocketPV) { sc.connWaitTimeout = timeout } } -// SocketClient implements PrivValidator, it uses a socket to request signatures +// SocketPV implements PrivValidator, it uses a socket to request signatures // from an external process. -type SocketClient struct { +type SocketPV struct { cmn.BaseService addr string @@ -80,16 +80,16 @@ type SocketClient struct { listener net.Listener } -// Check that SocketClient implements PrivValidator2. -var _ types.PrivValidator2 = (*SocketClient)(nil) +// Check that SocketPV implements PrivValidator. +var _ types.PrivValidator = (*SocketPV)(nil) -// NewSocketClient returns an instance of SocketClient. -func NewSocketClient( +// NewSocketPV returns an instance of SocketPV. +func NewSocketPV( logger log.Logger, socketAddr string, privKey crypto.PrivKeyEd25519, -) *SocketClient { - sc := &SocketClient{ +) *SocketPV { + sc := &SocketPV{ addr: socketAddr, acceptDeadline: acceptDeadline, connDeadline: connDeadline, @@ -98,15 +98,14 @@ func NewSocketClient( privKey: privKey, } - sc.BaseService = *cmn.NewBaseService(logger, "SocketClient", sc) + sc.BaseService = *cmn.NewBaseService(logger, "SocketPV", sc) return sc } // GetAddress implements PrivValidator. -// TODO(xla): Remove when PrivValidator2 replaced PrivValidator. -func (sc *SocketClient) GetAddress() types.Address { - addr, err := sc.Address() +func (sc *SocketPV) GetAddress() types.Address { + addr, err := sc.getAddress() if err != nil { panic(err) } @@ -115,8 +114,8 @@ func (sc *SocketClient) GetAddress() types.Address { } // Address is an alias for PubKey().Address(). -func (sc *SocketClient) Address() (cmn.HexBytes, error) { - p, err := sc.PubKey() +func (sc *SocketPV) getAddress() (cmn.HexBytes, error) { + p, err := sc.getPubKey() if err != nil { return nil, err } @@ -125,9 +124,8 @@ func (sc *SocketClient) Address() (cmn.HexBytes, error) { } // GetPubKey implements PrivValidator. -// TODO(xla): Remove when PrivValidator2 replaced PrivValidator. -func (sc *SocketClient) GetPubKey() crypto.PubKey { - pubKey, err := sc.PubKey() +func (sc *SocketPV) GetPubKey() crypto.PubKey { + pubKey, err := sc.getPubKey() if err != nil { panic(err) } @@ -135,23 +133,22 @@ func (sc *SocketClient) GetPubKey() crypto.PubKey { return pubKey } -// PubKey implements PrivValidator2. -func (sc *SocketClient) PubKey() (crypto.PubKey, error) { +func (sc *SocketPV) getPubKey() (crypto.PubKey, error) { err := writeMsg(sc.conn, &PubKeyMsg{}) if err != nil { - return crypto.PubKey{}, err + return nil, err } res, err := readMsg(sc.conn) if err != nil { - return crypto.PubKey{}, err + return nil, err } return res.(*PubKeyMsg).PubKey, nil } -// SignVote implements PrivValidator2. -func (sc *SocketClient) SignVote(chainID string, vote *types.Vote) error { +// SignVote implements PrivValidator. +func (sc *SocketPV) SignVote(chainID string, vote *types.Vote) error { err := writeMsg(sc.conn, &SignVoteMsg{Vote: vote}) if err != nil { return err @@ -167,8 +164,8 @@ func (sc *SocketClient) SignVote(chainID string, vote *types.Vote) error { return nil } -// SignProposal implements PrivValidator2. -func (sc *SocketClient) SignProposal( +// SignProposal implements PrivValidator. +func (sc *SocketPV) SignProposal( chainID string, proposal *types.Proposal, ) error { @@ -187,8 +184,8 @@ func (sc *SocketClient) SignProposal( return nil } -// SignHeartbeat implements PrivValidator2. -func (sc *SocketClient) SignHeartbeat( +// SignHeartbeat implements PrivValidator. +func (sc *SocketPV) SignHeartbeat( chainID string, heartbeat *types.Heartbeat, ) error { @@ -208,21 +205,22 @@ func (sc *SocketClient) SignHeartbeat( } // OnStart implements cmn.Service. -func (sc *SocketClient) OnStart() error { +func (sc *SocketPV) OnStart() error { if err := sc.listen(); err != nil { + err = cmn.ErrorWrap(err, "failed to listen") sc.Logger.Error( "OnStart", - "err", errors.Wrap(err, "failed to listen"), + "err", err, ) - return err } conn, err := sc.waitConnection() if err != nil { + err = cmn.ErrorWrap(err, "failed to accept connection") sc.Logger.Error( "OnStart", - "err", errors.Wrap(err, "failed to accept connection"), + "err", err, ) return err @@ -234,27 +232,29 @@ func (sc *SocketClient) OnStart() error { } // OnStop implements cmn.Service. -func (sc *SocketClient) OnStop() { +func (sc *SocketPV) OnStop() { if sc.conn != nil { if err := sc.conn.Close(); err != nil { + err = cmn.ErrorWrap(err, "failed to close connection") sc.Logger.Error( "OnStop", - "err", errors.Wrap(err, "failed to close connection"), + "err", err, ) } } if sc.listener != nil { if err := sc.listener.Close(); err != nil { + err = cmn.ErrorWrap(err, "failed to close listener") sc.Logger.Error( "OnStop", - "err", errors.Wrap(err, "failed to close listener"), + "err", err, ) } } } -func (sc *SocketClient) acceptConnection() (net.Conn, error) { +func (sc *SocketPV) acceptConnection() (net.Conn, error) { conn, err := sc.listener.Accept() if err != nil { if !sc.IsRunning() { @@ -264,7 +264,7 @@ func (sc *SocketClient) acceptConnection() (net.Conn, error) { } - conn, err = p2pconn.MakeSecretConnection(conn, sc.privKey.Wrap()) + conn, err = p2pconn.MakeSecretConnection(conn, sc.privKey) if err != nil { return nil, err } @@ -272,7 +272,7 @@ func (sc *SocketClient) acceptConnection() (net.Conn, error) { return conn, nil } -func (sc *SocketClient) listen() error { +func (sc *SocketPV) listen() error { ln, err := net.Listen(cmn.ProtocolAndAddress(sc.addr)) if err != nil { return err @@ -290,7 +290,7 @@ func (sc *SocketClient) listen() error { // waitConnection uses the configured wait timeout to error if no external // process connects in the time period. -func (sc *SocketClient) waitConnection() (net.Conn, error) { +func (sc *SocketPV) waitConnection() (net.Conn, error) { var ( connc = make(chan net.Conn, 1) errc = make(chan error, 1) @@ -311,7 +311,7 @@ func (sc *SocketClient) waitConnection() (net.Conn, error) { return conn, nil case err := <-errc: if _, ok := err.(timeoutError); ok { - return nil, errors.Wrap(ErrConnWaitTimeout, err.Error()) + return nil, cmn.ErrorWrap(ErrConnWaitTimeout, err.Error()) } return nil, err case <-time.After(sc.connWaitTimeout): @@ -344,7 +344,7 @@ type RemoteSigner struct { connDeadline time.Duration connRetries int privKey crypto.PrivKeyEd25519 - privVal PrivValidator + privVal types.PrivValidator conn net.Conn } @@ -353,7 +353,7 @@ type RemoteSigner struct { func NewRemoteSigner( logger log.Logger, chainID, socketAddr string, - privVal PrivValidator, + privVal types.PrivValidator, privKey crypto.PrivKeyEd25519, ) *RemoteSigner { rs := &RemoteSigner{ @@ -374,8 +374,8 @@ func NewRemoteSigner( func (rs *RemoteSigner) OnStart() error { conn, err := rs.connect() if err != nil { - rs.Logger.Error("OnStart", "err", errors.Wrap(err, "connect")) - + err = cmn.ErrorWrap(err, "connect") + rs.Logger.Error("OnStart", "err", err) return err } @@ -391,7 +391,7 @@ func (rs *RemoteSigner) OnStop() { } if err := rs.conn.Close(); err != nil { - rs.Logger.Error("OnStop", "err", errors.Wrap(err, "closing listener failed")) + rs.Logger.Error("OnStop", "err", cmn.ErrorWrap(err, "closing listener failed")) } } @@ -404,28 +404,31 @@ func (rs *RemoteSigner) connect() (net.Conn, error) { conn, err := cmn.Connect(rs.addr) if err != nil { + err = cmn.ErrorWrap(err, "connection failed") rs.Logger.Error( "connect", "addr", rs.addr, - "err", errors.Wrap(err, "connection failed"), + "err", err, ) continue } if err := conn.SetDeadline(time.Now().Add(connDeadline)); err != nil { + err = cmn.ErrorWrap(err, "setting connection timeout failed") rs.Logger.Error( "connect", - "err", errors.Wrap(err, "setting connection timeout failed"), + "err", err, ) continue } - conn, err = p2pconn.MakeSecretConnection(conn, rs.privKey.Wrap()) + conn, err = p2pconn.MakeSecretConnection(conn, rs.privKey) if err != nil { + err = cmn.ErrorWrap(err, "encrypting connection failed") rs.Logger.Error( "connect", - "err", errors.Wrap(err, "encrypting connection failed"), + "err", err, ) continue @@ -451,13 +454,12 @@ func (rs *RemoteSigner) handleConnection(conn net.Conn) { return } - var res PrivValMsg + var res SocketPVMsg switch r := req.(type) { case *PubKeyMsg: var p crypto.PubKey - - p, err = rs.privVal.PubKey() + p = rs.privVal.GetPubKey() res = &PubKeyMsg{p} case *SignVoteMsg: err = rs.privVal.SignVote(rs.chainID, r.Vote) @@ -487,23 +489,16 @@ func (rs *RemoteSigner) handleConnection(conn net.Conn) { //--------------------------------------------------------- -const ( - msgTypePubKey = byte(0x01) - msgTypeSignVote = byte(0x10) - msgTypeSignProposal = byte(0x11) - msgTypeSignHeartbeat = byte(0x12) -) +// SocketPVMsg is sent between RemoteSigner and SocketPV. +type SocketPVMsg interface{} -// PrivValMsg is sent between RemoteSigner and SocketClient. -type PrivValMsg interface{} - -var _ = wire.RegisterInterface( - struct{ PrivValMsg }{}, - wire.ConcreteType{&PubKeyMsg{}, msgTypePubKey}, - wire.ConcreteType{&SignVoteMsg{}, msgTypeSignVote}, - wire.ConcreteType{&SignProposalMsg{}, msgTypeSignProposal}, - wire.ConcreteType{&SignHeartbeatMsg{}, msgTypeSignHeartbeat}, -) +func RegisterSocketPVMsg(cdc *amino.Codec) { + cdc.RegisterInterface((*SocketPVMsg)(nil), nil) + cdc.RegisterConcrete(&PubKeyMsg{}, "tendermint/socketpv/PubKeyMsg", nil) + cdc.RegisterConcrete(&SignVoteMsg{}, "tendermint/socketpv/SignVoteMsg", nil) + cdc.RegisterConcrete(&SignProposalMsg{}, "tendermint/socketpv/SignProposalMsg", nil) + cdc.RegisterConcrete(&SignHeartbeatMsg{}, "tendermint/socketpv/SignHeartbeatMsg", nil) +} // PubKeyMsg is a PrivValidatorSocket message containing the public key. type PubKeyMsg struct { @@ -525,40 +520,19 @@ type SignHeartbeatMsg struct { Heartbeat *types.Heartbeat } -func readMsg(r io.Reader) (PrivValMsg, error) { - var ( - n int - err error - ) - - read := wire.ReadBinary(struct{ PrivValMsg }{}, r, 0, &n, &err) - if err != nil { - if _, ok := err.(timeoutError); ok { - return nil, errors.Wrap(ErrConnTimeout, err.Error()) - } - - return nil, err - } - - w, ok := read.(struct{ PrivValMsg }) - if !ok { - return nil, errors.New("unknown type") +func readMsg(r io.Reader) (msg SocketPVMsg, err error) { + const maxSocketPVMsgSize = 1024 * 10 + _, err = cdc.UnmarshalBinaryReader(r, &msg, maxSocketPVMsgSize) + if _, ok := err.(timeoutError); ok { + err = cmn.ErrorWrap(ErrConnTimeout, err.Error()) } - - return w.PrivValMsg, nil + return } -func writeMsg(w io.Writer, msg interface{}) error { - var ( - err error - n int - ) - - // TODO(xla): This extra wrap should be gone with the sdk-2 update. - wire.WriteBinary(struct{ PrivValMsg }{msg}, w, &n, &err) +func writeMsg(w io.Writer, msg interface{}) (err error) { + _, err = cdc.MarshalBinaryWriter(w, msg) if _, ok := err.(timeoutError); ok { - return errors.Wrap(ErrConnTimeout, err.Error()) + err = cmn.ErrorWrap(ErrConnTimeout, err.Error()) } - - return err + return } diff --git a/types/priv_validator/socket_tcp.go b/types/priv_validator/socket_tcp.go index 2421eb9f4..b26db00c2 100644 --- a/types/priv_validator/socket_tcp.go +++ b/types/priv_validator/socket_tcp.go @@ -1,4 +1,4 @@ -package types +package privval import ( "net" diff --git a/types/priv_validator/socket_tcp_test.go b/types/priv_validator/socket_tcp_test.go index cd95ab0b9..44a673c0c 100644 --- a/types/priv_validator/socket_tcp_test.go +++ b/types/priv_validator/socket_tcp_test.go @@ -1,4 +1,4 @@ -package types +package privval import ( "net" diff --git a/types/priv_validator/socket_test.go b/types/priv_validator/socket_test.go index 2859c9452..eb0f4c12d 100644 --- a/types/priv_validator/socket_test.go +++ b/types/priv_validator/socket_test.go @@ -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()) diff --git a/types/priv_validator/unencrypted.go b/types/priv_validator/unencrypted.go deleted file mode 100644 index 10a304d9e..000000000 --- a/types/priv_validator/unencrypted.go +++ /dev/null @@ -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 -} diff --git a/types/priv_validator/upgrade.go b/types/priv_validator/upgrade.go deleted file mode 100644 index 063655421..000000000 --- a/types/priv_validator/upgrade.go +++ /dev/null @@ -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 -} diff --git a/types/priv_validator/upgrade_pv/main.go b/types/priv_validator/upgrade_pv/main.go deleted file mode 100644 index 5a0d4f263..000000000 --- a/types/priv_validator/upgrade_pv/main.go +++ /dev/null @@ -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 ") - os.Exit(1) - } - file := os.Args[1] - _, err := priv_val.UpgradePrivValidator(file) - if err != nil { - fmt.Println(err) - os.Exit(1) - } -} diff --git a/types/priv_validator/wire.go b/types/priv_validator/wire.go new file mode 100644 index 000000000..688910834 --- /dev/null +++ b/types/priv_validator/wire.go @@ -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) +} diff --git a/types/priv_validator_test.go b/types/priv_validator_test.go deleted file mode 100644 index edfcdf58c..000000000 --- a/types/priv_validator_test.go +++ /dev/null @@ -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() -} diff --git a/types/proposal.go b/types/proposal.go index c240756bb..95008897b 100644 --- a/types/proposal.go +++ b/types/proposal.go @@ -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) } diff --git a/types/proposal_test.go b/types/proposal_test.go index 610f76855..43fb7c209 100644 --- a/types/proposal_test.go +++ b/types/proposal_test.go @@ -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) {} @ 2018-02-11T07:09:22.765Z}` + expected := `Proposal{12345/23456 111:626C6F636B70 (-1,:0:000000000000) @ 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) } } diff --git a/types/results.go b/types/results.go index 71834664d..326cee48d 100644 --- a/types/results.go +++ b/types/results.go @@ -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) } diff --git a/types/results_test.go b/types/results_test.go index 88624e8c8..8018b4e85 100644 --- a/types/results_test.go +++ b/types/results_test.go @@ -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) diff --git a/types/signable.go b/types/signable.go index 19829ede7..cc6498882 100644 --- a/types/signable.go +++ b/types/signable.go @@ -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)) -} diff --git a/types/test_util.go b/types/test_util.go index 73e53eb19..f21c2831f 100644 --- a/types/test_util.go +++ b/types/test_util.go @@ -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 } diff --git a/types/tx.go b/types/tx.go index fc1d27212..e7247693a 100644 --- a/types/tx.go +++ b/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") diff --git a/types/tx_test.go b/types/tx_test.go index 340bd5a6b..2a93ceb31 100644 --- a/types/tx_test.go +++ b/types/tx_test.go @@ -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) } } diff --git a/types/validator.go b/types/validator.go index 027f7dc3c..e3ccc551a 100644 --- a/types/validator.go +++ b/types/validator.go @@ -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()) diff --git a/types/validator_set.go b/types/validator_set.go index 83d066ec1..51bf71563 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -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 diff --git a/types/validator_set_test.go b/types/validator_set_test.go index b346be1be..edfe4d5ce 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -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) diff --git a/types/vote.go b/types/vote.go index ceb6e985e..e4ead612a 100644 --- a/types/vote.go +++ b/types/vote.go @@ -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) } diff --git a/types/vote_set_test.go b/types/vote_set_test.go index 4a09d04d3..d424667b1 100644 --- a/types/vote_set_test.go +++ b/types/vote_set_test.go @@ -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 } diff --git a/types/vote_test.go b/types/vote_test.go index a4a0f309f..263d2c351 100644 --- a/types/vote_test.go +++ b/types/vote_test.go @@ -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 {} @ 2017-12-25T03:00:01.234Z}`}, - {"Prevote", examplePrevote().String(), `Vote{56789:616464720000 12345/02/1(Prevote) 686173680000 {} @ 2017-12-25T03:00:01.234Z}`}, + {"Precommit", examplePrecommit().String(), `Vote{56789:616464720000 12345/02/2(Precommit) 686173680000 @ 2017-12-25T03:00:01.234Z}`}, + {"Prevote", examplePrevote().String(), `Vote{56789:616464720000 12345/02/1(Prevote) 686173680000 @ 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) } diff --git a/types/wire.go b/types/wire.go new file mode 100644 index 000000000..bd5c4497d --- /dev/null +++ b/types/wire.go @@ -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) +}