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 }