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 }