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.

421 lines
13 KiB

Close and retry a RemoteSigner on err (#2923) * Close and recreate a RemoteSigner on err * Update changelog * Address Anton's comments / suggestions: - update changelog - restart TCPVal - shut down on `ErrUnexpectedResponse` * re-init remote signer client with fresh connection if Ping fails - add/update TODOs in secret connection - rename tcp.go -> tcp_client.go, same with ipc to clarify their purpose * account for `conn returned by waitConnection can be `nil` - also add TODO about RemoteSigner conn field * Tests for retrying: IPC / TCP - shorter info log on success - set conn and use it in tests to close conn * Tests for retrying: IPC / TCP - shorter info log on success - set conn and use it in tests to close conn - add rwmutex for conn field in IPC * comments and doc.go * fix ipc tests. fixes #2677 * use constants for tests * cleanup some error statements * fixes #2784, race in tests * remove print statement * minor fixes from review * update comment on sts spec * cosmetics * p2p/conn: add failing tests * p2p/conn: make SecretConnection thread safe * changelog * IPCVal signer refactor - use a .reset() method - don't use embedded RemoteSignerClient - guard RemoteSignerClient with mutex - drop the .conn - expose Close() on RemoteSignerClient * apply IPCVal refactor to TCPVal * remove mtx from RemoteSignerClient * consolidate IPCVal and TCPVal, fixes #3104 - done in tcp_client.go - now called SocketVal - takes a listener in the constructor - make tcpListener and unixListener contain all the differences * delete ipc files * introduce unix and tcp dialer for RemoteSigner * rename files - drop tcp_ prefix - rename priv_validator.go to file.go * bring back listener options * fix node * fix priv_val_server * fix node test * minor cleanup and comments
6 years ago
Close and retry a RemoteSigner on err (#2923) * Close and recreate a RemoteSigner on err * Update changelog * Address Anton's comments / suggestions: - update changelog - restart TCPVal - shut down on `ErrUnexpectedResponse` * re-init remote signer client with fresh connection if Ping fails - add/update TODOs in secret connection - rename tcp.go -> tcp_client.go, same with ipc to clarify their purpose * account for `conn returned by waitConnection can be `nil` - also add TODO about RemoteSigner conn field * Tests for retrying: IPC / TCP - shorter info log on success - set conn and use it in tests to close conn * Tests for retrying: IPC / TCP - shorter info log on success - set conn and use it in tests to close conn - add rwmutex for conn field in IPC * comments and doc.go * fix ipc tests. fixes #2677 * use constants for tests * cleanup some error statements * fixes #2784, race in tests * remove print statement * minor fixes from review * update comment on sts spec * cosmetics * p2p/conn: add failing tests * p2p/conn: make SecretConnection thread safe * changelog * IPCVal signer refactor - use a .reset() method - don't use embedded RemoteSignerClient - guard RemoteSignerClient with mutex - drop the .conn - expose Close() on RemoteSignerClient * apply IPCVal refactor to TCPVal * remove mtx from RemoteSignerClient * consolidate IPCVal and TCPVal, fixes #3104 - done in tcp_client.go - now called SocketVal - takes a listener in the constructor - make tcpListener and unixListener contain all the differences * delete ipc files * introduce unix and tcp dialer for RemoteSigner * rename files - drop tcp_ prefix - rename priv_validator.go to file.go * bring back listener options * fix node * fix priv_val_server * fix node test * minor cleanup and comments
6 years ago
WAL: better errors and new fail point (#3246) * privval: more info in errors * wal: change Debug logs to Info * wal: log and return error on corrupted wal instead of panicing * fail: Exit right away instead of sending interupt * consensus: FAIL before handling our own vote allows to replicate #3089: - run using `FAIL_TEST_INDEX=0` - delete some bytes from the end of the WAL - start normally Results in logs like: ``` I[2019-02-03|18:12:58.225] Searching for height module=consensus wal=/Users/ethanbuchman/.tendermint/data/cs.wal/wal height=1 min=0 max=0 E[2019-02-03|18:12:58.225] Error on catchup replay. Proceeding to start ConsensusState anyway module=consensus err="failed to read data: EOF" I[2019-02-03|18:12:58.225] Started node module=main nodeInfo="{ProtocolVersion:{P2P:6 Block:9 App:1} ID_:35e87e93f2e31f305b65a5517fd2102331b56002 ListenAddr:tcp://0.0.0.0:26656 Network:test-chain-J8JvJH Version:0.29.1 Channels:4020212223303800 Moniker:Ethans-MacBook-Pro.local Other:{TxIndex:on RPCAddress:tcp://0.0.0.0:26657}}" E[2019-02-03|18:12:58.226] Couldn't connect to any seeds module=p2p I[2019-02-03|18:12:59.229] Timed out module=consensus dur=998.568ms height=1 round=0 step=RoundStepNewHeight I[2019-02-03|18:12:59.230] enterNewRound(1/0). Current: 1/0/RoundStepNewHeight module=consensus height=1 round=0 I[2019-02-03|18:12:59.230] enterPropose(1/0). Current: 1/0/RoundStepNewRound module=consensus height=1 round=0 I[2019-02-03|18:12:59.230] enterPropose: Our turn to propose module=consensus height=1 round=0 proposer=AD278B7767B05D7FBEB76207024C650988FA77D5 privValidator="PrivValidator{AD278B7767B05D7FBEB76207024C650988FA77D5 LH:1, LR:0, LS:2}" E[2019-02-03|18:12:59.230] enterPropose: Error signing proposal module=consensus height=1 round=0 err="Error signing proposal: Step regression at height 1 round 0. Got 1, last step 2" I[2019-02-03|18:13:02.233] Timed out module=consensus dur=3s height=1 round=0 step=RoundStepPropose I[2019-02-03|18:13:02.233] enterPrevote(1/0). Current: 1/0/RoundStepPropose module=consensus I[2019-02-03|18:13:02.233] enterPrevote: ProposalBlock is nil module=consensus height=1 round=0 E[2019-02-03|18:13:02.234] Error signing vote module=consensus height=1 round=0 vote="Vote{0:AD278B7767B0 1/00/1(Prevote) 000000000000 000000000000 @ 2019-02-04T02:13:02.233897Z}" err="Error signing vote: Conflicting data" ``` Notice the EOF, the step regression, and the conflicting data. * wal: change errors to be DataCorruptionError * exit on corrupt WAL * fix log * fix new line
6 years ago
WAL: better errors and new fail point (#3246) * privval: more info in errors * wal: change Debug logs to Info * wal: log and return error on corrupted wal instead of panicing * fail: Exit right away instead of sending interupt * consensus: FAIL before handling our own vote allows to replicate #3089: - run using `FAIL_TEST_INDEX=0` - delete some bytes from the end of the WAL - start normally Results in logs like: ``` I[2019-02-03|18:12:58.225] Searching for height module=consensus wal=/Users/ethanbuchman/.tendermint/data/cs.wal/wal height=1 min=0 max=0 E[2019-02-03|18:12:58.225] Error on catchup replay. Proceeding to start ConsensusState anyway module=consensus err="failed to read data: EOF" I[2019-02-03|18:12:58.225] Started node module=main nodeInfo="{ProtocolVersion:{P2P:6 Block:9 App:1} ID_:35e87e93f2e31f305b65a5517fd2102331b56002 ListenAddr:tcp://0.0.0.0:26656 Network:test-chain-J8JvJH Version:0.29.1 Channels:4020212223303800 Moniker:Ethans-MacBook-Pro.local Other:{TxIndex:on RPCAddress:tcp://0.0.0.0:26657}}" E[2019-02-03|18:12:58.226] Couldn't connect to any seeds module=p2p I[2019-02-03|18:12:59.229] Timed out module=consensus dur=998.568ms height=1 round=0 step=RoundStepNewHeight I[2019-02-03|18:12:59.230] enterNewRound(1/0). Current: 1/0/RoundStepNewHeight module=consensus height=1 round=0 I[2019-02-03|18:12:59.230] enterPropose(1/0). Current: 1/0/RoundStepNewRound module=consensus height=1 round=0 I[2019-02-03|18:12:59.230] enterPropose: Our turn to propose module=consensus height=1 round=0 proposer=AD278B7767B05D7FBEB76207024C650988FA77D5 privValidator="PrivValidator{AD278B7767B05D7FBEB76207024C650988FA77D5 LH:1, LR:0, LS:2}" E[2019-02-03|18:12:59.230] enterPropose: Error signing proposal module=consensus height=1 round=0 err="Error signing proposal: Step regression at height 1 round 0. Got 1, last step 2" I[2019-02-03|18:13:02.233] Timed out module=consensus dur=3s height=1 round=0 step=RoundStepPropose I[2019-02-03|18:13:02.233] enterPrevote(1/0). Current: 1/0/RoundStepPropose module=consensus I[2019-02-03|18:13:02.233] enterPrevote: ProposalBlock is nil module=consensus height=1 round=0 E[2019-02-03|18:13:02.234] Error signing vote module=consensus height=1 round=0 vote="Vote{0:AD278B7767B0 1/00/1(Prevote) 000000000000 000000000000 @ 2019-02-04T02:13:02.233897Z}" err="Error signing vote: Conflicting data" ``` Notice the EOF, the step regression, and the conflicting data. * wal: change errors to be DataCorruptionError * exit on corrupt WAL * fix log * fix new line
6 years ago
WAL: better errors and new fail point (#3246) * privval: more info in errors * wal: change Debug logs to Info * wal: log and return error on corrupted wal instead of panicing * fail: Exit right away instead of sending interupt * consensus: FAIL before handling our own vote allows to replicate #3089: - run using `FAIL_TEST_INDEX=0` - delete some bytes from the end of the WAL - start normally Results in logs like: ``` I[2019-02-03|18:12:58.225] Searching for height module=consensus wal=/Users/ethanbuchman/.tendermint/data/cs.wal/wal height=1 min=0 max=0 E[2019-02-03|18:12:58.225] Error on catchup replay. Proceeding to start ConsensusState anyway module=consensus err="failed to read data: EOF" I[2019-02-03|18:12:58.225] Started node module=main nodeInfo="{ProtocolVersion:{P2P:6 Block:9 App:1} ID_:35e87e93f2e31f305b65a5517fd2102331b56002 ListenAddr:tcp://0.0.0.0:26656 Network:test-chain-J8JvJH Version:0.29.1 Channels:4020212223303800 Moniker:Ethans-MacBook-Pro.local Other:{TxIndex:on RPCAddress:tcp://0.0.0.0:26657}}" E[2019-02-03|18:12:58.226] Couldn't connect to any seeds module=p2p I[2019-02-03|18:12:59.229] Timed out module=consensus dur=998.568ms height=1 round=0 step=RoundStepNewHeight I[2019-02-03|18:12:59.230] enterNewRound(1/0). Current: 1/0/RoundStepNewHeight module=consensus height=1 round=0 I[2019-02-03|18:12:59.230] enterPropose(1/0). Current: 1/0/RoundStepNewRound module=consensus height=1 round=0 I[2019-02-03|18:12:59.230] enterPropose: Our turn to propose module=consensus height=1 round=0 proposer=AD278B7767B05D7FBEB76207024C650988FA77D5 privValidator="PrivValidator{AD278B7767B05D7FBEB76207024C650988FA77D5 LH:1, LR:0, LS:2}" E[2019-02-03|18:12:59.230] enterPropose: Error signing proposal module=consensus height=1 round=0 err="Error signing proposal: Step regression at height 1 round 0. Got 1, last step 2" I[2019-02-03|18:13:02.233] Timed out module=consensus dur=3s height=1 round=0 step=RoundStepPropose I[2019-02-03|18:13:02.233] enterPrevote(1/0). Current: 1/0/RoundStepPropose module=consensus I[2019-02-03|18:13:02.233] enterPrevote: ProposalBlock is nil module=consensus height=1 round=0 E[2019-02-03|18:13:02.234] Error signing vote module=consensus height=1 round=0 vote="Vote{0:AD278B7767B0 1/00/1(Prevote) 000000000000 000000000000 @ 2019-02-04T02:13:02.233897Z}" err="Error signing vote: Conflicting data" ``` Notice the EOF, the step regression, and the conflicting data. * wal: change errors to be DataCorruptionError * exit on corrupt WAL * fix log * fix new line
6 years ago
  1. package privval
  2. import (
  3. "bytes"
  4. "errors"
  5. "fmt"
  6. "io/ioutil"
  7. "time"
  8. "github.com/tendermint/tendermint/crypto"
  9. "github.com/tendermint/tendermint/crypto/ed25519"
  10. cmn "github.com/tendermint/tendermint/libs/common"
  11. "github.com/tendermint/tendermint/types"
  12. tmtime "github.com/tendermint/tendermint/types/time"
  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. // A vote is either stepPrevote or stepPrecommit.
  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. panic("Unknown vote type")
  30. return 0
  31. }
  32. }
  33. //-------------------------------------------------------------------------------
  34. // FilePVKey stores the immutable part of PrivValidator.
  35. type FilePVKey struct {
  36. Address types.Address `json:"address"`
  37. PubKey crypto.PubKey `json:"pub_key"`
  38. PrivKey crypto.PrivKey `json:"priv_key"`
  39. filePath string
  40. }
  41. // Save persists the FilePVKey to its filePath.
  42. func (pvKey FilePVKey) Save() {
  43. outFile := pvKey.filePath
  44. if outFile == "" {
  45. panic("Cannot save PrivValidator key: filePath not set")
  46. }
  47. jsonBytes, err := cdc.MarshalJSONIndent(pvKey, "", " ")
  48. if err != nil {
  49. panic(err)
  50. }
  51. err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600)
  52. if err != nil {
  53. panic(err)
  54. }
  55. }
  56. //-------------------------------------------------------------------------------
  57. // FilePVLastSignState stores the mutable part of PrivValidator.
  58. type FilePVLastSignState struct {
  59. Height int64 `json:"height"`
  60. Round int `json:"round"`
  61. Step int8 `json:"step"`
  62. Signature []byte `json:"signature,omitempty"`
  63. SignBytes cmn.HexBytes `json:"signbytes,omitempty"`
  64. filePath string
  65. }
  66. // CheckHRS checks the given height, round, step (HRS) against that of the
  67. // FilePVLastSignState. It returns an error if the arguments constitute a regression,
  68. // or if they match but the SignBytes are empty.
  69. // The returned boolean indicates whether the last Signature should be reused -
  70. // it returns true if the HRS matches the arguments and the SignBytes are not empty (indicating
  71. // we have already signed for this HRS, and can reuse the existing signature).
  72. // It panics if the HRS matches the arguments, there's a SignBytes, but no Signature.
  73. func (lss *FilePVLastSignState) CheckHRS(height int64, round int, step int8) (bool, error) {
  74. if lss.Height > height {
  75. return false, fmt.Errorf("Height regression. Got %v, last height %v", height, lss.Height)
  76. }
  77. if lss.Height == height {
  78. if lss.Round > round {
  79. return false, fmt.Errorf("Round regression at height %v. Got %v, last round %v", height, round, lss.Round)
  80. }
  81. if lss.Round == round {
  82. if lss.Step > step {
  83. return false, fmt.Errorf("Step regression at height %v round %v. Got %v, last step %v", height, round, step, lss.Step)
  84. } else if lss.Step == step {
  85. if lss.SignBytes != nil {
  86. if lss.Signature == nil {
  87. panic("pv: Signature is nil but SignBytes is not!")
  88. }
  89. return true, nil
  90. }
  91. return false, errors.New("No SignBytes found")
  92. }
  93. }
  94. }
  95. return false, nil
  96. }
  97. // Save persists the FilePvLastSignState to its filePath.
  98. func (lss *FilePVLastSignState) Save() {
  99. outFile := lss.filePath
  100. if outFile == "" {
  101. panic("Cannot save FilePVLastSignState: filePath not set")
  102. }
  103. jsonBytes, err := cdc.MarshalJSONIndent(lss, "", " ")
  104. if err != nil {
  105. panic(err)
  106. }
  107. err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600)
  108. if err != nil {
  109. panic(err)
  110. }
  111. }
  112. //-------------------------------------------------------------------------------
  113. // FilePV implements PrivValidator using data persisted to disk
  114. // to prevent double signing.
  115. // NOTE: the directories containing pv.Key.filePath and pv.LastSignState.filePath must already exist.
  116. // It includes the LastSignature and LastSignBytes so we don't lose the signature
  117. // if the process crashes after signing but before the resulting consensus message is processed.
  118. type FilePV struct {
  119. Key FilePVKey
  120. LastSignState FilePVLastSignState
  121. }
  122. // GenFilePV generates a new validator with randomly generated private key
  123. // and sets the filePaths, but does not call Save().
  124. func GenFilePV(keyFilePath, stateFilePath string) *FilePV {
  125. privKey := ed25519.GenPrivKey()
  126. return &FilePV{
  127. Key: FilePVKey{
  128. Address: privKey.PubKey().Address(),
  129. PubKey: privKey.PubKey(),
  130. PrivKey: privKey,
  131. filePath: keyFilePath,
  132. },
  133. LastSignState: FilePVLastSignState{
  134. Step: stepNone,
  135. filePath: stateFilePath,
  136. },
  137. }
  138. }
  139. // LoadFilePV loads a FilePV from the filePaths. The FilePV handles double
  140. // signing prevention by persisting data to the stateFilePath. If either file path
  141. // does not exist, the program will exit.
  142. func LoadFilePV(keyFilePath, stateFilePath string) *FilePV {
  143. return loadFilePV(keyFilePath, stateFilePath, true)
  144. }
  145. // LoadFilePVEmptyState loads a FilePV from the given keyFilePath, with an empty LastSignState.
  146. // If the keyFilePath does not exist, the program will exit.
  147. func LoadFilePVEmptyState(keyFilePath, stateFilePath string) *FilePV {
  148. return loadFilePV(keyFilePath, stateFilePath, false)
  149. }
  150. // If loadState is true, we load from the stateFilePath. Otherwise, we use an empty LastSignState.
  151. func loadFilePV(keyFilePath, stateFilePath string, loadState bool) *FilePV {
  152. keyJSONBytes, err := ioutil.ReadFile(keyFilePath)
  153. if err != nil {
  154. cmn.Exit(err.Error())
  155. }
  156. pvKey := FilePVKey{}
  157. err = cdc.UnmarshalJSON(keyJSONBytes, &pvKey)
  158. if err != nil {
  159. cmn.Exit(fmt.Sprintf("Error reading PrivValidator key from %v: %v\n", keyFilePath, err))
  160. }
  161. // overwrite pubkey and address for convenience
  162. pvKey.PubKey = pvKey.PrivKey.PubKey()
  163. pvKey.Address = pvKey.PubKey.Address()
  164. pvKey.filePath = keyFilePath
  165. pvState := FilePVLastSignState{}
  166. if loadState {
  167. stateJSONBytes, err := ioutil.ReadFile(stateFilePath)
  168. if err != nil {
  169. cmn.Exit(err.Error())
  170. }
  171. err = cdc.UnmarshalJSON(stateJSONBytes, &pvState)
  172. if err != nil {
  173. cmn.Exit(fmt.Sprintf("Error reading PrivValidator state from %v: %v\n", stateFilePath, err))
  174. }
  175. }
  176. pvState.filePath = stateFilePath
  177. return &FilePV{
  178. Key: pvKey,
  179. LastSignState: pvState,
  180. }
  181. }
  182. // LoadOrGenFilePV loads a FilePV from the given filePaths
  183. // or else generates a new one and saves it to the filePaths.
  184. func LoadOrGenFilePV(keyFilePath, stateFilePath string) *FilePV {
  185. var pv *FilePV
  186. if cmn.FileExists(keyFilePath) {
  187. pv = LoadFilePV(keyFilePath, stateFilePath)
  188. } else {
  189. pv = GenFilePV(keyFilePath, stateFilePath)
  190. pv.Save()
  191. }
  192. return pv
  193. }
  194. // GetAddress returns the address of the validator.
  195. // Implements PrivValidator.
  196. func (pv *FilePV) GetAddress() types.Address {
  197. return pv.Key.Address
  198. }
  199. // GetPubKey returns the public key of the validator.
  200. // Implements PrivValidator.
  201. func (pv *FilePV) GetPubKey() crypto.PubKey {
  202. return pv.Key.PubKey
  203. }
  204. // SignVote signs a canonical representation of the vote, along with the
  205. // chainID. Implements PrivValidator.
  206. func (pv *FilePV) SignVote(chainID string, vote *types.Vote) error {
  207. if err := pv.signVote(chainID, vote); err != nil {
  208. return fmt.Errorf("Error signing vote: %v", err)
  209. }
  210. return nil
  211. }
  212. // SignProposal signs a canonical representation of the proposal, along with
  213. // the chainID. Implements PrivValidator.
  214. func (pv *FilePV) SignProposal(chainID string, proposal *types.Proposal) error {
  215. if err := pv.signProposal(chainID, proposal); err != nil {
  216. return fmt.Errorf("Error signing proposal: %v", err)
  217. }
  218. return nil
  219. }
  220. // Save persists the FilePV to disk.
  221. func (pv *FilePV) Save() {
  222. pv.Key.Save()
  223. pv.LastSignState.Save()
  224. }
  225. // Reset resets all fields in the FilePV.
  226. // NOTE: Unsafe!
  227. func (pv *FilePV) Reset() {
  228. var sig []byte
  229. pv.LastSignState.Height = 0
  230. pv.LastSignState.Round = 0
  231. pv.LastSignState.Step = 0
  232. pv.LastSignState.Signature = sig
  233. pv.LastSignState.SignBytes = nil
  234. pv.Save()
  235. }
  236. // String returns a string representation of the FilePV.
  237. func (pv *FilePV) String() string {
  238. return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", pv.GetAddress(), pv.LastSignState.Height, pv.LastSignState.Round, pv.LastSignState.Step)
  239. }
  240. //------------------------------------------------------------------------------------
  241. // signVote checks if the vote is good to sign and sets the vote signature.
  242. // It may need to set the timestamp as well if the vote is otherwise the same as
  243. // a previously signed vote (ie. we crashed after signing but before the vote hit the WAL).
  244. func (pv *FilePV) signVote(chainID string, vote *types.Vote) error {
  245. height, round, step := vote.Height, vote.Round, voteToStep(vote)
  246. lss := pv.LastSignState
  247. sameHRS, err := lss.CheckHRS(height, round, step)
  248. if err != nil {
  249. return err
  250. }
  251. signBytes := vote.SignBytes(chainID)
  252. // We might crash before writing to the wal,
  253. // causing us to try to re-sign for the same HRS.
  254. // If signbytes are the same, use the last signature.
  255. // If they only differ by timestamp, use last timestamp and signature
  256. // Otherwise, return error
  257. if sameHRS {
  258. if bytes.Equal(signBytes, lss.SignBytes) {
  259. vote.Signature = lss.Signature
  260. } else if timestamp, ok := checkVotesOnlyDifferByTimestamp(lss.SignBytes, signBytes); ok {
  261. vote.Timestamp = timestamp
  262. vote.Signature = lss.Signature
  263. } else {
  264. err = fmt.Errorf("Conflicting data")
  265. }
  266. return err
  267. }
  268. // It passed the checks. Sign the vote
  269. sig, err := pv.Key.PrivKey.Sign(signBytes)
  270. if err != nil {
  271. return err
  272. }
  273. pv.saveSigned(height, round, step, signBytes, sig)
  274. vote.Signature = sig
  275. return nil
  276. }
  277. // signProposal checks if the proposal is good to sign and sets the proposal signature.
  278. // It may need to set the timestamp as well if the proposal is otherwise the same as
  279. // a previously signed proposal ie. we crashed after signing but before the proposal hit the WAL).
  280. func (pv *FilePV) signProposal(chainID string, proposal *types.Proposal) error {
  281. height, round, step := proposal.Height, proposal.Round, stepPropose
  282. lss := pv.LastSignState
  283. sameHRS, err := lss.CheckHRS(height, round, step)
  284. if err != nil {
  285. return err
  286. }
  287. signBytes := proposal.SignBytes(chainID)
  288. // We might crash before writing to the wal,
  289. // causing us to try to re-sign for the same HRS.
  290. // If signbytes are the same, use the last signature.
  291. // If they only differ by timestamp, use last timestamp and signature
  292. // Otherwise, return error
  293. if sameHRS {
  294. if bytes.Equal(signBytes, lss.SignBytes) {
  295. proposal.Signature = lss.Signature
  296. } else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(lss.SignBytes, signBytes); ok {
  297. proposal.Timestamp = timestamp
  298. proposal.Signature = lss.Signature
  299. } else {
  300. err = fmt.Errorf("Conflicting data")
  301. }
  302. return err
  303. }
  304. // It passed the checks. Sign the proposal
  305. sig, err := pv.Key.PrivKey.Sign(signBytes)
  306. if err != nil {
  307. return err
  308. }
  309. pv.saveSigned(height, round, step, signBytes, sig)
  310. proposal.Signature = sig
  311. return nil
  312. }
  313. // Persist height/round/step and signature
  314. func (pv *FilePV) saveSigned(height int64, round int, step int8,
  315. signBytes []byte, sig []byte) {
  316. pv.LastSignState.Height = height
  317. pv.LastSignState.Round = round
  318. pv.LastSignState.Step = step
  319. pv.LastSignState.Signature = sig
  320. pv.LastSignState.SignBytes = signBytes
  321. pv.LastSignState.Save()
  322. }
  323. //-----------------------------------------------------------------------------------------
  324. // returns the timestamp from the lastSignBytes.
  325. // returns true if the only difference in the votes is their timestamp.
  326. func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {
  327. var lastVote, newVote types.CanonicalVote
  328. if err := cdc.UnmarshalBinaryLengthPrefixed(lastSignBytes, &lastVote); err != nil {
  329. panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into vote: %v", err))
  330. }
  331. if err := cdc.UnmarshalBinaryLengthPrefixed(newSignBytes, &newVote); err != nil {
  332. panic(fmt.Sprintf("signBytes cannot be unmarshalled into vote: %v", err))
  333. }
  334. lastTime := lastVote.Timestamp
  335. // set the times to the same value and check equality
  336. now := tmtime.Now()
  337. lastVote.Timestamp = now
  338. newVote.Timestamp = now
  339. lastVoteBytes, _ := cdc.MarshalJSON(lastVote)
  340. newVoteBytes, _ := cdc.MarshalJSON(newVote)
  341. return lastTime, bytes.Equal(newVoteBytes, lastVoteBytes)
  342. }
  343. // returns the timestamp from the lastSignBytes.
  344. // returns true if the only difference in the proposals is their timestamp
  345. func checkProposalsOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {
  346. var lastProposal, newProposal types.CanonicalProposal
  347. if err := cdc.UnmarshalBinaryLengthPrefixed(lastSignBytes, &lastProposal); err != nil {
  348. panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into proposal: %v", err))
  349. }
  350. if err := cdc.UnmarshalBinaryLengthPrefixed(newSignBytes, &newProposal); err != nil {
  351. panic(fmt.Sprintf("signBytes cannot be unmarshalled into proposal: %v", err))
  352. }
  353. lastTime := lastProposal.Timestamp
  354. // set the times to the same value and check equality
  355. now := tmtime.Now()
  356. lastProposal.Timestamp = now
  357. newProposal.Timestamp = now
  358. lastProposalBytes, _ := cdc.MarshalBinaryLengthPrefixed(lastProposal)
  359. newProposalBytes, _ := cdc.MarshalBinaryLengthPrefixed(newProposal)
  360. return lastTime, bytes.Equal(newProposalBytes, lastProposalBytes)
  361. }