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 }