@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"io/ioutil"
"sync"
"time"
"github.com/tendermint/tendermint/crypto"
@ -35,126 +34,208 @@ func voteToStep(vote *types.Vote) int8 {
}
}
// FilePV implements PrivValidator using data persisted to disk
// to prevent double signing.
// NOTE: the directory containing the pv.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 {
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 [ ] byte ` json:"last_signature,omitempty" `
LastSignBytes cmn . HexBytes ` json:"last_signbytes,omitempty" `
PrivKey crypto . PrivKey ` json:"priv_key" `
// For persistence.
// Overloaded for testing.
//-------------------------------------------------------------------------------
// FilePVKey stores the immutable part of PrivValidator.
type FilePVKey struct {
Address types . Address ` json:"address" `
PubKey crypto . PubKey ` json:"pub_key" `
PrivKey crypto . PrivKey ` json:"priv_key" `
filePath string
mtx sync . Mutex
}
// GetAddress returns the address of the validator.
// Implements PrivValidator.
func ( pv * FilePV ) GetAddress ( ) types . Address {
return pv . Address
// 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 )
}
}
// GetPubKey returns the public key of the validator.
// Implements PrivValidator.
func ( pv * FilePV ) GetPubKey ( ) crypto . PubKey {
return pv . PubKey
//-------------------------------------------------------------------------------
// FilePVLastSignState stores the mutable part of PrivValidator.
type FilePVLastSignState struct {
Height int64 ` json:"height" `
Round int ` json:"round" `
Step int8 ` json:"step" `
Signature [ ] byte ` json:"signature,omitempty" `
SignBytes cmn . HexBytes ` json:"signbytes,omitempty" `
filePath string
}
// 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
}
// 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
// and sets the filePath, but does not call Save().
func GenFilePV ( filePath string ) * FilePV {
// and sets the filePaths , but does not call Save().
func GenFilePV ( keyFilePath , stateF ilePath string ) * FilePV {
privKey := ed25519 . GenPrivKey ( )
return & FilePV {
Address : privKey . PubKey ( ) . Address ( ) ,
PubKey : privKey . PubKey ( ) ,
PrivKey : privKey ,
LastStep : stepNone ,
filePath : filePath ,
Key : FilePVKey {
Address : privKey . PubKey ( ) . Address ( ) ,
PubKey : privKey . PubKey ( ) ,
PrivKey : privKey ,
filePath : keyFilePath ,
} ,
LastSignState : FilePVLastSignState {
Step : stepNone ,
filePath : stateFilePath ,
} ,
}
}
// 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 )
// LoadFilePV loads a FilePV from the filePaths. The FilePV handles double
// signing prevention by persisting data to the stateFilePath. If either file path
// does not exist, the program will exit.
func LoadFilePV ( keyFilePath , stateFilePath string ) * FilePV {
return loadFilePV ( keyFilePath , stateFilePath , true )
}
// LoadFilePVEmptyState loads a FilePV from the given keyFilePath, with an empty LastSignState.
// If the keyFilePath does not exist, the program will exit.
func LoadFilePVEmptyState ( keyFilePath , stateFilePath string ) * FilePV {
return loadFilePV ( keyFilePath , stateFilePath , false )
}
// If loadState is true, we load from the stateFilePath. Otherwise, we use an empty LastSignState.
func loadFilePV ( keyFilePath , stateFilePath string , loadState bool ) * FilePV {
keyJSONBytes , err := ioutil . ReadFile ( keyFilePath )
if err != nil {
cmn . Exit ( err . Error ( ) )
}
pv := & FilePV { }
err = cdc . UnmarshalJSON ( pvJSONBytes , & pv )
pvKey := FilePVKey { }
err = cdc . UnmarshalJSON ( key JSONBytes, & pvKey )
if err != nil {
cmn . Exit ( fmt . Sprintf ( "Error reading PrivValidator from %v: %v\n" , filePath , err ) )
cmn . Exit ( fmt . Sprintf ( "Error reading PrivValidator key from %v: %v\n" , keyF ilePath, err ) )
}
// overwrite pubkey and address for convenience
pv . PubKey = pv . PrivKey . PubKey ( )
pv . Address = pv . PubKey . Address ( )
pvKey . PubKey = pvKey . PrivKey . PubKey ( )
pvKey . Address = pvKey . PubKey . Address ( )
pvKey . filePath = keyFilePath
pvState := FilePVLastSignState { }
if loadState {
stateJSONBytes , err := ioutil . ReadFile ( stateFilePath )
if err != nil {
cmn . Exit ( err . Error ( ) )
}
err = cdc . UnmarshalJSON ( stateJSONBytes , & pvState )
if err != nil {
cmn . Exit ( fmt . Sprintf ( "Error reading PrivValidator state from %v: %v\n" , stateFilePath , err ) )
}
}
pv . filePath = filePath
return pv
pvState . filePath = stateFilePath
return & FilePV {
Key : pvKey ,
LastSignState : pvState ,
}
}
// 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 {
// LoadOrGenFilePV loads a FilePV from the given filePaths
// or else generates a new one and saves it to the filePaths .
func LoadOrGenFilePV ( keyFilePath , stateF ilePath string ) * FilePV {
var pv * FilePV
if cmn . FileExists ( filePath ) {
pv = LoadFilePV ( filePath )
if cmn . FileExists ( keyF ilePath) {
pv = LoadFilePV ( keyFilePath , stateF ilePath)
} else {
pv = GenFilePV ( filePath )
pv = GenFilePV ( keyFilePath , stateF ilePath)
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 . MarshalJSONIndent ( pv , "" , " " )
if err != nil {
panic ( err )
}
err = cmn . WriteFileAtomic ( outFile , jsonBytes , 0600 )
if err != nil {
panic ( err )
}
// GetAddress returns the address of the validator.
// Implements PrivValidator.
func ( pv * FilePV ) GetAddress ( ) types . Address {
return pv . Key . Address
}
// Reset resets all fields in the FilePV.
// NOTE: Unsafe!
func ( pv * FilePV ) Reset ( ) {
var sig [ ] byte
pv . LastHeight = 0
pv . LastRound = 0
pv . LastStep = 0
pv . LastSignature = sig
pv . LastSignBytes = 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
// 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 fmt . Errorf ( "Error signing vote: %v" , err )
}
@ -164,65 +245,63 @@ func (pv *FilePV) SignVote(chainID string, vote *types.Vote) error {
// 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" )
}
// Save persists the FilePV to disk.
func ( pv * FilePV ) Save ( ) {
pv . Key . Save ( )
pv . LastSignState . Save ( )
}
if pv . LastHeight == height {
if pv . LastRound > round {
return false , errors . New ( "Round regression" )
}
// 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 ( )
}
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
// 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 . Last SignBytes) {
vote . Signature = pv . Last Signature
} else if timestamp , ok := checkVotesOnlyDifferByTimestamp ( pv . Last 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 . Last Signature
vote . Signature = lss . Signature
} else {
err = fmt . Errorf ( "Conflicting data" )
}
@ -230,7 +309,7 @@ func (pv *FilePV) signVote(chainID string, vote *types.Vote) error {
}
// It passed the checks. Sign the vote
sig , err := pv . PrivKey . Sign ( signBytes )
sig , err := pv . Key . PrivKey . Sign ( signBytes )
if err != nil {
return err
}
@ -244,24 +323,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 . Last SignBytes) {
proposal . Signature = pv . Last Signature
} else if timestamp , ok := checkProposalsOnlyDifferByTimestamp ( pv . Last 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 . Last Signature
proposal . Signature = lss . Signature
} else {
err = fmt . Errorf ( "Conflicting data" )
}
@ -269,7 +351,7 @@ func (pv *FilePV) signProposal(chainID string, proposal *types.Proposal) error {
}
// It passed the checks. Sign the proposal
sig , err := pv . PrivKey . Sign ( signBytes )
sig , err := pv . Key . PrivKey . Sign ( signBytes )
if err != nil {
return err
}
@ -282,20 +364,15 @@ func (pv *FilePV) signProposal(chainID string, proposal *types.Proposal) error {
func ( pv * FilePV ) saveSigned ( height int64 , round int , step int8 ,
signBytes [ ] byte , sig [ ] byte ) {
pv . LastHeight = height
pv . LastRound = round
pv . LastStep = step
pv . LastSignature = sig
pv . LastSignBytes = signBytes
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 . LastHeight , pv . LastRound , pv . LastStep )
pv . LastSignState . Height = height
pv . LastSignState . Round = round
pv . LastSignState . Step = step
pv . LastSignState . Signature = sig
pv . LastSignState . SignBytes = signBytes
pv . LastSignState . Save ( )
}
//-------------------------------------
//-----------------------------------------------------------------------------------------
// returns the timestamp from the lastSignBytes.
// returns true if the only difference in the votes is their timestamp.