diff --git a/privval/priv_validator.go b/privval/priv_validator.go index 6d1f80f2..b4d4ba6e 100644 --- a/privval/priv_validator.go +++ b/privval/priv_validator.go @@ -34,15 +34,7 @@ func voteToStep(vote *types.Vote) int8 { } } -// FilePV implements PrivValidator using data persisted to disk -// to prevent double signing. -// NOTE: the directories containing pv.Key.filePath and pv.LastSignState.filePath must already exist. -// It includes the LastSignature and LastSignBytes so we don't lose the signature -// if the process crashes after signing but before the resulting consensus message is processed. -type FilePV struct { - Key FilePVKey - LastSignState FilePVLastSignState -} +//------------------------------------------------------------------------------- // FilePVKey stores the immutable part of PrivValidator. type FilePVKey struct { @@ -53,6 +45,26 @@ type FilePVKey struct { filePath string } +// Save persists the FilePVKey to its filePath. +func (pvKey FilePVKey) Save() { + outFile := pvKey.filePath + if outFile == "" { + panic("Cannot save PrivValidator key: filePath not set") + } + + jsonBytes, err := cdc.MarshalJSONIndent(pvKey, "", " ") + if err != nil { + panic(err) + } + err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600) + if err != nil { + panic(err) + } + +} + +//------------------------------------------------------------------------------- + // FilePVLastSignState stores the mutable part of PrivValidator. type FilePVLastSignState struct { Height int64 `json:"height"` @@ -64,16 +76,67 @@ type FilePVLastSignState struct { filePath string } -// GetAddress returns the address of the validator. -// Implements PrivValidator. -func (pv *FilePV) GetAddress() types.Address { - return pv.Key.Address +// CheckHRS checks the given height, round, step (HRS) against that of the +// FilePVLastSignState. It returns an error if the arguments constitute a regression, +// or if they match but the SignBytes are empty. +// The returned boolean indicates whether the last Signature should be reused - +// it returns true if the HRS matches the arguments and the SignBytes are not empty (indicating +// we have already signed for this HRS, and can reuse the existing signature). +// It panics if the HRS matches the arguments, there's a SignBytes, but no Signature. +func (lss *FilePVLastSignState) CheckHRS(height int64, round int, step int8) (bool, error) { + + if lss.Height > height { + return false, errors.New("Height regression") + } + + if lss.Height == height { + if lss.Round > round { + return false, errors.New("Round regression") + } + + if lss.Round == round { + if lss.Step > step { + return false, errors.New("Step regression") + } else if lss.Step == step { + if lss.SignBytes != nil { + if lss.Signature == nil { + panic("pv: Signature is nil but SignBytes is not!") + } + return true, nil + } + return false, errors.New("No SignBytes found") + } + } + } + return false, nil } -// GetPubKey returns the public key of the validator. -// Implements PrivValidator. -func (pv *FilePV) GetPubKey() crypto.PubKey { - return pv.Key.PubKey +// Save persists the FilePvLastSignState to its filePath. +func (lss *FilePVLastSignState) Save() { + outFile := lss.filePath + if outFile == "" { + panic("Cannot save FilePVLastSignState: filePath not set") + } + jsonBytes, err := cdc.MarshalJSONIndent(lss, "", " ") + if err != nil { + panic(err) + } + err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600) + if err != nil { + panic(err) + } +} + +//------------------------------------------------------------------------------- + +// FilePV implements PrivValidator using data persisted to disk +// to prevent double signing. +// NOTE: the directories containing pv.Key.filePath and pv.LastSignState.filePath must already exist. +// It includes the LastSignature and LastSignBytes so we don't lose the signature +// if the process crashes after signing but before the resulting consensus message is processed. +type FilePV struct { + Key FilePVKey + LastSignState FilePVLastSignState } // GenFilePV generates a new validator with randomly generated private key @@ -145,54 +208,16 @@ func LoadOrGenFilePV(keyFilePath string, stateFilePath string) *FilePV { return pv } -// Save persists the FilePV to disk. -func (pv *FilePV) Save() { - pv.saveKey() - pv.saveState() +// GetAddress returns the address of the validator. +// Implements PrivValidator. +func (pv *FilePV) GetAddress() types.Address { + return pv.Key.Address } -func (pv *FilePV) saveKey() { - outFile := pv.Key.filePath - if outFile == "" { - panic("Cannot save PrivValidator key: filePath not set") - } - - jsonBytes, err := cdc.MarshalJSONIndent(pv.Key, "", " ") - if err != nil { - panic(err) - } - err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600) - if err != nil { - panic(err) - } - -} - -func (pv *FilePV) saveState() { - outFile := pv.LastSignState.filePath - if outFile == "" { - panic("Cannot save PrivValidator state: filePath not set") - } - jsonBytes, err := cdc.MarshalJSONIndent(pv.LastSignState, "", " ") - 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 []byte - pv.LastSignState.Height = 0 - pv.LastSignState.Round = 0 - pv.LastSignState.Step = 0 - pv.LastSignState.Signature = sig - pv.LastSignState.SignBytes = nil - pv.Save() +// GetPubKey returns the public key of the validator. +// Implements PrivValidator. +func (pv *FilePV) GetPubKey() crypto.PubKey { + return pv.Key.PubKey } // SignVote signs a canonical representation of the vote, along with the @@ -213,59 +238,57 @@ func (pv *FilePV) SignProposal(chainID string, proposal *types.Proposal) error { 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) { - lss := pv.LastSignState - - if lss.Height > height { - return false, errors.New("Height regression") - } - - if lss.Height == height { - if lss.Round > round { - return false, errors.New("Round regression") - } - - if lss.Round == round { - if lss.Step > step { - return false, errors.New("Step regression") - } else if lss.Step == step { - if lss.SignBytes != nil { - if lss.Signature == nil { - panic("pv: LastSignature is nil but LastSignBytes is not!") - } - return true, nil - } - return false, errors.New("No LastSignature found") - } - } - } - return false, nil +// Save persists the FilePV to disk. +func (pv *FilePV) Save() { + pv.Key.Save() + pv.LastSignState.Save() } +// Reset resets all fields in the FilePV. +// NOTE: Unsafe! +func (pv *FilePV) Reset() { + var sig []byte + pv.LastSignState.Height = 0 + pv.LastSignState.Round = 0 + pv.LastSignState.Step = 0 + pv.LastSignState.Signature = sig + pv.LastSignState.SignBytes = nil + pv.Save() +} + +// 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.LastSignState.Height, pv.LastSignState.Round, pv.LastSignState.Step) +} + +//------------------------------------------------------------------------------------ + // 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) + lss := pv.LastSignState + + sameHRS, err := lss.CheckHRS(height, round, step) if err != nil { return err } + signBytes := vote.SignBytes(chainID) + // 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.LastSignState.SignBytes) { - vote.Signature = pv.LastSignState.Signature - } else if timestamp, ok := checkVotesOnlyDifferByTimestamp(pv.LastSignState.SignBytes, signBytes); ok { + if bytes.Equal(signBytes, lss.SignBytes) { + vote.Signature = lss.Signature + } else if timestamp, ok := checkVotesOnlyDifferByTimestamp(lss.SignBytes, signBytes); ok { vote.Timestamp = timestamp - vote.Signature = pv.LastSignState.Signature + vote.Signature = lss.Signature } else { err = fmt.Errorf("Conflicting data") } @@ -287,24 +310,27 @@ func (pv *FilePV) signVote(chainID string, vote *types.Vote) error { // 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) + lss := pv.LastSignState + + sameHRS, err := lss.CheckHRS(height, round, step) if err != nil { return err } + signBytes := proposal.SignBytes(chainID) + // 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.LastSignState.SignBytes) { - proposal.Signature = pv.LastSignState.Signature - } else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(pv.LastSignState.SignBytes, signBytes); ok { + if bytes.Equal(signBytes, lss.SignBytes) { + proposal.Signature = lss.Signature + } else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(lss.SignBytes, signBytes); ok { proposal.Timestamp = timestamp - proposal.Signature = pv.LastSignState.Signature + proposal.Signature = lss.Signature } else { err = fmt.Errorf("Conflicting data") } @@ -330,15 +356,10 @@ func (pv *FilePV) saveSigned(height int64, round int, step int8, pv.LastSignState.Step = step pv.LastSignState.Signature = sig pv.LastSignState.SignBytes = signBytes - pv.saveState() + pv.LastSignState.Save() } -// 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.LastSignState.Height, pv.LastSignState.Round, pv.LastSignState.Step) -} - -//------------------------------------- +//----------------------------------------------------------------------------------------- // returns the timestamp from the lastSignBytes. // returns true if the only difference in the votes is their timestamp.