package types import ( "bytes" "encoding/json" "errors" "fmt" "io/ioutil" "os" "sync" "time" crypto "github.com/tendermint/go-crypto" data "github.com/tendermint/go-wire/data" cmn "github.com/tendermint/tmlibs/common" ) // TODO: type ? const ( stepNone int8 = 0 // Used to distinguish the initial state stepPropose int8 = 1 stepPrevote int8 = 2 stepPrecommit int8 = 3 ) func voteToStep(vote *Vote) int8 { switch vote.Type { case VoteTypePrevote: return stepPrevote case VoteTypePrecommit: return stepPrecommit default: cmn.PanicSanity("Unknown vote type") return 0 } } // PrivValidator defines the functionality of a local Tendermint validator // that signs votes, proposals, and heartbeats, and never double signs. type PrivValidator interface { GetAddress() Address // redundant since .PubKey().Address() GetPubKey() crypto.PubKey SignVote(chainID string, vote *Vote) error SignProposal(chainID string, proposal *Proposal) error SignHeartbeat(chainID string, heartbeat *Heartbeat) error } // PrivValidatorFS implements PrivValidator using data persisted to disk // to prevent double signing. The Signer itself can be mutated to use // something besides the default, for instance a hardware signer. 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 data.Bytes `json:"last_signbytes,omitempty"` // so we dont lose signatures // PrivKey should be empty if a Signer other than the default is being used. PrivKey crypto.PrivKey `json:"priv_key"` Signer `json:"-"` // For persistence. // Overloaded for testing. filePath string mtx sync.Mutex } // Signer is an interface that defines how to sign messages. // It is the caller's duty to verify the msg before calling Sign, // eg. to avoid double signing. // Currently, the only callers are SignVote, SignProposal, and SignHeartbeat. type Signer interface { Sign(msg []byte) (crypto.Signature, error) } // DefaultSigner implements Signer. // It uses a standard, unencrypted crypto.PrivKey. type DefaultSigner struct { PrivKey crypto.PrivKey `json:"priv_key"` } // NewDefaultSigner returns an instance of DefaultSigner. func NewDefaultSigner(priv crypto.PrivKey) *DefaultSigner { return &DefaultSigner{ PrivKey: priv, } } // Sign implements Signer. It signs the byte slice with a private key. func (ds *DefaultSigner) Sign(msg []byte) (crypto.Signature, error) { return ds.PrivKey.Sign(msg), nil } // GetAddress returns the address of the validator. // Implements PrivValidator. func (pv *PrivValidatorFS) GetAddress() Address { return pv.Address } // GetPubKey returns the public key of the validator. // Implements PrivValidator. func (pv *PrivValidatorFS) GetPubKey() crypto.PubKey { return pv.PubKey } // GenPrivValidatorFS generates a new validator with randomly generated private key // and sets the filePath, but does not call Save(). func GenPrivValidatorFS(filePath string) *PrivValidatorFS { privKey := crypto.GenPrivKeyEd25519().Wrap() return &PrivValidatorFS{ Address: privKey.PubKey().Address(), PubKey: privKey.PubKey(), PrivKey: privKey, LastStep: stepNone, Signer: NewDefaultSigner(privKey), filePath: filePath, } } // LoadPrivValidatorFS loads a PrivValidatorFS from the filePath. func LoadPrivValidatorFS(filePath string) *PrivValidatorFS { return LoadPrivValidatorFSWithSigner(filePath, func(privVal PrivValidator) Signer { return NewDefaultSigner(privVal.(*PrivValidatorFS).PrivKey) }) } // LoadOrGenPrivValidatorFS loads a PrivValidatorFS from the given filePath // or else generates a new one and saves it to the filePath. func LoadOrGenPrivValidatorFS(filePath string) *PrivValidatorFS { var privVal *PrivValidatorFS if _, err := os.Stat(filePath); err == nil { 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() { if privVal.filePath == "" { cmn.PanicSanity("Cannot save PrivValidator: filePath not set") } jsonBytes, err := json.Marshal(privVal) if err != nil { // `@; BOOM!!! cmn.PanicCrisis(err) } err = cmn.WriteFileAtomic(privVal.filePath, jsonBytes, 0600) if err != nil { // `@; BOOM!!! cmn.PanicCrisis(err) } } // Reset resets all fields in the PrivValidatorFS. // NOTE: Unsafe! func (privVal *PrivValidatorFS) Reset() { privVal.LastHeight = 0 privVal.LastRound = 0 privVal.LastStep = 0 privVal.LastSignature = crypto.Signature{} privVal.LastSignBytes = nil privVal.Save() } // SignVote signs a canonical representation of the vote, along with the // chainID. Implements PrivValidator. func (privVal *PrivValidatorFS) SignVote(chainID string, vote *Vote) error { privVal.mtx.Lock() defer privVal.mtx.Unlock() if err := privVal.signVote(chainID, vote); err != nil { return errors.New(cmn.Fmt("Error signing vote: %v", err)) } return nil } // SignProposal signs a canonical representation of the proposal, along with // the chainID. Implements PrivValidator. func (privVal *PrivValidatorFS) SignProposal(chainID string, proposal *Proposal) error { privVal.mtx.Lock() defer privVal.mtx.Unlock() if err := privVal.signProposal(chainID, proposal); err != nil { return fmt.Errorf("Error signing proposal: %v", err) } return nil } // returns error if HRS regression or no LastSignBytes. returns true if HRS is unchanged func (privVal *PrivValidatorFS) checkHRS(height int64, round int, step int8) (bool, error) { if privVal.LastHeight > height { return false, errors.New("Height regression") } if privVal.LastHeight == height { if privVal.LastRound > round { return false, errors.New("Round regression") } if privVal.LastRound == round { if privVal.LastStep > step { return false, errors.New("Step regression") } else if privVal.LastStep == step { if privVal.LastSignBytes != nil { if privVal.LastSignature.Empty() { panic("privVal: LastSignature is nil but LastSignBytes is not!") } return true, nil } return false, errors.New("No LastSignature found") } } } return false, nil } // signVote checks if the vote is good to sign and sets the vote signature. // It may need to set the timestamp as well if the vote is otherwise the same as // a previously signed vote (ie. we crashed after signing but before the vote hit the WAL). func (privVal *PrivValidatorFS) signVote(chainID string, vote *Vote) error { height, round, step := vote.Height, vote.Round, voteToStep(vote) signBytes := SignBytes(chainID, vote) sameHRS, err := privVal.checkHRS(height, round, step) if err != nil { return err } // We might crash before writing to the wal, // causing us to try to re-sign for the same HRS. // If signbytes are the same, use the last signature. // If they only differ by timestamp, use last timestamp and signature // Otherwise, return error if sameHRS { if bytes.Equal(signBytes, privVal.LastSignBytes) { vote.Signature = privVal.LastSignature } else if timestamp, ok := checkVotesOnlyDifferByTimestamp(privVal.LastSignBytes, signBytes); ok { vote.Timestamp = timestamp vote.Signature = privVal.LastSignature } else { err = fmt.Errorf("Conflicting data") } return err } // It passed the checks. Sign the vote sig, err := privVal.Sign(signBytes) if err != nil { return err } privVal.saveSigned(height, round, step, signBytes, sig) vote.Signature = sig return nil } // signProposal checks if the proposal is good to sign and sets the proposal signature. // It may need to set the timestamp as well if the proposal is otherwise the same as // a previously signed proposal ie. we crashed after signing but before the proposal hit the WAL). func (privVal *PrivValidatorFS) signProposal(chainID string, proposal *Proposal) error { height, round, step := proposal.Height, proposal.Round, stepPropose signBytes := SignBytes(chainID, proposal) sameHRS, err := privVal.checkHRS(height, round, step) if err != nil { return err } // We might crash before writing to the wal, // causing us to try to re-sign for the same HRS. // If signbytes are the same, use the last signature. // If they only differ by timestamp, use last timestamp and signature // Otherwise, return error if sameHRS { if bytes.Equal(signBytes, privVal.LastSignBytes) { proposal.Signature = privVal.LastSignature } else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(privVal.LastSignBytes, signBytes); ok { proposal.Timestamp = timestamp proposal.Signature = privVal.LastSignature } else { err = fmt.Errorf("Conflicting data") } return err } // It passed the checks. Sign the proposal sig, err := privVal.Sign(signBytes) if err != nil { return err } privVal.saveSigned(height, round, step, signBytes, sig) proposal.Signature = sig return nil } // Persist height/round/step and signature func (privVal *PrivValidatorFS) saveSigned(height int64, round int, step int8, signBytes []byte, sig crypto.Signature) { privVal.LastHeight = height privVal.LastRound = round privVal.LastStep = step privVal.LastSignature = sig privVal.LastSignBytes = signBytes privVal.save() } // SignHeartbeat signs a canonical representation of the heartbeat, along with the chainID. // Implements PrivValidator. func (privVal *PrivValidatorFS) SignHeartbeat(chainID string, heartbeat *Heartbeat) error { privVal.mtx.Lock() defer privVal.mtx.Unlock() var err error heartbeat.Signature, err = privVal.Sign(SignBytes(chainID, heartbeat)) 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) } // 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) }