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.

355 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. "github.com/tendermint/tendermint/types"
  11. cmn "github.com/tendermint/tmlibs/common"
  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. pv.filePath = filePath
  84. return pv
  85. }
  86. // LoadOrGenFilePV loads a FilePV from the given filePath
  87. // or else generates a new one and saves it to the filePath.
  88. func LoadOrGenFilePV(filePath string) *FilePV {
  89. var pv *FilePV
  90. if cmn.FileExists(filePath) {
  91. pv = LoadFilePV(filePath)
  92. } else {
  93. pv = GenFilePV(filePath)
  94. pv.Save()
  95. }
  96. return pv
  97. }
  98. // Save persists the FilePV to disk.
  99. func (pv *FilePV) Save() {
  100. pv.mtx.Lock()
  101. defer pv.mtx.Unlock()
  102. pv.save()
  103. }
  104. func (pv *FilePV) save() {
  105. outFile := pv.filePath
  106. if outFile == "" {
  107. panic("Cannot save PrivValidator: filePath not set")
  108. }
  109. jsonBytes, err := cdc.MarshalJSONIndent(pv, "", " ")
  110. if err != nil {
  111. panic(err)
  112. }
  113. err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600)
  114. if err != nil {
  115. panic(err)
  116. }
  117. }
  118. // Reset resets all fields in the FilePV.
  119. // NOTE: Unsafe!
  120. func (pv *FilePV) Reset() {
  121. var sig crypto.Signature
  122. pv.LastHeight = 0
  123. pv.LastRound = 0
  124. pv.LastStep = 0
  125. pv.LastSignature = sig
  126. pv.LastSignBytes = nil
  127. pv.Save()
  128. }
  129. // SignVote signs a canonical representation of the vote, along with the
  130. // chainID. Implements PrivValidator.
  131. func (pv *FilePV) SignVote(chainID string, vote *types.Vote) error {
  132. pv.mtx.Lock()
  133. defer pv.mtx.Unlock()
  134. if err := pv.signVote(chainID, vote); err != nil {
  135. return errors.New(cmn.Fmt("Error signing vote: %v", err))
  136. }
  137. return nil
  138. }
  139. // SignProposal signs a canonical representation of the proposal, along with
  140. // the chainID. Implements PrivValidator.
  141. func (pv *FilePV) SignProposal(chainID string, proposal *types.Proposal) error {
  142. pv.mtx.Lock()
  143. defer pv.mtx.Unlock()
  144. if err := pv.signProposal(chainID, proposal); err != nil {
  145. return fmt.Errorf("Error signing proposal: %v", err)
  146. }
  147. return nil
  148. }
  149. // returns error if HRS regression or no LastSignBytes. returns true if HRS is unchanged
  150. func (pv *FilePV) checkHRS(height int64, round int, step int8) (bool, error) {
  151. if pv.LastHeight > height {
  152. return false, errors.New("Height regression")
  153. }
  154. if pv.LastHeight == height {
  155. if pv.LastRound > round {
  156. return false, errors.New("Round regression")
  157. }
  158. if pv.LastRound == round {
  159. if pv.LastStep > step {
  160. return false, errors.New("Step regression")
  161. } else if pv.LastStep == step {
  162. if pv.LastSignBytes != nil {
  163. if pv.LastSignature == nil {
  164. panic("pv: LastSignature is nil but LastSignBytes is not!")
  165. }
  166. return true, nil
  167. }
  168. return false, errors.New("No LastSignature found")
  169. }
  170. }
  171. }
  172. return false, nil
  173. }
  174. // signVote checks if the vote is good to sign and sets the vote signature.
  175. // It may need to set the timestamp as well if the vote is otherwise the same as
  176. // a previously signed vote (ie. we crashed after signing but before the vote hit the WAL).
  177. func (pv *FilePV) signVote(chainID string, vote *types.Vote) error {
  178. height, round, step := vote.Height, vote.Round, voteToStep(vote)
  179. signBytes := vote.SignBytes(chainID)
  180. sameHRS, err := pv.checkHRS(height, round, step)
  181. if err != nil {
  182. return err
  183. }
  184. // We might crash before writing to the wal,
  185. // causing us to try to re-sign for the same HRS.
  186. // If signbytes are the same, use the last signature.
  187. // If they only differ by timestamp, use last timestamp and signature
  188. // Otherwise, return error
  189. if sameHRS {
  190. if bytes.Equal(signBytes, pv.LastSignBytes) {
  191. vote.Signature = pv.LastSignature
  192. } else if timestamp, ok := checkVotesOnlyDifferByTimestamp(pv.LastSignBytes, signBytes); ok {
  193. vote.Timestamp = timestamp
  194. vote.Signature = pv.LastSignature
  195. } else {
  196. err = fmt.Errorf("Conflicting data")
  197. }
  198. return err
  199. }
  200. // It passed the checks. Sign the vote
  201. sig, err := pv.PrivKey.Sign(signBytes)
  202. if err != nil {
  203. return err
  204. }
  205. pv.saveSigned(height, round, step, signBytes, sig)
  206. vote.Signature = sig
  207. return nil
  208. }
  209. // signProposal checks if the proposal is good to sign and sets the proposal signature.
  210. // It may need to set the timestamp as well if the proposal is otherwise the same as
  211. // a previously signed proposal ie. we crashed after signing but before the proposal hit the WAL).
  212. func (pv *FilePV) signProposal(chainID string, proposal *types.Proposal) error {
  213. height, round, step := proposal.Height, proposal.Round, stepPropose
  214. signBytes := proposal.SignBytes(chainID)
  215. sameHRS, err := pv.checkHRS(height, round, step)
  216. if err != nil {
  217. return err
  218. }
  219. // We might crash before writing to the wal,
  220. // causing us to try to re-sign for the same HRS.
  221. // If signbytes are the same, use the last signature.
  222. // If they only differ by timestamp, use last timestamp and signature
  223. // Otherwise, return error
  224. if sameHRS {
  225. if bytes.Equal(signBytes, pv.LastSignBytes) {
  226. proposal.Signature = pv.LastSignature
  227. } else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(pv.LastSignBytes, signBytes); ok {
  228. proposal.Timestamp = timestamp
  229. proposal.Signature = pv.LastSignature
  230. } else {
  231. err = fmt.Errorf("Conflicting data")
  232. }
  233. return err
  234. }
  235. // It passed the checks. Sign the proposal
  236. sig, err := pv.PrivKey.Sign(signBytes)
  237. if err != nil {
  238. return err
  239. }
  240. pv.saveSigned(height, round, step, signBytes, sig)
  241. proposal.Signature = sig
  242. return nil
  243. }
  244. // Persist height/round/step and signature
  245. func (pv *FilePV) saveSigned(height int64, round int, step int8,
  246. signBytes []byte, sig crypto.Signature) {
  247. pv.LastHeight = height
  248. pv.LastRound = round
  249. pv.LastStep = step
  250. pv.LastSignature = sig
  251. pv.LastSignBytes = signBytes
  252. pv.save()
  253. }
  254. // SignHeartbeat signs a canonical representation of the heartbeat, along with the chainID.
  255. // Implements PrivValidator.
  256. func (pv *FilePV) SignHeartbeat(chainID string, heartbeat *types.Heartbeat) error {
  257. pv.mtx.Lock()
  258. defer pv.mtx.Unlock()
  259. sig, err:= pv.PrivKey.Sign(heartbeat.SignBytes(chainID))
  260. if err != nil {
  261. return err
  262. }
  263. heartbeat.Signature = sig
  264. return nil
  265. }
  266. // String returns a string representation of the FilePV.
  267. func (pv *FilePV) String() string {
  268. return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", pv.GetAddress(), pv.LastHeight, pv.LastRound, pv.LastStep)
  269. }
  270. //-------------------------------------
  271. // returns the timestamp from the lastSignBytes.
  272. // returns true if the only difference in the votes is their timestamp.
  273. func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {
  274. var lastVote, newVote types.CanonicalJSONVote
  275. if err := cdc.UnmarshalJSON(lastSignBytes, &lastVote); err != nil {
  276. panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into vote: %v", err))
  277. }
  278. if err := cdc.UnmarshalJSON(newSignBytes, &newVote); err != nil {
  279. panic(fmt.Sprintf("signBytes cannot be unmarshalled into vote: %v", err))
  280. }
  281. lastTime, err := time.Parse(types.TimeFormat, lastVote.Timestamp)
  282. if err != nil {
  283. panic(err)
  284. }
  285. // set the times to the same value and check equality
  286. now := types.CanonicalTime(time.Now())
  287. lastVote.Timestamp = now
  288. newVote.Timestamp = now
  289. lastVoteBytes, _ := cdc.MarshalJSON(lastVote)
  290. newVoteBytes, _ := cdc.MarshalJSON(newVote)
  291. return lastTime, bytes.Equal(newVoteBytes, lastVoteBytes)
  292. }
  293. // returns the timestamp from the lastSignBytes.
  294. // returns true if the only difference in the proposals is their timestamp
  295. func checkProposalsOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {
  296. var lastProposal, newProposal types.CanonicalJSONProposal
  297. if err := cdc.UnmarshalJSON(lastSignBytes, &lastProposal); err != nil {
  298. panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into proposal: %v", err))
  299. }
  300. if err := cdc.UnmarshalJSON(newSignBytes, &newProposal); err != nil {
  301. panic(fmt.Sprintf("signBytes cannot be unmarshalled into proposal: %v", err))
  302. }
  303. lastTime, err := time.Parse(types.TimeFormat, lastProposal.Timestamp)
  304. if err != nil {
  305. panic(err)
  306. }
  307. // set the times to the same value and check equality
  308. now := types.CanonicalTime(time.Now())
  309. lastProposal.Timestamp = now
  310. newProposal.Timestamp = now
  311. lastProposalBytes, _ := cdc.MarshalJSON(lastProposal)
  312. newProposalBytes, _ := cdc.MarshalJSON(newProposal)
  313. return lastTime, bytes.Equal(newProposalBytes, lastProposalBytes)
  314. }