github.com/evdatsion/aphelion-dpos-bft@v0.32.1/privval/file.go (about)

     1  package privval
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"time"
     9  
    10  	"github.com/evdatsion/aphelion-dpos-bft/crypto"
    11  	"github.com/evdatsion/aphelion-dpos-bft/crypto/ed25519"
    12  	cmn "github.com/evdatsion/aphelion-dpos-bft/libs/common"
    13  	"github.com/evdatsion/aphelion-dpos-bft/types"
    14  	tmtime "github.com/evdatsion/aphelion-dpos-bft/types/time"
    15  )
    16  
    17  // TODO: type ?
    18  const (
    19  	stepNone      int8 = 0 // Used to distinguish the initial state
    20  	stepPropose   int8 = 1
    21  	stepPrevote   int8 = 2
    22  	stepPrecommit int8 = 3
    23  )
    24  
    25  // A vote is either stepPrevote or stepPrecommit.
    26  func voteToStep(vote *types.Vote) int8 {
    27  	switch vote.Type {
    28  	case types.PrevoteType:
    29  		return stepPrevote
    30  	case types.PrecommitType:
    31  		return stepPrecommit
    32  	default:
    33  		panic("Unknown vote type")
    34  	}
    35  }
    36  
    37  //-------------------------------------------------------------------------------
    38  
    39  // FilePVKey stores the immutable part of PrivValidator.
    40  type FilePVKey struct {
    41  	Address types.Address  `json:"address"`
    42  	PubKey  crypto.PubKey  `json:"pub_key"`
    43  	PrivKey crypto.PrivKey `json:"priv_key"`
    44  
    45  	filePath string
    46  }
    47  
    48  // Save persists the FilePVKey to its filePath.
    49  func (pvKey FilePVKey) Save() {
    50  	outFile := pvKey.filePath
    51  	if outFile == "" {
    52  		panic("cannot save PrivValidator key: filePath not set")
    53  	}
    54  
    55  	jsonBytes, err := cdc.MarshalJSONIndent(pvKey, "", "  ")
    56  	if err != nil {
    57  		panic(err)
    58  	}
    59  	err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600)
    60  	if err != nil {
    61  		panic(err)
    62  	}
    63  
    64  }
    65  
    66  //-------------------------------------------------------------------------------
    67  
    68  // FilePVLastSignState stores the mutable part of PrivValidator.
    69  type FilePVLastSignState struct {
    70  	Height    int64        `json:"height"`
    71  	Round     int          `json:"round"`
    72  	Step      int8         `json:"step"`
    73  	Signature []byte       `json:"signature,omitempty"`
    74  	SignBytes cmn.HexBytes `json:"signbytes,omitempty"`
    75  
    76  	filePath string
    77  }
    78  
    79  // CheckHRS checks the given height, round, step (HRS) against that of the
    80  // FilePVLastSignState. It returns an error if the arguments constitute a regression,
    81  // or if they match but the SignBytes are empty.
    82  // The returned boolean indicates whether the last Signature should be reused -
    83  // it returns true if the HRS matches the arguments and the SignBytes are not empty (indicating
    84  // we have already signed for this HRS, and can reuse the existing signature).
    85  // It panics if the HRS matches the arguments, there's a SignBytes, but no Signature.
    86  func (lss *FilePVLastSignState) CheckHRS(height int64, round int, step int8) (bool, error) {
    87  
    88  	if lss.Height > height {
    89  		return false, fmt.Errorf("height regression. Got %v, last height %v", height, lss.Height)
    90  	}
    91  
    92  	if lss.Height == height {
    93  		if lss.Round > round {
    94  			return false, fmt.Errorf("round regression at height %v. Got %v, last round %v", height, round, lss.Round)
    95  		}
    96  
    97  		if lss.Round == round {
    98  			if lss.Step > step {
    99  				return false, fmt.Errorf("step regression at height %v round %v. Got %v, last step %v", height, round, step, lss.Step)
   100  			} else if lss.Step == step {
   101  				if lss.SignBytes != nil {
   102  					if lss.Signature == nil {
   103  						panic("pv: Signature is nil but SignBytes is not!")
   104  					}
   105  					return true, nil
   106  				}
   107  				return false, errors.New("no SignBytes found")
   108  			}
   109  		}
   110  	}
   111  	return false, nil
   112  }
   113  
   114  // Save persists the FilePvLastSignState to its filePath.
   115  func (lss *FilePVLastSignState) Save() {
   116  	outFile := lss.filePath
   117  	if outFile == "" {
   118  		panic("cannot save FilePVLastSignState: filePath not set")
   119  	}
   120  	jsonBytes, err := cdc.MarshalJSONIndent(lss, "", "  ")
   121  	if err != nil {
   122  		panic(err)
   123  	}
   124  	err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600)
   125  	if err != nil {
   126  		panic(err)
   127  	}
   128  }
   129  
   130  //-------------------------------------------------------------------------------
   131  
   132  // FilePV implements PrivValidator using data persisted to disk
   133  // to prevent double signing.
   134  // NOTE: the directories containing pv.Key.filePath and pv.LastSignState.filePath must already exist.
   135  // It includes the LastSignature and LastSignBytes so we don't lose the signature
   136  // if the process crashes after signing but before the resulting consensus message is processed.
   137  type FilePV struct {
   138  	Key           FilePVKey
   139  	LastSignState FilePVLastSignState
   140  }
   141  
   142  // GenFilePV generates a new validator with randomly generated private key
   143  // and sets the filePaths, but does not call Save().
   144  func GenFilePV(keyFilePath, stateFilePath string) *FilePV {
   145  	privKey := ed25519.GenPrivKey()
   146  
   147  	return &FilePV{
   148  		Key: FilePVKey{
   149  			Address:  privKey.PubKey().Address(),
   150  			PubKey:   privKey.PubKey(),
   151  			PrivKey:  privKey,
   152  			filePath: keyFilePath,
   153  		},
   154  		LastSignState: FilePVLastSignState{
   155  			Step:     stepNone,
   156  			filePath: stateFilePath,
   157  		},
   158  	}
   159  }
   160  
   161  // LoadFilePV loads a FilePV from the filePaths.  The FilePV handles double
   162  // signing prevention by persisting data to the stateFilePath.  If either file path
   163  // does not exist, the program will exit.
   164  func LoadFilePV(keyFilePath, stateFilePath string) *FilePV {
   165  	return loadFilePV(keyFilePath, stateFilePath, true)
   166  }
   167  
   168  // LoadFilePVEmptyState loads a FilePV from the given keyFilePath, with an empty LastSignState.
   169  // If the keyFilePath does not exist, the program will exit.
   170  func LoadFilePVEmptyState(keyFilePath, stateFilePath string) *FilePV {
   171  	return loadFilePV(keyFilePath, stateFilePath, false)
   172  }
   173  
   174  // If loadState is true, we load from the stateFilePath. Otherwise, we use an empty LastSignState.
   175  func loadFilePV(keyFilePath, stateFilePath string, loadState bool) *FilePV {
   176  	keyJSONBytes, err := ioutil.ReadFile(keyFilePath)
   177  	if err != nil {
   178  		cmn.Exit(err.Error())
   179  	}
   180  	pvKey := FilePVKey{}
   181  	err = cdc.UnmarshalJSON(keyJSONBytes, &pvKey)
   182  	if err != nil {
   183  		cmn.Exit(fmt.Sprintf("Error reading PrivValidator key from %v: %v\n", keyFilePath, err))
   184  	}
   185  
   186  	// overwrite pubkey and address for convenience
   187  	pvKey.PubKey = pvKey.PrivKey.PubKey()
   188  	pvKey.Address = pvKey.PubKey.Address()
   189  	pvKey.filePath = keyFilePath
   190  
   191  	pvState := FilePVLastSignState{}
   192  	if loadState {
   193  		stateJSONBytes, err := ioutil.ReadFile(stateFilePath)
   194  		if err != nil {
   195  			cmn.Exit(err.Error())
   196  		}
   197  		err = cdc.UnmarshalJSON(stateJSONBytes, &pvState)
   198  		if err != nil {
   199  			cmn.Exit(fmt.Sprintf("Error reading PrivValidator state from %v: %v\n", stateFilePath, err))
   200  		}
   201  	}
   202  
   203  	pvState.filePath = stateFilePath
   204  
   205  	return &FilePV{
   206  		Key:           pvKey,
   207  		LastSignState: pvState,
   208  	}
   209  }
   210  
   211  // LoadOrGenFilePV loads a FilePV from the given filePaths
   212  // or else generates a new one and saves it to the filePaths.
   213  func LoadOrGenFilePV(keyFilePath, stateFilePath string) *FilePV {
   214  	var pv *FilePV
   215  	if cmn.FileExists(keyFilePath) {
   216  		pv = LoadFilePV(keyFilePath, stateFilePath)
   217  	} else {
   218  		pv = GenFilePV(keyFilePath, stateFilePath)
   219  		pv.Save()
   220  	}
   221  	return pv
   222  }
   223  
   224  // GetAddress returns the address of the validator.
   225  // Implements PrivValidator.
   226  func (pv *FilePV) GetAddress() types.Address {
   227  	return pv.Key.Address
   228  }
   229  
   230  // GetPubKey returns the public key of the validator.
   231  // Implements PrivValidator.
   232  func (pv *FilePV) GetPubKey() crypto.PubKey {
   233  	return pv.Key.PubKey
   234  }
   235  
   236  // SignVote signs a canonical representation of the vote, along with the
   237  // chainID. Implements PrivValidator.
   238  func (pv *FilePV) SignVote(chainID string, vote *types.Vote) error {
   239  	if err := pv.signVote(chainID, vote); err != nil {
   240  		return fmt.Errorf("error signing vote: %v", err)
   241  	}
   242  	return nil
   243  }
   244  
   245  // SignProposal signs a canonical representation of the proposal, along with
   246  // the chainID. Implements PrivValidator.
   247  func (pv *FilePV) SignProposal(chainID string, proposal *types.Proposal) error {
   248  	if err := pv.signProposal(chainID, proposal); err != nil {
   249  		return fmt.Errorf("error signing proposal: %v", err)
   250  	}
   251  	return nil
   252  }
   253  
   254  // Save persists the FilePV to disk.
   255  func (pv *FilePV) Save() {
   256  	pv.Key.Save()
   257  	pv.LastSignState.Save()
   258  }
   259  
   260  // Reset resets all fields in the FilePV.
   261  // NOTE: Unsafe!
   262  func (pv *FilePV) Reset() {
   263  	var sig []byte
   264  	pv.LastSignState.Height = 0
   265  	pv.LastSignState.Round = 0
   266  	pv.LastSignState.Step = 0
   267  	pv.LastSignState.Signature = sig
   268  	pv.LastSignState.SignBytes = nil
   269  	pv.Save()
   270  }
   271  
   272  // String returns a string representation of the FilePV.
   273  func (pv *FilePV) String() string {
   274  	return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", pv.GetAddress(), pv.LastSignState.Height, pv.LastSignState.Round, pv.LastSignState.Step)
   275  }
   276  
   277  //------------------------------------------------------------------------------------
   278  
   279  // signVote checks if the vote is good to sign and sets the vote signature.
   280  // It may need to set the timestamp as well if the vote is otherwise the same as
   281  // a previously signed vote (ie. we crashed after signing but before the vote hit the WAL).
   282  func (pv *FilePV) signVote(chainID string, vote *types.Vote) error {
   283  	height, round, step := vote.Height, vote.Round, voteToStep(vote)
   284  
   285  	lss := pv.LastSignState
   286  
   287  	sameHRS, err := lss.CheckHRS(height, round, step)
   288  	if err != nil {
   289  		return err
   290  	}
   291  
   292  	signBytes := vote.SignBytes(chainID)
   293  
   294  	// We might crash before writing to the wal,
   295  	// causing us to try to re-sign for the same HRS.
   296  	// If signbytes are the same, use the last signature.
   297  	// If they only differ by timestamp, use last timestamp and signature
   298  	// Otherwise, return error
   299  	if sameHRS {
   300  		if bytes.Equal(signBytes, lss.SignBytes) {
   301  			vote.Signature = lss.Signature
   302  		} else if timestamp, ok := checkVotesOnlyDifferByTimestamp(lss.SignBytes, signBytes); ok {
   303  			vote.Timestamp = timestamp
   304  			vote.Signature = lss.Signature
   305  		} else {
   306  			err = fmt.Errorf("conflicting data")
   307  		}
   308  		return err
   309  	}
   310  
   311  	// It passed the checks. Sign the vote
   312  	sig, err := pv.Key.PrivKey.Sign(signBytes)
   313  	if err != nil {
   314  		return err
   315  	}
   316  	pv.saveSigned(height, round, step, signBytes, sig)
   317  	vote.Signature = sig
   318  	return nil
   319  }
   320  
   321  // signProposal checks if the proposal is good to sign and sets the proposal signature.
   322  // It may need to set the timestamp as well if the proposal is otherwise the same as
   323  // a previously signed proposal ie. we crashed after signing but before the proposal hit the WAL).
   324  func (pv *FilePV) signProposal(chainID string, proposal *types.Proposal) error {
   325  	height, round, step := proposal.Height, proposal.Round, stepPropose
   326  
   327  	lss := pv.LastSignState
   328  
   329  	sameHRS, err := lss.CheckHRS(height, round, step)
   330  	if err != nil {
   331  		return err
   332  	}
   333  
   334  	signBytes := proposal.SignBytes(chainID)
   335  
   336  	// We might crash before writing to the wal,
   337  	// causing us to try to re-sign for the same HRS.
   338  	// If signbytes are the same, use the last signature.
   339  	// If they only differ by timestamp, use last timestamp and signature
   340  	// Otherwise, return error
   341  	if sameHRS {
   342  		if bytes.Equal(signBytes, lss.SignBytes) {
   343  			proposal.Signature = lss.Signature
   344  		} else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(lss.SignBytes, signBytes); ok {
   345  			proposal.Timestamp = timestamp
   346  			proposal.Signature = lss.Signature
   347  		} else {
   348  			err = fmt.Errorf("conflicting data")
   349  		}
   350  		return err
   351  	}
   352  
   353  	// It passed the checks. Sign the proposal
   354  	sig, err := pv.Key.PrivKey.Sign(signBytes)
   355  	if err != nil {
   356  		return err
   357  	}
   358  	pv.saveSigned(height, round, step, signBytes, sig)
   359  	proposal.Signature = sig
   360  	return nil
   361  }
   362  
   363  // Persist height/round/step and signature
   364  func (pv *FilePV) saveSigned(height int64, round int, step int8,
   365  	signBytes []byte, sig []byte) {
   366  
   367  	pv.LastSignState.Height = height
   368  	pv.LastSignState.Round = round
   369  	pv.LastSignState.Step = step
   370  	pv.LastSignState.Signature = sig
   371  	pv.LastSignState.SignBytes = signBytes
   372  	pv.LastSignState.Save()
   373  }
   374  
   375  //-----------------------------------------------------------------------------------------
   376  
   377  // returns the timestamp from the lastSignBytes.
   378  // returns true if the only difference in the votes is their timestamp.
   379  func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {
   380  	var lastVote, newVote types.CanonicalVote
   381  	if err := cdc.UnmarshalBinaryLengthPrefixed(lastSignBytes, &lastVote); err != nil {
   382  		panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into vote: %v", err))
   383  	}
   384  	if err := cdc.UnmarshalBinaryLengthPrefixed(newSignBytes, &newVote); err != nil {
   385  		panic(fmt.Sprintf("signBytes cannot be unmarshalled into vote: %v", err))
   386  	}
   387  
   388  	lastTime := lastVote.Timestamp
   389  
   390  	// set the times to the same value and check equality
   391  	now := tmtime.Now()
   392  	lastVote.Timestamp = now
   393  	newVote.Timestamp = now
   394  	lastVoteBytes, _ := cdc.MarshalJSON(lastVote)
   395  	newVoteBytes, _ := cdc.MarshalJSON(newVote)
   396  
   397  	return lastTime, bytes.Equal(newVoteBytes, lastVoteBytes)
   398  }
   399  
   400  // returns the timestamp from the lastSignBytes.
   401  // returns true if the only difference in the proposals is their timestamp
   402  func checkProposalsOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {
   403  	var lastProposal, newProposal types.CanonicalProposal
   404  	if err := cdc.UnmarshalBinaryLengthPrefixed(lastSignBytes, &lastProposal); err != nil {
   405  		panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into proposal: %v", err))
   406  	}
   407  	if err := cdc.UnmarshalBinaryLengthPrefixed(newSignBytes, &newProposal); err != nil {
   408  		panic(fmt.Sprintf("signBytes cannot be unmarshalled into proposal: %v", err))
   409  	}
   410  
   411  	lastTime := lastProposal.Timestamp
   412  	// set the times to the same value and check equality
   413  	now := tmtime.Now()
   414  	lastProposal.Timestamp = now
   415  	newProposal.Timestamp = now
   416  	lastProposalBytes, _ := cdc.MarshalBinaryLengthPrefixed(lastProposal)
   417  	newProposalBytes, _ := cdc.MarshalBinaryLengthPrefixed(newProposal)
   418  
   419  	return lastTime, bytes.Equal(newProposalBytes, lastProposalBytes)
   420  }