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