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