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.

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