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