You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

359 lines
10 KiB

  1. package privval
  2. import (
  3. "bytes"
  4. "errors"
  5. "fmt"
  6. "io/ioutil"
  7. "sync"
  8. "time"
  9. "github.com/tendermint/tendermint/crypto"
  10. cmn "github.com/tendermint/tendermint/libs/common"
  11. "github.com/tendermint/tendermint/types"
  12. )
  13. // TODO: type ?
  14. const (
  15. stepNone int8 = 0 // Used to distinguish the initial state
  16. stepPropose int8 = 1
  17. stepPrevote int8 = 2
  18. stepPrecommit int8 = 3
  19. )
  20. func voteToStep(vote *types.Vote) int8 {
  21. switch vote.Type {
  22. case types.VoteTypePrevote:
  23. return stepPrevote
  24. case types.VoteTypePrecommit:
  25. return stepPrecommit
  26. default:
  27. cmn.PanicSanity("Unknown vote type")
  28. return 0
  29. }
  30. }
  31. // FilePV implements PrivValidator using data persisted to disk
  32. // to prevent double signing.
  33. // NOTE: the directory containing the pv.filePath must already exist.
  34. type FilePV struct {
  35. Address types.Address `json:"address"`
  36. PubKey crypto.PubKey `json:"pub_key"`
  37. LastHeight int64 `json:"last_height"`
  38. LastRound int `json:"last_round"`
  39. LastStep int8 `json:"last_step"`
  40. LastSignature crypto.Signature `json:"last_signature,omitempty"` // so we dont lose signatures XXX Why would we lose signatures?
  41. LastSignBytes cmn.HexBytes `json:"last_signbytes,omitempty"` // so we dont lose signatures XXX Why would we lose signatures?
  42. PrivKey crypto.PrivKey `json:"priv_key"`
  43. // For persistence.
  44. // Overloaded for testing.
  45. filePath string
  46. mtx sync.Mutex
  47. }
  48. // GetAddress returns the address of the validator.
  49. // Implements PrivValidator.
  50. func (pv *FilePV) GetAddress() types.Address {
  51. return pv.Address
  52. }
  53. // GetPubKey returns the public key of the validator.
  54. // Implements PrivValidator.
  55. func (pv *FilePV) GetPubKey() crypto.PubKey {
  56. return pv.PubKey
  57. }
  58. // GenFilePV generates a new validator with randomly generated private key
  59. // and sets the filePath, but does not call Save().
  60. func GenFilePV(filePath string) *FilePV {
  61. privKey := crypto.GenPrivKeyEd25519()
  62. return &FilePV{
  63. Address: privKey.PubKey().Address(),
  64. PubKey: privKey.PubKey(),
  65. PrivKey: privKey,
  66. LastStep: stepNone,
  67. filePath: filePath,
  68. }
  69. }
  70. // LoadFilePV loads a FilePV from the filePath. The FilePV handles double
  71. // signing prevention by persisting data to the filePath. If the filePath does
  72. // not exist, the FilePV must be created manually and saved.
  73. func LoadFilePV(filePath string) *FilePV {
  74. pvJSONBytes, err := ioutil.ReadFile(filePath)
  75. if err != nil {
  76. cmn.Exit(err.Error())
  77. }
  78. pv := &FilePV{}
  79. err = cdc.UnmarshalJSON(pvJSONBytes, &pv)
  80. if err != nil {
  81. cmn.Exit(cmn.Fmt("Error reading PrivValidator from %v: %v\n", filePath, err))
  82. }
  83. // overwrite pubkey and address for convenience
  84. pv.PubKey = pv.PrivKey.PubKey()
  85. pv.Address = pv.PubKey.Address()
  86. pv.filePath = filePath
  87. return pv
  88. }
  89. // LoadOrGenFilePV loads a FilePV from the given filePath
  90. // or else generates a new one and saves it to the filePath.
  91. func LoadOrGenFilePV(filePath string) *FilePV {
  92. var pv *FilePV
  93. if cmn.FileExists(filePath) {
  94. pv = LoadFilePV(filePath)
  95. } else {
  96. pv = GenFilePV(filePath)
  97. pv.Save()
  98. }
  99. return pv
  100. }
  101. // Save persists the FilePV to disk.
  102. func (pv *FilePV) Save() {
  103. pv.mtx.Lock()
  104. defer pv.mtx.Unlock()
  105. pv.save()
  106. }
  107. func (pv *FilePV) save() {
  108. outFile := pv.filePath
  109. if outFile == "" {
  110. panic("Cannot save PrivValidator: filePath not set")
  111. }
  112. jsonBytes, err := cdc.MarshalJSONIndent(pv, "", " ")
  113. if err != nil {
  114. panic(err)
  115. }
  116. err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600)
  117. if err != nil {
  118. panic(err)
  119. }
  120. }
  121. // Reset resets all fields in the FilePV.
  122. // NOTE: Unsafe!
  123. func (pv *FilePV) Reset() {
  124. var sig crypto.Signature
  125. pv.LastHeight = 0
  126. pv.LastRound = 0
  127. pv.LastStep = 0
  128. pv.LastSignature = sig
  129. pv.LastSignBytes = nil
  130. pv.Save()
  131. }
  132. // SignVote signs a canonical representation of the vote, along with the
  133. // chainID. Implements PrivValidator.
  134. func (pv *FilePV) SignVote(chainID string, vote *types.Vote) error {
  135. pv.mtx.Lock()
  136. defer pv.mtx.Unlock()
  137. if err := pv.signVote(chainID, vote); err != nil {
  138. return errors.New(cmn.Fmt("Error signing vote: %v", err))
  139. }
  140. return nil
  141. }
  142. // SignProposal signs a canonical representation of the proposal, along with
  143. // the chainID. Implements PrivValidator.
  144. func (pv *FilePV) SignProposal(chainID string, proposal *types.Proposal) error {
  145. pv.mtx.Lock()
  146. defer pv.mtx.Unlock()
  147. if err := pv.signProposal(chainID, proposal); err != nil {
  148. return fmt.Errorf("Error signing proposal: %v", err)
  149. }
  150. return nil
  151. }
  152. // returns error if HRS regression or no LastSignBytes. returns true if HRS is unchanged
  153. func (pv *FilePV) checkHRS(height int64, round int, step int8) (bool, error) {
  154. if pv.LastHeight > height {
  155. return false, errors.New("Height regression")
  156. }
  157. if pv.LastHeight == height {
  158. if pv.LastRound > round {
  159. return false, errors.New("Round regression")
  160. }
  161. if pv.LastRound == round {
  162. if pv.LastStep > step {
  163. return false, errors.New("Step regression")
  164. } else if pv.LastStep == step {
  165. if pv.LastSignBytes != nil {
  166. if pv.LastSignature == nil {
  167. panic("pv: LastSignature is nil but LastSignBytes is not!")
  168. }
  169. return true, nil
  170. }
  171. return false, errors.New("No LastSignature found")
  172. }
  173. }
  174. }
  175. return false, nil
  176. }
  177. // signVote checks if the vote is good to sign and sets the vote signature.
  178. // It may need to set the timestamp as well if the vote is otherwise the same as
  179. // a previously signed vote (ie. we crashed after signing but before the vote hit the WAL).
  180. func (pv *FilePV) signVote(chainID string, vote *types.Vote) error {
  181. height, round, step := vote.Height, vote.Round, voteToStep(vote)
  182. signBytes := vote.SignBytes(chainID)
  183. sameHRS, err := pv.checkHRS(height, round, step)
  184. if err != nil {
  185. return err
  186. }
  187. // We might crash before writing to the wal,
  188. // causing us to try to re-sign for the same HRS.
  189. // If signbytes are the same, use the last signature.
  190. // If they only differ by timestamp, use last timestamp and signature
  191. // Otherwise, return error
  192. if sameHRS {
  193. if bytes.Equal(signBytes, pv.LastSignBytes) {
  194. vote.Signature = pv.LastSignature
  195. } else if timestamp, ok := checkVotesOnlyDifferByTimestamp(pv.LastSignBytes, signBytes); ok {
  196. vote.Timestamp = timestamp
  197. vote.Signature = pv.LastSignature
  198. } else {
  199. err = fmt.Errorf("Conflicting data")
  200. }
  201. return err
  202. }
  203. // It passed the checks. Sign the vote
  204. sig, err := pv.PrivKey.Sign(signBytes)
  205. if err != nil {
  206. return err
  207. }
  208. pv.saveSigned(height, round, step, signBytes, sig)
  209. vote.Signature = sig
  210. return nil
  211. }
  212. // signProposal checks if the proposal is good to sign and sets the proposal signature.
  213. // It may need to set the timestamp as well if the proposal is otherwise the same as
  214. // a previously signed proposal ie. we crashed after signing but before the proposal hit the WAL).
  215. func (pv *FilePV) signProposal(chainID string, proposal *types.Proposal) error {
  216. height, round, step := proposal.Height, proposal.Round, stepPropose
  217. signBytes := proposal.SignBytes(chainID)
  218. sameHRS, err := pv.checkHRS(height, round, step)
  219. if err != nil {
  220. return err
  221. }
  222. // We might crash before writing to the wal,
  223. // causing us to try to re-sign for the same HRS.
  224. // If signbytes are the same, use the last signature.
  225. // If they only differ by timestamp, use last timestamp and signature
  226. // Otherwise, return error
  227. if sameHRS {
  228. if bytes.Equal(signBytes, pv.LastSignBytes) {
  229. proposal.Signature = pv.LastSignature
  230. } else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(pv.LastSignBytes, signBytes); ok {
  231. proposal.Timestamp = timestamp
  232. proposal.Signature = pv.LastSignature
  233. } else {
  234. err = fmt.Errorf("Conflicting data")
  235. }
  236. return err
  237. }
  238. // It passed the checks. Sign the proposal
  239. sig, err := pv.PrivKey.Sign(signBytes)
  240. if err != nil {
  241. return err
  242. }
  243. pv.saveSigned(height, round, step, signBytes, sig)
  244. proposal.Signature = sig
  245. return nil
  246. }
  247. // Persist height/round/step and signature
  248. func (pv *FilePV) saveSigned(height int64, round int, step int8,
  249. signBytes []byte, sig crypto.Signature) {
  250. pv.LastHeight = height
  251. pv.LastRound = round
  252. pv.LastStep = step
  253. pv.LastSignature = sig
  254. pv.LastSignBytes = signBytes
  255. pv.save()
  256. }
  257. // SignHeartbeat signs a canonical representation of the heartbeat, along with the chainID.
  258. // Implements PrivValidator.
  259. func (pv *FilePV) SignHeartbeat(chainID string, heartbeat *types.Heartbeat) error {
  260. pv.mtx.Lock()
  261. defer pv.mtx.Unlock()
  262. sig, err := pv.PrivKey.Sign(heartbeat.SignBytes(chainID))
  263. if err != nil {
  264. return err
  265. }
  266. heartbeat.Signature = sig
  267. return nil
  268. }
  269. // String returns a string representation of the FilePV.
  270. func (pv *FilePV) String() string {
  271. return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", pv.GetAddress(), pv.LastHeight, pv.LastRound, pv.LastStep)
  272. }
  273. //-------------------------------------
  274. // returns the timestamp from the lastSignBytes.
  275. // returns true if the only difference in the votes is their timestamp.
  276. func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {
  277. var lastVote, newVote types.CanonicalJSONVote
  278. if err := cdc.UnmarshalJSON(lastSignBytes, &lastVote); err != nil {
  279. panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into vote: %v", err))
  280. }
  281. if err := cdc.UnmarshalJSON(newSignBytes, &newVote); err != nil {
  282. panic(fmt.Sprintf("signBytes cannot be unmarshalled into vote: %v", err))
  283. }
  284. lastTime, err := time.Parse(types.TimeFormat, lastVote.Timestamp)
  285. if err != nil {
  286. panic(err)
  287. }
  288. // set the times to the same value and check equality
  289. now := types.CanonicalTime(time.Now())
  290. lastVote.Timestamp = now
  291. newVote.Timestamp = now
  292. lastVoteBytes, _ := cdc.MarshalJSON(lastVote)
  293. newVoteBytes, _ := cdc.MarshalJSON(newVote)
  294. return lastTime, bytes.Equal(newVoteBytes, lastVoteBytes)
  295. }
  296. // returns the timestamp from the lastSignBytes.
  297. // returns true if the only difference in the proposals is their timestamp
  298. func checkProposalsOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {
  299. var lastProposal, newProposal types.CanonicalJSONProposal
  300. if err := cdc.UnmarshalJSON(lastSignBytes, &lastProposal); err != nil {
  301. panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into proposal: %v", err))
  302. }
  303. if err := cdc.UnmarshalJSON(newSignBytes, &newProposal); err != nil {
  304. panic(fmt.Sprintf("signBytes cannot be unmarshalled into proposal: %v", err))
  305. }
  306. lastTime, err := time.Parse(types.TimeFormat, lastProposal.Timestamp)
  307. if err != nil {
  308. panic(err)
  309. }
  310. // set the times to the same value and check equality
  311. now := types.CanonicalTime(time.Now())
  312. lastProposal.Timestamp = now
  313. newProposal.Timestamp = now
  314. lastProposalBytes, _ := cdc.MarshalJSON(lastProposal)
  315. newProposalBytes, _ := cdc.MarshalJSON(newProposal)
  316. return lastTime, bytes.Equal(newProposalBytes, lastProposalBytes)
  317. }