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.

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