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