github.com/pokt-network/tendermint@v0.32.11-0.20230426215212-59310158d3e9/privval/file_lite.go (about)

     1  package privval
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"github.com/tendermint/tendermint/crypto"
     7  	"github.com/tendermint/tendermint/crypto/ed25519"
     8  	tmos "github.com/tendermint/tendermint/libs/os"
     9  	"github.com/tendermint/tendermint/libs/tempfile"
    10  	"github.com/tendermint/tendermint/types"
    11  	"io/ioutil"
    12  )
    13  
    14  type PrivateKeyFile struct {
    15  	PrivateKey string `json:"priv_key"`
    16  }
    17  
    18  // FilePVLean implements PrivValidator using data persisted to disk
    19  // to prevent double signing.
    20  // NOTE: the directories containing pv.Keys.filePath and pv.LastSignStates.filePath must already exist.
    21  // It includes the LastSignature and LastSignBytes so we don't lose the signature
    22  // if the process crashes after signing but before the resulting consensus message is processed.
    23  type FilePVLean struct {
    24  	Keys           []FilePVKey
    25  	LastSignStates []FilePVLastSignState
    26  	KeyFilepath    string
    27  	StateFilepath  string
    28  }
    29  
    30  func GenFilePVsLean(keyFilePath, stateFilePath string, numOfKeys uint) *FilePVLean {
    31  	filePvKeys := []FilePVKey{}
    32  	lastSignStates := []FilePVLastSignState{}
    33  	for i := 0; i < int(numOfKeys); i++ {
    34  		privKey := ed25519.GenPrivKey()
    35  		filePvKeys = append(filePvKeys, FilePVKey{
    36  			Address:  privKey.PubKey().Address(),
    37  			PubKey:   privKey.PubKey(),
    38  			PrivKey:  privKey,
    39  			filePath: keyFilePath,
    40  		})
    41  		lastSignStates = append(lastSignStates, FilePVLastSignState{
    42  			Step:     stepNone,
    43  			filePath: stateFilePath,
    44  		})
    45  	}
    46  	return &FilePVLean{
    47  		Keys:           filePvKeys,
    48  		LastSignStates: lastSignStates,
    49  		KeyFilepath:    keyFilePath,
    50  		StateFilepath:  stateFilePath,
    51  	}
    52  }
    53  
    54  // GenFilePVLean generates a new validator with randomly generated private key
    55  // and sets the filePaths, but does not call Save().
    56  func GenFilePVLean(keyFilePath, stateFilePath string) *FilePVLean {
    57  	return GenFilePVsLean(keyFilePath, stateFilePath, 1)
    58  }
    59  
    60  // LoadFilePVLean loads a FilePV from the filePaths.  The FilePV handles double
    61  // signing prevention by persisting data to the stateFilePath.  If either file path
    62  // does not exist, the program will exit.
    63  func LoadFilePVLean(keyFilePath, stateFilePath string) *FilePVLean {
    64  	return loadFilePVLean(keyFilePath, stateFilePath, true)
    65  }
    66  
    67  // If loadState is true, we load from the stateFilePath. Otherwise, we use an empty LastSignState.
    68  func loadFilePVLean(keyFilePath, stateFilePath string, loadState bool) *FilePVLean {
    69  	keyJSONBytes, err := ioutil.ReadFile(keyFilePath)
    70  	if err != nil {
    71  		tmos.Exit(err.Error())
    72  	}
    73  
    74  	var pvKeys []FilePVKey
    75  
    76  	err = cdc.UnmarshalJSON(keyJSONBytes, &pvKeys)
    77  	if err != nil {
    78  		tmos.Exit(fmt.Sprintf("Error reading PrivValidator key from %v: %v\n", keyFilePath, err))
    79  	}
    80  
    81  	for _, key := range pvKeys {
    82  		// overwrite pubkey and address for convenience
    83  		key.PubKey = key.PrivKey.PubKey()
    84  		key.Address = key.PubKey.Address()
    85  	}
    86  
    87  	var pvState []FilePVLastSignState
    88  	if loadState {
    89  		stateJSONBytes, err := ioutil.ReadFile(stateFilePath)
    90  		if err != nil {
    91  			tmos.Exit(err.Error())
    92  		}
    93  		err = cdc.UnmarshalJSON(stateJSONBytes, &pvState)
    94  		if err != nil {
    95  			tmos.Exit(fmt.Sprintf("Error reading PrivValidator state from %v: %v\n", stateFilePath, err))
    96  		}
    97  	}
    98  
    99  	return &FilePVLean{
   100  		Keys:           pvKeys,
   101  		LastSignStates: pvState,
   102  		KeyFilepath:    keyFilePath,
   103  		StateFilepath:  stateFilePath,
   104  	}
   105  }
   106  
   107  // LoadOrGenFilePVLean loads a FilePV from the given filePaths
   108  // or else generates a new one and saves it to the filePaths.
   109  func LoadOrGenFilePVLean(keyFilePath, stateFilePath string) *FilePVLean {
   110  	var pv *FilePVLean
   111  	if tmos.FileExists(keyFilePath) && tmos.FileExists(stateFilePath) {
   112  		pv = LoadFilePVLean(keyFilePath, stateFilePath)
   113  	} else {
   114  		panic("no key file found at " + keyFilePath + " or no state path at " + stateFilePath + " run pocket accounts set-validator(s)")
   115  	}
   116  	return pv
   117  }
   118  
   119  // SignVote signs a canonical representation of the vote, along with the
   120  // chainID. Implements PrivValidator.
   121  func (pv *FilePVLean) SignVote(chainID string, vote *types.Vote, key crypto.PubKey) error {
   122  	if err := pv.signVote(chainID, vote, key); err != nil {
   123  		return fmt.Errorf("error signing vote: %v", err)
   124  	}
   125  	return nil
   126  }
   127  
   128  // SignProposal signs a canonical representation of the proposal, along with
   129  // the chainID. Implements PrivValidator.
   130  func (pv *FilePVLean) SignProposal(chainID string, proposal *types.Proposal, key crypto.PubKey) error {
   131  	if err := pv.signProposal(chainID, proposal, key); err != nil {
   132  		return fmt.Errorf("error signing proposal: %v", err)
   133  	}
   134  	return nil
   135  }
   136  
   137  // String returns a string representation of the FilePV.
   138  func (pv *FilePVLean) String() string {
   139  	if len(pv.Keys) == 0 {
   140  		return "PrivValidator empty"
   141  	}
   142  	return fmt.Sprintf(
   143  		"PrivValidator{%v LH:%v, LR:%v, LS:%v}",
   144  		pv.Keys[0].Address, // TODO make string multi validator?
   145  		pv.LastSignStates[0].Height,
   146  		pv.LastSignStates[0].Round,
   147  		pv.LastSignStates[0].Step,
   148  	)
   149  }
   150  
   151  func (pv *FilePVLean) GetPubKeys() ([]crypto.PubKey, error) {
   152  	keys := make([]crypto.PubKey, len(pv.Keys))
   153  	for i, k := range pv.Keys {
   154  		keys[i] = k.PubKey
   155  	}
   156  	return keys, nil
   157  }
   158  
   159  //------------------------------------------------------------------------------------
   160  
   161  func (pv *FilePVLean) GetPublicKeyIndexFromList(pubKey crypto.PubKey) (int, error) {
   162  	keys, err := pv.GetPubKeys()
   163  	if err != nil {
   164  		return 0, err
   165  	}
   166  	for i, pk := range keys {
   167  		if pk.Equals(pubKey) {
   168  			return i, nil
   169  		}
   170  	}
   171  	return 0, fmt.Errorf("unable to find public key in the filePVLite file")
   172  }
   173  
   174  // signVote checks if the vote is good to sign and sets the vote signature.
   175  // It may need to set the timestamp as well if the vote is otherwise the same as
   176  // a previously signed vote (ie. we crashed after signing but before the vote hit the WAL).
   177  func (pv *FilePVLean) signVote(chainID string, vote *types.Vote, pubKey crypto.PubKey) error {
   178  	height, round, step := vote.Height, vote.Round, voteToStep(vote)
   179  
   180  	lss := pv.LastSignStates
   181  
   182  	index, err := pv.GetPublicKeyIndexFromList(pubKey)
   183  	if err != nil {
   184  		return err
   185  	}
   186  
   187  	sameHRS, err := lss[index].CheckHRS(height, round, step)
   188  	if err != nil {
   189  		return err
   190  	}
   191  
   192  	signBytes := vote.SignBytes(chainID)
   193  
   194  	// We might crash before writing to the wal,
   195  	// causing us to try to re-sign for the same HRS.
   196  	// If signbytes are the same, use the last signature.
   197  	// If they only differ by timestamp, use last timestamp and signature
   198  	// Otherwise, return error
   199  	if sameHRS {
   200  		if bytes.Equal(signBytes, lss[index].SignBytes) {
   201  			vote.Signature = lss[index].Signature
   202  		} else if timestamp, ok := checkVotesOnlyDifferByTimestamp(lss[index].SignBytes, signBytes); ok {
   203  			vote.Timestamp = timestamp
   204  			vote.Signature = lss[index].Signature
   205  		} else {
   206  			err = fmt.Errorf("conflicting data")
   207  		}
   208  		return err
   209  	}
   210  
   211  	// It passed the checks. Sign the vote
   212  	sig, err := pv.Keys[index].PrivKey.Sign(signBytes)
   213  	if err != nil {
   214  		return err
   215  	}
   216  	pv.saveSigned(height, round, step, signBytes, sig, index)
   217  	vote.Signature = sig
   218  	return nil
   219  }
   220  
   221  // signProposal checks if the proposal is good to sign and sets the proposal signature.
   222  // It may need to set the timestamp as well if the proposal is otherwise the same as
   223  // a previously signed proposal ie. we crashed after signing but before the proposal hit the WAL).
   224  func (pv *FilePVLean) signProposal(chainID string, proposal *types.Proposal, pubKey crypto.PubKey) error {
   225  	height, round, step := proposal.Height, proposal.Round, stepPropose
   226  
   227  	lss := pv.LastSignStates
   228  
   229  	index, err := pv.GetPublicKeyIndexFromList(pubKey)
   230  	if err != nil {
   231  		return err
   232  	}
   233  
   234  	sameHRS, err := lss[index].CheckHRS(height, round, step)
   235  	if err != nil {
   236  		return err
   237  	}
   238  
   239  	signBytes := proposal.SignBytes(chainID)
   240  
   241  	// We might crash before writing to the wal,
   242  	// causing us to try to re-sign for the same HRS.
   243  	// If signbytes are the same, use the last signature.
   244  	// If they only differ by timestamp, use last timestamp and signature
   245  	// Otherwise, return error
   246  	if sameHRS {
   247  		if bytes.Equal(signBytes, lss[index].SignBytes) {
   248  			proposal.Signature = lss[index].Signature
   249  		} else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(lss[index].SignBytes, signBytes); ok {
   250  			proposal.Timestamp = timestamp
   251  			proposal.Signature = lss[index].Signature
   252  		} else {
   253  			err = fmt.Errorf("conflicting data")
   254  		}
   255  		return err
   256  	}
   257  
   258  	// It passed the checks. Sign the proposal
   259  	sig, err := pv.Keys[index].PrivKey.Sign(signBytes)
   260  	if err != nil {
   261  		return err
   262  	}
   263  	pv.saveSigned(height, round, step, signBytes, sig, index)
   264  	proposal.Signature = sig
   265  	return nil
   266  }
   267  
   268  // Persist height/round/step and signature
   269  func (pv *FilePVLean) saveSigned(height int64, round int, step int8,
   270  	signBytes []byte, sig []byte, index int) {
   271  
   272  	pv.LastSignStates[index].Height = height
   273  	pv.LastSignStates[index].Round = round
   274  	pv.LastSignStates[index].Step = step
   275  	pv.LastSignStates[index].Signature = sig
   276  	pv.LastSignStates[index].SignBytes = signBytes
   277  
   278  	// backwards compatibility if you're using normal pocket
   279  	if len(pv.LastSignStates) == 1 {
   280  		pv.LastSignStates[index].Save()
   281  		return
   282  	}
   283  	pv.SaveLastSignState()
   284  }
   285  
   286  func (pv *FilePVLean) SaveLastSignState() {
   287  	outFile := pv.StateFilepath
   288  	if outFile == "" {
   289  		panic("cannot save FilePVLastSignState: filePath not set")
   290  	}
   291  	jsonBytes, err := cdc.MarshalJSONIndent(pv.LastSignStates, "", "  ")
   292  	if err != nil {
   293  		panic(err)
   294  	}
   295  	err = tempfile.WriteFileAtomic(outFile, jsonBytes, 0600)
   296  	if err != nil {
   297  		panic(err)
   298  	}
   299  }