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.

420 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. }
  31. }
  32. //-------------------------------------------------------------------------------
  33. // FilePVKey stores the immutable part of PrivValidator.
  34. type FilePVKey struct {
  35. Address types.Address `json:"address"`
  36. PubKey crypto.PubKey `json:"pub_key"`
  37. PrivKey crypto.PrivKey `json:"priv_key"`
  38. filePath string
  39. }
  40. // Save persists the FilePVKey to its filePath.
  41. func (pvKey FilePVKey) Save() {
  42. outFile := pvKey.filePath
  43. if outFile == "" {
  44. panic("Cannot save PrivValidator key: filePath not set")
  45. }
  46. jsonBytes, err := cdc.MarshalJSONIndent(pvKey, "", " ")
  47. if err != nil {
  48. panic(err)
  49. }
  50. err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600)
  51. if err != nil {
  52. panic(err)
  53. }
  54. }
  55. //-------------------------------------------------------------------------------
  56. // FilePVLastSignState stores the mutable part of PrivValidator.
  57. type FilePVLastSignState struct {
  58. Height int64 `json:"height"`
  59. Round int `json:"round"`
  60. Step int8 `json:"step"`
  61. Signature []byte `json:"signature,omitempty"`
  62. SignBytes cmn.HexBytes `json:"signbytes,omitempty"`
  63. filePath string
  64. }
  65. // CheckHRS checks the given height, round, step (HRS) against that of the
  66. // FilePVLastSignState. It returns an error if the arguments constitute a regression,
  67. // or if they match but the SignBytes are empty.
  68. // The returned boolean indicates whether the last Signature should be reused -
  69. // it returns true if the HRS matches the arguments and the SignBytes are not empty (indicating
  70. // we have already signed for this HRS, and can reuse the existing signature).
  71. // It panics if the HRS matches the arguments, there's a SignBytes, but no Signature.
  72. func (lss *FilePVLastSignState) CheckHRS(height int64, round int, step int8) (bool, error) {
  73. if lss.Height > height {
  74. return false, fmt.Errorf("Height regression. Got %v, last height %v", height, lss.Height)
  75. }
  76. if lss.Height == height {
  77. if lss.Round > round {
  78. return false, fmt.Errorf("Round regression at height %v. Got %v, last round %v", height, round, lss.Round)
  79. }
  80. if lss.Round == round {
  81. if lss.Step > step {
  82. return false, fmt.Errorf("Step regression at height %v round %v. Got %v, last step %v", height, round, step, lss.Step)
  83. } else if lss.Step == step {
  84. if lss.SignBytes != nil {
  85. if lss.Signature == nil {
  86. panic("pv: Signature is nil but SignBytes is not!")
  87. }
  88. return true, nil
  89. }
  90. return false, errors.New("No SignBytes found")
  91. }
  92. }
  93. }
  94. return false, nil
  95. }
  96. // Save persists the FilePvLastSignState to its filePath.
  97. func (lss *FilePVLastSignState) Save() {
  98. outFile := lss.filePath
  99. if outFile == "" {
  100. panic("Cannot save FilePVLastSignState: filePath not set")
  101. }
  102. jsonBytes, err := cdc.MarshalJSONIndent(lss, "", " ")
  103. if err != nil {
  104. panic(err)
  105. }
  106. err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600)
  107. if err != nil {
  108. panic(err)
  109. }
  110. }
  111. //-------------------------------------------------------------------------------
  112. // FilePV implements PrivValidator using data persisted to disk
  113. // to prevent double signing.
  114. // NOTE: the directories containing pv.Key.filePath and pv.LastSignState.filePath must already exist.
  115. // It includes the LastSignature and LastSignBytes so we don't lose the signature
  116. // if the process crashes after signing but before the resulting consensus message is processed.
  117. type FilePV struct {
  118. Key FilePVKey
  119. LastSignState FilePVLastSignState
  120. }
  121. // GenFilePV generates a new validator with randomly generated private key
  122. // and sets the filePaths, but does not call Save().
  123. func GenFilePV(keyFilePath, stateFilePath string) *FilePV {
  124. privKey := ed25519.GenPrivKey()
  125. return &FilePV{
  126. Key: FilePVKey{
  127. Address: privKey.PubKey().Address(),
  128. PubKey: privKey.PubKey(),
  129. PrivKey: privKey,
  130. filePath: keyFilePath,
  131. },
  132. LastSignState: FilePVLastSignState{
  133. Step: stepNone,
  134. filePath: stateFilePath,
  135. },
  136. }
  137. }
  138. // LoadFilePV loads a FilePV from the filePaths. The FilePV handles double
  139. // signing prevention by persisting data to the stateFilePath. If either file path
  140. // does not exist, the program will exit.
  141. func LoadFilePV(keyFilePath, stateFilePath string) *FilePV {
  142. return loadFilePV(keyFilePath, stateFilePath, true)
  143. }
  144. // LoadFilePVEmptyState loads a FilePV from the given keyFilePath, with an empty LastSignState.
  145. // If the keyFilePath does not exist, the program will exit.
  146. func LoadFilePVEmptyState(keyFilePath, stateFilePath string) *FilePV {
  147. return loadFilePV(keyFilePath, stateFilePath, false)
  148. }
  149. // If loadState is true, we load from the stateFilePath. Otherwise, we use an empty LastSignState.
  150. func loadFilePV(keyFilePath, stateFilePath string, loadState bool) *FilePV {
  151. keyJSONBytes, err := ioutil.ReadFile(keyFilePath)
  152. if err != nil {
  153. cmn.Exit(err.Error())
  154. }
  155. pvKey := FilePVKey{}
  156. err = cdc.UnmarshalJSON(keyJSONBytes, &pvKey)
  157. if err != nil {
  158. cmn.Exit(fmt.Sprintf("Error reading PrivValidator key from %v: %v\n", keyFilePath, err))
  159. }
  160. // overwrite pubkey and address for convenience
  161. pvKey.PubKey = pvKey.PrivKey.PubKey()
  162. pvKey.Address = pvKey.PubKey.Address()
  163. pvKey.filePath = keyFilePath
  164. pvState := FilePVLastSignState{}
  165. if loadState {
  166. stateJSONBytes, err := ioutil.ReadFile(stateFilePath)
  167. if err != nil {
  168. cmn.Exit(err.Error())
  169. }
  170. err = cdc.UnmarshalJSON(stateJSONBytes, &pvState)
  171. if err != nil {
  172. cmn.Exit(fmt.Sprintf("Error reading PrivValidator state from %v: %v\n", stateFilePath, err))
  173. }
  174. }
  175. pvState.filePath = stateFilePath
  176. return &FilePV{
  177. Key: pvKey,
  178. LastSignState: pvState,
  179. }
  180. }
  181. // LoadOrGenFilePV loads a FilePV from the given filePaths
  182. // or else generates a new one and saves it to the filePaths.
  183. func LoadOrGenFilePV(keyFilePath, stateFilePath string) *FilePV {
  184. var pv *FilePV
  185. if cmn.FileExists(keyFilePath) {
  186. pv = LoadFilePV(keyFilePath, stateFilePath)
  187. } else {
  188. pv = GenFilePV(keyFilePath, stateFilePath)
  189. pv.Save()
  190. }
  191. return pv
  192. }
  193. // GetAddress returns the address of the validator.
  194. // Implements PrivValidator.
  195. func (pv *FilePV) GetAddress() types.Address {
  196. return pv.Key.Address
  197. }
  198. // GetPubKey returns the public key of the validator.
  199. // Implements PrivValidator.
  200. func (pv *FilePV) GetPubKey() crypto.PubKey {
  201. return pv.Key.PubKey
  202. }
  203. // SignVote signs a canonical representation of the vote, along with the
  204. // chainID. Implements PrivValidator.
  205. func (pv *FilePV) SignVote(chainID string, vote *types.Vote) error {
  206. if err := pv.signVote(chainID, vote); err != nil {
  207. return fmt.Errorf("Error signing vote: %v", err)
  208. }
  209. return nil
  210. }
  211. // SignProposal signs a canonical representation of the proposal, along with
  212. // the chainID. Implements PrivValidator.
  213. func (pv *FilePV) SignProposal(chainID string, proposal *types.Proposal) error {
  214. if err := pv.signProposal(chainID, proposal); err != nil {
  215. return fmt.Errorf("Error signing proposal: %v", err)
  216. }
  217. return nil
  218. }
  219. // Save persists the FilePV to disk.
  220. func (pv *FilePV) Save() {
  221. pv.Key.Save()
  222. pv.LastSignState.Save()
  223. }
  224. // Reset resets all fields in the FilePV.
  225. // NOTE: Unsafe!
  226. func (pv *FilePV) Reset() {
  227. var sig []byte
  228. pv.LastSignState.Height = 0
  229. pv.LastSignState.Round = 0
  230. pv.LastSignState.Step = 0
  231. pv.LastSignState.Signature = sig
  232. pv.LastSignState.SignBytes = nil
  233. pv.Save()
  234. }
  235. // String returns a string representation of the FilePV.
  236. func (pv *FilePV) String() string {
  237. return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", pv.GetAddress(), pv.LastSignState.Height, pv.LastSignState.Round, pv.LastSignState.Step)
  238. }
  239. //------------------------------------------------------------------------------------
  240. // signVote checks if the vote is good to sign and sets the vote signature.
  241. // It may need to set the timestamp as well if the vote is otherwise the same as
  242. // a previously signed vote (ie. we crashed after signing but before the vote hit the WAL).
  243. func (pv *FilePV) signVote(chainID string, vote *types.Vote) error {
  244. height, round, step := vote.Height, vote.Round, voteToStep(vote)
  245. lss := pv.LastSignState
  246. sameHRS, err := lss.CheckHRS(height, round, step)
  247. if err != nil {
  248. return err
  249. }
  250. signBytes := vote.SignBytes(chainID)
  251. // We might crash before writing to the wal,
  252. // causing us to try to re-sign for the same HRS.
  253. // If signbytes are the same, use the last signature.
  254. // If they only differ by timestamp, use last timestamp and signature
  255. // Otherwise, return error
  256. if sameHRS {
  257. if bytes.Equal(signBytes, lss.SignBytes) {
  258. vote.Signature = lss.Signature
  259. } else if timestamp, ok := checkVotesOnlyDifferByTimestamp(lss.SignBytes, signBytes); ok {
  260. vote.Timestamp = timestamp
  261. vote.Signature = lss.Signature
  262. } else {
  263. err = fmt.Errorf("Conflicting data")
  264. }
  265. return err
  266. }
  267. // It passed the checks. Sign the vote
  268. sig, err := pv.Key.PrivKey.Sign(signBytes)
  269. if err != nil {
  270. return err
  271. }
  272. pv.saveSigned(height, round, step, signBytes, sig)
  273. vote.Signature = sig
  274. return nil
  275. }
  276. // signProposal checks if the proposal is good to sign and sets the proposal signature.
  277. // It may need to set the timestamp as well if the proposal is otherwise the same as
  278. // a previously signed proposal ie. we crashed after signing but before the proposal hit the WAL).
  279. func (pv *FilePV) signProposal(chainID string, proposal *types.Proposal) error {
  280. height, round, step := proposal.Height, proposal.Round, stepPropose
  281. lss := pv.LastSignState
  282. sameHRS, err := lss.CheckHRS(height, round, step)
  283. if err != nil {
  284. return err
  285. }
  286. signBytes := proposal.SignBytes(chainID)
  287. // We might crash before writing to the wal,
  288. // causing us to try to re-sign for the same HRS.
  289. // If signbytes are the same, use the last signature.
  290. // If they only differ by timestamp, use last timestamp and signature
  291. // Otherwise, return error
  292. if sameHRS {
  293. if bytes.Equal(signBytes, lss.SignBytes) {
  294. proposal.Signature = lss.Signature
  295. } else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(lss.SignBytes, signBytes); ok {
  296. proposal.Timestamp = timestamp
  297. proposal.Signature = lss.Signature
  298. } else {
  299. err = fmt.Errorf("Conflicting data")
  300. }
  301. return err
  302. }
  303. // It passed the checks. Sign the proposal
  304. sig, err := pv.Key.PrivKey.Sign(signBytes)
  305. if err != nil {
  306. return err
  307. }
  308. pv.saveSigned(height, round, step, signBytes, sig)
  309. proposal.Signature = sig
  310. return nil
  311. }
  312. // Persist height/round/step and signature
  313. func (pv *FilePV) saveSigned(height int64, round int, step int8,
  314. signBytes []byte, sig []byte) {
  315. pv.LastSignState.Height = height
  316. pv.LastSignState.Round = round
  317. pv.LastSignState.Step = step
  318. pv.LastSignState.Signature = sig
  319. pv.LastSignState.SignBytes = signBytes
  320. pv.LastSignState.Save()
  321. }
  322. //-----------------------------------------------------------------------------------------
  323. // returns the timestamp from the lastSignBytes.
  324. // returns true if the only difference in the votes is their timestamp.
  325. func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {
  326. var lastVote, newVote types.CanonicalVote
  327. if err := cdc.UnmarshalBinaryLengthPrefixed(lastSignBytes, &lastVote); err != nil {
  328. panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into vote: %v", err))
  329. }
  330. if err := cdc.UnmarshalBinaryLengthPrefixed(newSignBytes, &newVote); err != nil {
  331. panic(fmt.Sprintf("signBytes cannot be unmarshalled into vote: %v", err))
  332. }
  333. lastTime := lastVote.Timestamp
  334. // set the times to the same value and check equality
  335. now := tmtime.Now()
  336. lastVote.Timestamp = now
  337. newVote.Timestamp = now
  338. lastVoteBytes, _ := cdc.MarshalJSON(lastVote)
  339. newVoteBytes, _ := cdc.MarshalJSON(newVote)
  340. return lastTime, bytes.Equal(newVoteBytes, lastVoteBytes)
  341. }
  342. // returns the timestamp from the lastSignBytes.
  343. // returns true if the only difference in the proposals is their timestamp
  344. func checkProposalsOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {
  345. var lastProposal, newProposal types.CanonicalProposal
  346. if err := cdc.UnmarshalBinaryLengthPrefixed(lastSignBytes, &lastProposal); err != nil {
  347. panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into proposal: %v", err))
  348. }
  349. if err := cdc.UnmarshalBinaryLengthPrefixed(newSignBytes, &newProposal); err != nil {
  350. panic(fmt.Sprintf("signBytes cannot be unmarshalled into proposal: %v", err))
  351. }
  352. lastTime := lastProposal.Timestamp
  353. // set the times to the same value and check equality
  354. now := tmtime.Now()
  355. lastProposal.Timestamp = now
  356. newProposal.Timestamp = now
  357. lastProposalBytes, _ := cdc.MarshalBinaryLengthPrefixed(lastProposal)
  358. newProposalBytes, _ := cdc.MarshalBinaryLengthPrefixed(newProposal)
  359. return lastTime, bytes.Equal(newProposalBytes, lastProposalBytes)
  360. }