github.com/vipernet-xyz/tm@v0.34.24/privval/file.go (about)

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