rearrange priv_validator.go

This commit is contained in:
Ethan Buchman
2018-12-17 23:04:23 -05:00
parent b5544a4560
commit d382f064ed

View File

@ -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. // FilePVKey stores the immutable part of PrivValidator.
type FilePVKey struct { type FilePVKey struct {
@ -53,6 +45,26 @@ type FilePVKey struct {
filePath string 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. // FilePVLastSignState stores the mutable part of PrivValidator.
type FilePVLastSignState struct { type FilePVLastSignState struct {
Height int64 `json:"height"` Height int64 `json:"height"`
@ -64,16 +76,67 @@ type FilePVLastSignState struct {
filePath string filePath string
} }
// GetAddress returns the address of the validator. // CheckHRS checks the given height, round, step (HRS) against that of the
// Implements PrivValidator. // FilePVLastSignState. It returns an error if the arguments constitute a regression,
func (pv *FilePV) GetAddress() types.Address { // or if they match but the SignBytes are empty.
return pv.Key.Address // 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")
} }
// GetPubKey returns the public key of the validator. if lss.Height == height {
// Implements PrivValidator. if lss.Round > round {
func (pv *FilePV) GetPubKey() crypto.PubKey { return false, errors.New("Round regression")
return pv.Key.PubKey }
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
}
// 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 // GenFilePV generates a new validator with randomly generated private key
@ -145,54 +208,16 @@ func LoadOrGenFilePV(keyFilePath string, stateFilePath string) *FilePV {
return pv return pv
} }
// Save persists the FilePV to disk. // GetAddress returns the address of the validator.
func (pv *FilePV) Save() { // Implements PrivValidator.
pv.saveKey() func (pv *FilePV) GetAddress() types.Address {
pv.saveState() return pv.Key.Address
} }
func (pv *FilePV) saveKey() { // GetPubKey returns the public key of the validator.
outFile := pv.Key.filePath // Implements PrivValidator.
if outFile == "" { func (pv *FilePV) GetPubKey() crypto.PubKey {
panic("Cannot save PrivValidator key: filePath not set") return pv.Key.PubKey
}
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()
} }
// SignVote signs a canonical representation of the vote, along with the // 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 return nil
} }
// returns error if HRS regression or no LastSignBytes. returns true if HRS is unchanged // Save persists the FilePV to disk.
func (pv *FilePV) checkHRS(height int64, round int, step int8) (bool, error) { func (pv *FilePV) Save() {
lss := pv.LastSignState pv.Key.Save()
pv.LastSignState.Save()
if lss.Height > height {
return false, errors.New("Height regression")
} }
if lss.Height == height { // Reset resets all fields in the FilePV.
if lss.Round > round { // NOTE: Unsafe!
return false, errors.New("Round regression") 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()
} }
if lss.Round == round { // String returns a string representation of the FilePV.
if lss.Step > step { func (pv *FilePV) String() string {
return false, errors.New("Step regression") return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", pv.GetAddress(), pv.LastSignState.Height, pv.LastSignState.Round, pv.LastSignState.Step)
} 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
} }
//------------------------------------------------------------------------------------
// signVote checks if the vote is good to sign and sets the vote signature. // 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 // 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). // 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 { func (pv *FilePV) signVote(chainID string, vote *types.Vote) error {
height, round, step := vote.Height, vote.Round, voteToStep(vote) 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 { if err != nil {
return err return err
} }
signBytes := vote.SignBytes(chainID)
// We might crash before writing to the wal, // We might crash before writing to the wal,
// causing us to try to re-sign for the same HRS. // causing us to try to re-sign for the same HRS.
// If signbytes are the same, use the last signature. // If signbytes are the same, use the last signature.
// If they only differ by timestamp, use last timestamp and signature // If they only differ by timestamp, use last timestamp and signature
// Otherwise, return error // Otherwise, return error
if sameHRS { if sameHRS {
if bytes.Equal(signBytes, pv.LastSignState.SignBytes) { if bytes.Equal(signBytes, lss.SignBytes) {
vote.Signature = pv.LastSignState.Signature vote.Signature = lss.Signature
} else if timestamp, ok := checkVotesOnlyDifferByTimestamp(pv.LastSignState.SignBytes, signBytes); ok { } else if timestamp, ok := checkVotesOnlyDifferByTimestamp(lss.SignBytes, signBytes); ok {
vote.Timestamp = timestamp vote.Timestamp = timestamp
vote.Signature = pv.LastSignState.Signature vote.Signature = lss.Signature
} else { } else {
err = fmt.Errorf("Conflicting data") 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). // 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 { func (pv *FilePV) signProposal(chainID string, proposal *types.Proposal) error {
height, round, step := proposal.Height, proposal.Round, stepPropose 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 { if err != nil {
return err return err
} }
signBytes := proposal.SignBytes(chainID)
// We might crash before writing to the wal, // We might crash before writing to the wal,
// causing us to try to re-sign for the same HRS. // causing us to try to re-sign for the same HRS.
// If signbytes are the same, use the last signature. // If signbytes are the same, use the last signature.
// If they only differ by timestamp, use last timestamp and signature // If they only differ by timestamp, use last timestamp and signature
// Otherwise, return error // Otherwise, return error
if sameHRS { if sameHRS {
if bytes.Equal(signBytes, pv.LastSignState.SignBytes) { if bytes.Equal(signBytes, lss.SignBytes) {
proposal.Signature = pv.LastSignState.Signature proposal.Signature = lss.Signature
} else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(pv.LastSignState.SignBytes, signBytes); ok { } else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(lss.SignBytes, signBytes); ok {
proposal.Timestamp = timestamp proposal.Timestamp = timestamp
proposal.Signature = pv.LastSignState.Signature proposal.Signature = lss.Signature
} else { } else {
err = fmt.Errorf("Conflicting data") 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.Step = step
pv.LastSignState.Signature = sig pv.LastSignState.Signature = sig
pv.LastSignState.SignBytes = signBytes 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 the timestamp from the lastSignBytes.
// returns true if the only difference in the votes is their timestamp. // returns true if the only difference in the votes is their timestamp.