github.com/vipernet-xyz/tm@v0.34.24/test/maverick/node/privval.go (about) 1 package node 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 8 "github.com/vipernet-xyz/tm/crypto" 9 "github.com/vipernet-xyz/tm/crypto/ed25519" 10 tmbytes "github.com/vipernet-xyz/tm/libs/bytes" 11 tmjson "github.com/vipernet-xyz/tm/libs/json" 12 tmos "github.com/vipernet-xyz/tm/libs/os" 13 "github.com/vipernet-xyz/tm/libs/tempfile" 14 tmproto "github.com/vipernet-xyz/tm/proto/tendermint/types" 15 "github.com/vipernet-xyz/tm/types" 16 ) 17 18 // ******************************************************************************************************************* 19 // 20 // WARNING: FOR TESTING ONLY. DO NOT USE THIS FILE OUTSIDE MAVERICK 21 // 22 // ******************************************************************************************************************* 23 24 const ( 25 stepNone int8 = 0 // Used to distinguish the initial state 26 stepPropose int8 = 1 27 stepPrevote int8 = 2 28 stepPrecommit int8 = 3 29 ) 30 31 // A vote is either stepPrevote or stepPrecommit. 32 func voteToStep(vote *tmproto.Vote) int8 { 33 switch vote.Type { 34 case tmproto.PrevoteType: 35 return stepPrevote 36 case tmproto.PrecommitType: 37 return stepPrecommit 38 default: 39 panic(fmt.Sprintf("Unknown vote type: %v", vote.Type)) 40 } 41 } 42 43 //------------------------------------------------------------------------------- 44 45 // FilePVKey stores the immutable part of PrivValidator. 46 type FilePVKey struct { 47 Address types.Address `json:"address"` 48 PubKey crypto.PubKey `json:"pub_key"` 49 PrivKey crypto.PrivKey `json:"priv_key"` 50 51 filePath string 52 } 53 54 // Save persists the FilePVKey to its filePath. 55 func (pvKey FilePVKey) Save() { 56 outFile := pvKey.filePath 57 if outFile == "" { 58 panic("cannot save PrivValidator key: filePath not set") 59 } 60 61 jsonBytes, err := tmjson.MarshalIndent(pvKey, "", " ") 62 if err != nil { 63 panic(err) 64 } 65 err = tempfile.WriteFileAtomic(outFile, jsonBytes, 0o600) 66 if err != nil { 67 panic(err) 68 } 69 } 70 71 //------------------------------------------------------------------------------- 72 73 // FilePVLastSignState stores the mutable part of PrivValidator. 74 type FilePVLastSignState struct { 75 Height int64 `json:"height"` 76 Round int32 `json:"round"` 77 Step int8 `json:"step"` 78 Signature []byte `json:"signature,omitempty"` 79 SignBytes tmbytes.HexBytes `json:"signbytes,omitempty"` 80 81 filePath string 82 } 83 84 // CheckHRS checks the given height, round, step (HRS) against that of the 85 // FilePVLastSignState. It returns an error if the arguments constitute a regression, 86 // or if they match but the SignBytes are empty. 87 // The returned boolean indicates whether the last Signature should be reused - 88 // it returns true if the HRS matches the arguments and the SignBytes are not empty (indicating 89 // we have already signed for this HRS, and can reuse the existing signature). 90 // It panics if the HRS matches the arguments, there's a SignBytes, but no Signature. 91 func (lss *FilePVLastSignState) CheckHRS(height int64, round int32, step int8) (bool, error) { 92 if lss.Height > height { 93 return false, fmt.Errorf("height regression. Got %v, last height %v", height, lss.Height) 94 } 95 96 if lss.Height == height { 97 if lss.Round > round { 98 return false, fmt.Errorf("round regression at height %v. Got %v, last round %v", height, round, lss.Round) 99 } 100 101 if lss.Round == round { 102 if lss.Step > step { 103 return false, fmt.Errorf( 104 "step regression at height %v round %v. Got %v, last step %v", 105 height, 106 round, 107 step, 108 lss.Step, 109 ) 110 } else if lss.Step == step { 111 if lss.SignBytes != nil { 112 if lss.Signature == nil { 113 panic("pv: Signature is nil but SignBytes is not!") 114 } 115 return true, nil 116 } 117 return false, errors.New("no SignBytes found") 118 } 119 } 120 } 121 return false, nil 122 } 123 124 // Save persists the FilePvLastSignState to its filePath. 125 func (lss *FilePVLastSignState) Save() { 126 outFile := lss.filePath 127 if outFile == "" { 128 panic("cannot save FilePVLastSignState: filePath not set") 129 } 130 jsonBytes, err := tmjson.MarshalIndent(lss, "", " ") 131 if err != nil { 132 panic(err) 133 } 134 err = tempfile.WriteFileAtomic(outFile, jsonBytes, 0o600) 135 if err != nil { 136 panic(err) 137 } 138 } 139 140 //------------------------------------------------------------------------------- 141 142 // FilePV implements PrivValidator using data persisted to disk 143 // to prevent double signing. 144 // NOTE: the directories containing pv.Key.filePath and pv.LastSignState.filePath must already exist. 145 // It includes the LastSignature and LastSignBytes so we don't lose the signature 146 // if the process crashes after signing but before the resulting consensus message is processed. 147 type FilePV struct { 148 Key FilePVKey 149 LastSignState FilePVLastSignState 150 } 151 152 // GenFilePV generates a new validator with randomly generated private key 153 // and sets the filePaths, but does not call Save(). 154 func GenFilePV(keyFilePath, stateFilePath string) *FilePV { 155 privKey := ed25519.GenPrivKey() 156 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 // LoadFilePV loads a FilePV from the filePaths. The FilePV handles double 172 // signing prevention by persisting data to the stateFilePath. If either file path 173 // does not exist, the program will exit. 174 func LoadFilePV(keyFilePath, stateFilePath string) *FilePV { 175 return loadFilePV(keyFilePath, stateFilePath, true) 176 } 177 178 // LoadFilePVEmptyState loads a FilePV from the given keyFilePath, with an empty LastSignState. 179 // If the keyFilePath does not exist, the program will exit. 180 func LoadFilePVEmptyState(keyFilePath, stateFilePath string) *FilePV { 181 return loadFilePV(keyFilePath, stateFilePath, false) 182 } 183 184 // If loadState is true, we load from the stateFilePath. Otherwise, we use an empty LastSignState. 185 func loadFilePV(keyFilePath, stateFilePath string, loadState bool) *FilePV { 186 keyJSONBytes, err := os.ReadFile(keyFilePath) 187 if err != nil { 188 tmos.Exit(err.Error()) 189 } 190 pvKey := FilePVKey{} 191 err = tmjson.Unmarshal(keyJSONBytes, &pvKey) 192 if err != nil { 193 tmos.Exit(fmt.Sprintf("Error reading PrivValidator key from %v: %v\n", keyFilePath, err)) 194 } 195 196 // overwrite pubkey and address for convenience 197 pvKey.PubKey = pvKey.PrivKey.PubKey() 198 pvKey.Address = pvKey.PubKey.Address() 199 pvKey.filePath = keyFilePath 200 201 pvState := FilePVLastSignState{} 202 203 if loadState { 204 stateJSONBytes, err := os.ReadFile(stateFilePath) 205 if err != nil { 206 tmos.Exit(err.Error()) 207 } 208 err = tmjson.Unmarshal(stateJSONBytes, &pvState) 209 if err != nil { 210 tmos.Exit(fmt.Sprintf("Error reading PrivValidator state from %v: %v\n", stateFilePath, err)) 211 } 212 } 213 214 pvState.filePath = stateFilePath 215 216 return &FilePV{ 217 Key: pvKey, 218 LastSignState: pvState, 219 } 220 } 221 222 // LoadOrGenFilePV loads a FilePV from the given filePaths 223 // or else generates a new one and saves it to the filePaths. 224 func LoadOrGenFilePV(keyFilePath, stateFilePath string) *FilePV { 225 var pv *FilePV 226 if tmos.FileExists(keyFilePath) { 227 pv = LoadFilePV(keyFilePath, stateFilePath) 228 } else { 229 pv = GenFilePV(keyFilePath, stateFilePath) 230 pv.Save() 231 } 232 return pv 233 } 234 235 // GetAddress returns the address of the validator. 236 // Implements PrivValidator. 237 func (pv *FilePV) GetAddress() types.Address { 238 return pv.Key.Address 239 } 240 241 // GetPubKey returns the public key of the validator. 242 // Implements PrivValidator. 243 func (pv *FilePV) GetPubKey() (crypto.PubKey, error) { 244 return pv.Key.PubKey, nil 245 } 246 247 // SignVote signs a canonical representation of the vote, along with the 248 // chainID. Implements PrivValidator. 249 func (pv *FilePV) SignVote(chainID string, vote *tmproto.Vote) error { 250 if err := pv.signVote(chainID, vote); err != nil { 251 return fmt.Errorf("error signing vote: %v", err) 252 } 253 return nil 254 } 255 256 // SignProposal signs a canonical representation of the proposal, along with 257 // the chainID. Implements PrivValidator. 258 func (pv *FilePV) SignProposal(chainID string, proposal *tmproto.Proposal) error { 259 if err := pv.signProposal(chainID, proposal); err != nil { 260 return fmt.Errorf("error signing proposal: %v", err) 261 } 262 return nil 263 } 264 265 // Save persists the FilePV to disk. 266 func (pv *FilePV) Save() { 267 pv.Key.Save() 268 pv.LastSignState.Save() 269 } 270 271 // Reset resets all fields in the FilePV. 272 // NOTE: Unsafe! 273 func (pv *FilePV) Reset() { 274 var sig []byte 275 pv.LastSignState.Height = 0 276 pv.LastSignState.Round = 0 277 pv.LastSignState.Step = 0 278 pv.LastSignState.Signature = sig 279 pv.LastSignState.SignBytes = nil 280 pv.Save() 281 } 282 283 // String returns a string representation of the FilePV. 284 func (pv *FilePV) String() string { 285 return fmt.Sprintf( 286 "PrivValidator{%v LH:%v, LR:%v, LS:%v}", 287 pv.GetAddress(), 288 pv.LastSignState.Height, 289 pv.LastSignState.Round, 290 pv.LastSignState.Step, 291 ) 292 } 293 294 //------------------------------------------------------------------------------------ 295 296 // signVote checks if the vote is good to sign and sets the vote signature. 297 // It may need to set the timestamp as well if the vote is otherwise the same as 298 // a previously signed vote (ie. we crashed after signing but before the vote hit the WAL). 299 func (pv *FilePV) signVote(chainID string, vote *tmproto.Vote) error { 300 height, round, step := vote.Height, vote.Round, voteToStep(vote) 301 302 lss := pv.LastSignState 303 304 _, err := lss.CheckHRS(height, round, step) 305 if err != nil { 306 return err 307 } 308 309 signBytes := types.VoteSignBytes(chainID, vote) 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 *tmproto.Proposal) error { 325 height, round, step := proposal.Height, proposal.Round, stepPropose 326 327 lss := pv.LastSignState 328 329 _, err := lss.CheckHRS(height, round, step) 330 if err != nil { 331 return err 332 } 333 334 signBytes := types.ProposalSignBytes(chainID, proposal) 335 336 // It passed the checks. Sign the proposal 337 sig, err := pv.Key.PrivKey.Sign(signBytes) 338 if err != nil { 339 return err 340 } 341 pv.saveSigned(height, round, step, signBytes, sig) 342 proposal.Signature = sig 343 return nil 344 } 345 346 // Persist height/round/step and signature 347 func (pv *FilePV) saveSigned(height int64, round int32, step int8, 348 signBytes []byte, sig []byte, 349 ) { 350 pv.LastSignState.Height = height 351 pv.LastSignState.Round = round 352 pv.LastSignState.Step = step 353 pv.LastSignState.Signature = sig 354 pv.LastSignState.SignBytes = signBytes 355 pv.LastSignState.Save() 356 }