tendermint/types/priv_validator.go

473 lines
14 KiB
Go
Raw Normal View History

package types
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"sync"
"time"
2017-05-02 00:53:32 -07:00
crypto "github.com/tendermint/go-crypto"
2017-09-20 15:40:41 -07:00
cmn "github.com/tendermint/tmlibs/common"
)
2017-09-11 12:45:12 -07:00
// 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:
2017-09-20 15:40:41 -07:00
cmn.PanicSanity("Unknown vote type")
2015-07-19 16:42:52 -07:00
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.
2017-09-18 15:12:31 -07:00
type PrivValidator interface {
GetAddress() Address // redundant since .PubKey().Address()
GetPubKey() crypto.PubKey
2017-09-18 15:12:31 -07:00
SignVote(chainID string, vote *Vote) error
SignProposal(chainID string, proposal *Proposal) error
SignHeartbeat(chainID string, heartbeat *Heartbeat) error
}
2017-09-18 19:05:33 -07:00
// 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.
2017-09-18 19:05:33 -07:00
type PrivValidatorFS struct {
2018-02-03 00:42:59 -08:00
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
2018-02-03 00:42:59 -08:00
LastSignBytes cmn.HexBytes `json:"last_signbytes,omitempty"` // so we dont lose signatures
2017-09-18 19:05:33 -07:00
// PrivKey should be empty if a Signer other than the default is being used.
PrivKey crypto.PrivKey `json:"priv_key"`
Signer `json:"-"`
2017-09-18 15:12:31 -07:00
// For persistence.
// Overloaded for testing.
filePath string
mtx sync.Mutex
}
// Signer is an interface that defines how to sign messages.
// It is the caller's duty to verify the msg before calling Sign,
// eg. to avoid double signing.
// Currently, the only callers are SignVote, SignProposal, and SignHeartbeat.
type Signer interface {
Sign(msg []byte) (crypto.Signature, error)
}
// DefaultSigner implements Signer.
// It uses a standard, unencrypted crypto.PrivKey.
type DefaultSigner struct {
PrivKey crypto.PrivKey `json:"priv_key"`
}
// NewDefaultSigner returns an instance of DefaultSigner.
func NewDefaultSigner(priv crypto.PrivKey) *DefaultSigner {
return &DefaultSigner{
PrivKey: priv,
}
}
// Sign implements Signer. It signs the byte slice with a private key.
func (ds *DefaultSigner) Sign(msg []byte) (crypto.Signature, error) {
return ds.PrivKey.Sign(msg), nil
}
// GetAddress returns the address of the validator.
// Implements PrivValidator.
func (pv *PrivValidatorFS) GetAddress() Address {
return pv.Address
}
// GetPubKey returns the public key of the validator.
// Implements PrivValidator.
func (pv *PrivValidatorFS) GetPubKey() crypto.PubKey {
return pv.PubKey
}
// GenPrivValidatorFS generates a new validator with randomly generated private key
// and sets the filePath, but does not call Save().
func GenPrivValidatorFS(filePath string) *PrivValidatorFS {
privKey := crypto.GenPrivKeyEd25519().Wrap()
return &PrivValidatorFS{
Address: privKey.PubKey().Address(),
PubKey: privKey.PubKey(),
PrivKey: privKey,
LastStep: stepNone,
Signer: NewDefaultSigner(privKey),
filePath: filePath,
}
}
// LoadPrivValidatorFS loads a PrivValidatorFS from the filePath.
func LoadPrivValidatorFS(filePath string) *PrivValidatorFS {
2017-09-21 14:08:17 -07:00
return LoadPrivValidatorFSWithSigner(filePath, func(privVal PrivValidator) Signer {
return NewDefaultSigner(privVal.(*PrivValidatorFS).PrivKey)
})
2017-09-18 19:05:33 -07:00
}
// 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 {
2017-09-21 21:05:39 -07:00
var privVal *PrivValidatorFS
p2p: introduce peerConn to simplify peer creation (#1226) * expose AuthEnc in the P2P config if AuthEnc is true, dialed peers must have a node ID in the address and it must match the persistent pubkey from the secret handshake. Refs #1157 * fixes after my own review * fix docs * fix build failure ``` p2p/pex/pex_reactor_test.go:288:88: cannot use seed.NodeInfo().NetAddress() (type *p2p.NetAddress) as type string in array or slice literal ``` * p2p: introduce peerConn to simplify peer creation * Introduce `peerConn` containing the known fields of `peer` * `peer` only created in `sw.addPeer` once handshake is complete and NodeInfo is checked * Eliminates some mutable variables and makes the code flow better * Simplifies the `newXxxPeer` funcs * Use ID instead of PubKey where possible. * SetPubKeyFilter -> SetIDFilter * nodeInfo.Validate takes ID * remove peer.PubKey() * persistent node ids * fixes from review * test: use ip_plus_id.sh more * fix invalid memory panic during fast_sync test ``` 2018-02-21T06:30:05Z box887.localdomain docker/local_testnet_4[14907]: panic: runtime error: invalid memory address or nil pointer dereference 2018-02-21T06:30:05Z box887.localdomain docker/local_testnet_4[14907]: [signal SIGSEGV: segmentation violation code=0x1 addr=0x20 pc=0x98dd3e] 2018-02-21T06:30:05Z box887.localdomain docker/local_testnet_4[14907]: 2018-02-21T06:30:05Z box887.localdomain docker/local_testnet_4[14907]: goroutine 3432 [running]: 2018-02-21T06:30:05Z box887.localdomain docker/local_testnet_4[14907]: github.com/tendermint/tendermint/p2p.newOutboundPeerConn(0xc423fd1380, 0xc420933e00, 0x1, 0x1239a60, 0 xc420128c40, 0x2, 0x42caf6, 0xc42001f300, 0xc422831d98, 0xc4227951c0, ...) 2018-02-21T06:30:05Z box887.localdomain docker/local_testnet_4[14907]: #011/go/src/github.com/tendermint/tendermint/p2p/peer.go:123 +0x31e 2018-02-21T06:30:05Z box887.localdomain docker/local_testnet_4[14907]: github.com/tendermint/tendermint/p2p.(*Switch).addOutboundPeerWithConfig(0xc4200ad040, 0xc423fd1380, 0 xc420933e00, 0xc423f48801, 0x28, 0x2) 2018-02-21T06:30:05Z box887.localdomain docker/local_testnet_4[14907]: #011/go/src/github.com/tendermint/tendermint/p2p/switch.go:455 +0x12b 2018-02-21T06:30:05Z box887.localdomain docker/local_testnet_4[14907]: github.com/tendermint/tendermint/p2p.(*Switch).DialPeerWithAddress(0xc4200ad040, 0xc423fd1380, 0x1, 0x 0, 0x0) 2018-02-21T06:30:05Z box887.localdomain docker/local_testnet_4[14907]: #011/go/src/github.com/tendermint/tendermint/p2p/switch.go:371 +0xdc 2018-02-21T06:30:05Z box887.localdomain docker/local_testnet_4[14907]: github.com/tendermint/tendermint/p2p.(*Switch).reconnectToPeer(0xc4200ad040, 0x123e000, 0xc42007bb00) 2018-02-21T06:30:05Z box887.localdomain docker/local_testnet_4[14907]: #011/go/src/github.com/tendermint/tendermint/p2p/switch.go:290 +0x25f 2018-02-21T06:30:05Z box887.localdomain docker/local_testnet_4[14907]: created by github.com/tendermint/tendermint/p2p.(*Switch).StopPeerForError 2018-02-21T06:30:05Z box887.localdomain docker/local_testnet_4[14907]: #011/go/src/github.com/tendermint/tendermint/p2p/switch.go:256 +0x1b7 ```
2018-02-27 03:54:40 -08:00
if cmn.FileExists(filePath) {
2017-09-21 21:05:39 -07:00
privVal = LoadPrivValidatorFS(filePath)
} else {
2017-09-21 21:05:39 -07:00
privVal = GenPrivValidatorFS(filePath)
privVal.Save()
}
2017-09-21 21:05:39 -07:00
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.
2017-09-21 14:08:17 -07:00
func LoadPrivValidatorFSWithSigner(filePath string, signerFunc func(PrivValidator) Signer) *PrivValidatorFS {
privValJSONBytes, err := ioutil.ReadFile(filePath)
if err != nil {
cmn.Exit(err.Error())
}
2017-09-21 14:08:17 -07:00
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
2017-09-21 14:08:17 -07:00
privVal.Signer = signerFunc(privVal)
return privVal
}
2017-09-18 19:05:33 -07:00
// Save persists the PrivValidatorFS to disk.
func (privVal *PrivValidatorFS) Save() {
privVal.mtx.Lock()
defer privVal.mtx.Unlock()
privVal.save()
}
2017-09-18 19:05:33 -07:00
func (privVal *PrivValidatorFS) save() {
outFile := privVal.filePath
if outFile == "" {
panic("Cannot save PrivValidator: filePath not set")
}
jsonBytes, err := json.Marshal(privVal)
if err != nil {
panic(err)
}
err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600)
if err != nil {
panic(err)
}
}
// Reset resets all fields in the PrivValidatorFS.
// NOTE: Unsafe!
func (privVal *PrivValidatorFS) Reset() {
var sig crypto.Signature
privVal.LastHeight = 0
privVal.LastRound = 0
privVal.LastStep = 0
privVal.LastSignature = sig
privVal.LastSignBytes = nil
privVal.Save()
}
// SignVote signs a canonical representation of the vote, along with the
// chainID. Implements PrivValidator.
func (privVal *PrivValidatorFS) SignVote(chainID string, vote *Vote) error {
privVal.mtx.Lock()
defer privVal.mtx.Unlock()
if err := privVal.signVote(chainID, vote); err != nil {
return errors.New(cmn.Fmt("Error signing vote: %v", err))
}
return nil
}
// SignProposal signs a canonical representation of the proposal, along with
// the chainID. Implements PrivValidator.
func (privVal *PrivValidatorFS) SignProposal(chainID string, proposal *Proposal) error {
privVal.mtx.Lock()
defer privVal.mtx.Unlock()
if err := privVal.signProposal(chainID, proposal); err != nil {
return fmt.Errorf("Error signing proposal: %v", err)
}
return nil
}
2017-12-21 13:28:05 -08:00
// 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")
}
}
2017-09-18 20:16:14 -07:00
}
return false, nil
}
2017-09-18 20:16:14 -07:00
// signVote checks if the vote is good to sign and sets the vote signature.
// It may need to set the timestamp as well if the vote is otherwise the same as
// a previously signed vote (ie. we crashed after signing but before the vote hit the WAL).
func (privVal *PrivValidatorFS) signVote(chainID string, vote *Vote) error {
height, round, step := vote.Height, vote.Round, voteToStep(vote)
signBytes := vote.SignBytes(chainID)
sameHRS, err := privVal.checkHRS(height, round, step)
if err != nil {
return err
2017-09-18 20:16:14 -07:00
}
// We might crash before writing to the wal,
// causing us to try to re-sign for the same HRS.
// If signbytes are the same, use the last signature.
// If they only differ by timestamp, use last timestamp and signature
// Otherwise, return error
if sameHRS {
if bytes.Equal(signBytes, privVal.LastSignBytes) {
vote.Signature = privVal.LastSignature
} else if timestamp, ok := checkVotesOnlyDifferByTimestamp(privVal.LastSignBytes, signBytes); ok {
vote.Timestamp = timestamp
vote.Signature = privVal.LastSignature
} else {
err = fmt.Errorf("Conflicting data")
}
return err
}
// It passed the checks. Sign the vote
sig, err := privVal.Sign(signBytes)
if err != nil {
return err
}
privVal.saveSigned(height, round, step, signBytes, sig)
vote.Signature = sig
return nil
}
// signProposal checks if the proposal is good to sign and sets the proposal signature.
// It may need to set the timestamp as well if the proposal is otherwise the same as
// a previously signed proposal ie. we crashed after signing but before the proposal hit the WAL).
func (privVal *PrivValidatorFS) signProposal(chainID string, proposal *Proposal) error {
height, round, step := proposal.Height, proposal.Round, stepPropose
signBytes := proposal.SignBytes(chainID)
sameHRS, err := privVal.checkHRS(height, round, step)
if err != nil {
return err
}
// We might crash before writing to the wal,
// causing us to try to re-sign for the same HRS.
// If signbytes are the same, use the last signature.
// If they only differ by timestamp, use last timestamp and signature
// Otherwise, return error
if sameHRS {
if bytes.Equal(signBytes, privVal.LastSignBytes) {
proposal.Signature = privVal.LastSignature
} else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(privVal.LastSignBytes, signBytes); ok {
proposal.Timestamp = timestamp
proposal.Signature = privVal.LastSignature
} else {
err = fmt.Errorf("Conflicting data")
}
return err
}
// It passed the checks. Sign the proposal
sig, err := privVal.Sign(signBytes)
if err != nil {
return err
}
privVal.saveSigned(height, round, step, signBytes, sig)
proposal.Signature = sig
return nil
}
// Persist height/round/step and signature
func (privVal *PrivValidatorFS) saveSigned(height int64, round int, step int8,
signBytes []byte, sig crypto.Signature) {
privVal.LastHeight = height
privVal.LastRound = round
privVal.LastStep = step
privVal.LastSignature = sig
privVal.LastSignBytes = signBytes
privVal.save()
2017-09-18 20:16:14 -07:00
}
// 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)
}
//-------------------------------------
2017-09-18 19:05:33 -07:00
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)
}
// 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)
}