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.

474 lines
14 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
5 years ago
privval: do not use old proposal timestamp (#7621) After #7592, @cmwaters noticed that the logic for re-using old timestamps for proposals may not work with proposer-based timestamps. This change removes the logic to re-use old proposal timestamps since it is no longer correct. Two proposals with different timestamps can no longer be treated as equivalent. Signing a proposal that only differs by timestamp in the new algorithm can be thought of as roughly equivalent to signing a proposal that only differs by `BlockID` in the old scheme. I also investigated the codebase and checked for any place we updated a timestamp using the pattern `(Timestamp = |Timestamp: )` and saw no additional places where we are updating the timestamp of a proposal message. Here is the output of that search: ``` privval/file.go:372: vote.Timestamp = timestamp privval/file.go:453: lastVote.Timestamp = now privval/file.go:454: newVote.Timestamp = now internal/test/factory/commit.go:25: Timestamp: now, internal/test/factory/vote.go:34: Timestamp: time, internal/consensus/state.go:2261: Timestamp: cs.voteTime(), internal/consensus/state.go:2286: vote.Timestamp = v.Timestamp light/detector.go:414: ev.Timestamp = common.Time light/detector.go:418: ev.Timestamp = trusted.Time types/block.go:616: Timestamp: ts, types/block.go:725: Timestamp: cs.Timestamp, types/block.go:736: cs.Timestamp = csp.Timestamp types/block.go:800: Timestamp: commitSig.Timestamp, types/evidence.go:84: Timestamp: blockTime, types/evidence.go:190: dve.Timestamp = evidenceTime types/evidence.go:202: Timestamp: dve.Timestamp, types/evidence.go:228: Timestamp: pb.Timestamp, types/evidence.go:382: Timestamp: %v}#%X`, types/evidence.go:491: l.Timestamp = evidenceTime types/evidence.go:517: Timestamp: l.Timestamp, types/evidence.go:546: Timestamp: lpb.Timestamp, types/evidence.go:722: Timestamp: time, types/vote.go:80: Timestamp: vote.Timestamp, types/vote.go:216: Timestamp: vote.Timestamp, types/vote.go:240: vote.Timestamp = pv.Timestamp types/test_util.go:27: Timestamp: now, types/proposal.go:44: Timestamp: tmtime.Now(), types/proposal.go:132: pb.Timestamp = p.Timestamp types/proposal.go:157: p.Timestamp = pp.Timestamp types/canonical.go:49: Timestamp: proposal.Timestamp, types/canonical.go:62: Timestamp: vote.Timestamp, test/e2e/runner/evidence.go:186: Timestamp: evTime, ```
2 years ago
privval: do not use old proposal timestamp (#7621) After #7592, @cmwaters noticed that the logic for re-using old timestamps for proposals may not work with proposer-based timestamps. This change removes the logic to re-use old proposal timestamps since it is no longer correct. Two proposals with different timestamps can no longer be treated as equivalent. Signing a proposal that only differs by timestamp in the new algorithm can be thought of as roughly equivalent to signing a proposal that only differs by `BlockID` in the old scheme. I also investigated the codebase and checked for any place we updated a timestamp using the pattern `(Timestamp = |Timestamp: )` and saw no additional places where we are updating the timestamp of a proposal message. Here is the output of that search: ``` privval/file.go:372: vote.Timestamp = timestamp privval/file.go:453: lastVote.Timestamp = now privval/file.go:454: newVote.Timestamp = now internal/test/factory/commit.go:25: Timestamp: now, internal/test/factory/vote.go:34: Timestamp: time, internal/consensus/state.go:2261: Timestamp: cs.voteTime(), internal/consensus/state.go:2286: vote.Timestamp = v.Timestamp light/detector.go:414: ev.Timestamp = common.Time light/detector.go:418: ev.Timestamp = trusted.Time types/block.go:616: Timestamp: ts, types/block.go:725: Timestamp: cs.Timestamp, types/block.go:736: cs.Timestamp = csp.Timestamp types/block.go:800: Timestamp: commitSig.Timestamp, types/evidence.go:84: Timestamp: blockTime, types/evidence.go:190: dve.Timestamp = evidenceTime types/evidence.go:202: Timestamp: dve.Timestamp, types/evidence.go:228: Timestamp: pb.Timestamp, types/evidence.go:382: Timestamp: %v}#%X`, types/evidence.go:491: l.Timestamp = evidenceTime types/evidence.go:517: Timestamp: l.Timestamp, types/evidence.go:546: Timestamp: lpb.Timestamp, types/evidence.go:722: Timestamp: time, types/vote.go:80: Timestamp: vote.Timestamp, types/vote.go:216: Timestamp: vote.Timestamp, types/vote.go:240: vote.Timestamp = pv.Timestamp types/test_util.go:27: Timestamp: now, types/proposal.go:44: Timestamp: tmtime.Now(), types/proposal.go:132: pb.Timestamp = p.Timestamp types/proposal.go:157: p.Timestamp = pp.Timestamp types/canonical.go:49: Timestamp: proposal.Timestamp, types/canonical.go:62: Timestamp: vote.Timestamp, test/e2e/runner/evidence.go:186: Timestamp: evTime, ```
2 years ago
privval: improve Remote Signer implementation (#3351) This issue is related to #3107 This is a first renaming/refactoring step before reworking and removing heartbeats. As discussed with @Liamsi , we preferred to go for a couple of independent and separate PRs to simplify review work. The changes: Help to clarify the relation between the validator and remote signer endpoints Differentiate between timeouts and deadlines Prepare to encapsulate networking related code behind RemoteSigner in the next PR My intention is to separate and encapsulate the "network related" code from the actual signer. SignerRemote ---(uses/contains)--> SignerValidatorEndpoint <--(connects to)--> SignerServiceEndpoint ---> SignerService (future.. not here yet but would like to decouple too) All reconnection/heartbeat/whatever code goes in the endpoints. Signer[Remote/Service] do not need to know about that. I agree Endpoint may not be the perfect name. I tried to find something "Go-ish" enough. It is a common name in go-kit, kubernetes, etc. Right now: SignerValidatorEndpoint: handles the listener contains SignerRemote Implements the PrivValidator interface connects and sets a connection object in a contained SignerRemote delegates PrivValidator some calls to SignerRemote which in turn uses the conn object that was set externally SignerRemote: Implements the PrivValidator interface read/writes from a connection object directly handles heartbeats SignerServiceEndpoint: Does most things in a single place delegates to a PrivValidator IIRC. * cleanup * Refactoring step 1 * Refactoring step 2 * move messages to another file * mark for future work / next steps * mark deprecated classes in docs * Fix linter problems * additional linter fixes
5 years ago
privval: improve Remote Signer implementation (#3351) This issue is related to #3107 This is a first renaming/refactoring step before reworking and removing heartbeats. As discussed with @Liamsi , we preferred to go for a couple of independent and separate PRs to simplify review work. The changes: Help to clarify the relation between the validator and remote signer endpoints Differentiate between timeouts and deadlines Prepare to encapsulate networking related code behind RemoteSigner in the next PR My intention is to separate and encapsulate the "network related" code from the actual signer. SignerRemote ---(uses/contains)--> SignerValidatorEndpoint <--(connects to)--> SignerServiceEndpoint ---> SignerService (future.. not here yet but would like to decouple too) All reconnection/heartbeat/whatever code goes in the endpoints. Signer[Remote/Service] do not need to know about that. I agree Endpoint may not be the perfect name. I tried to find something "Go-ish" enough. It is a common name in go-kit, kubernetes, etc. Right now: SignerValidatorEndpoint: handles the listener contains SignerRemote Implements the PrivValidator interface connects and sets a connection object in a contained SignerRemote delegates PrivValidator some calls to SignerRemote which in turn uses the conn object that was set externally SignerRemote: Implements the PrivValidator interface read/writes from a connection object directly handles heartbeats SignerServiceEndpoint: Does most things in a single place delegates to a PrivValidator IIRC. * cleanup * Refactoring step 1 * Refactoring step 2 * move messages to another file * mark for future work / next steps * mark deprecated classes in docs * Fix linter problems * additional linter fixes
5 years ago
privval: improve Remote Signer implementation (#3351) This issue is related to #3107 This is a first renaming/refactoring step before reworking and removing heartbeats. As discussed with @Liamsi , we preferred to go for a couple of independent and separate PRs to simplify review work. The changes: Help to clarify the relation between the validator and remote signer endpoints Differentiate between timeouts and deadlines Prepare to encapsulate networking related code behind RemoteSigner in the next PR My intention is to separate and encapsulate the "network related" code from the actual signer. SignerRemote ---(uses/contains)--> SignerValidatorEndpoint <--(connects to)--> SignerServiceEndpoint ---> SignerService (future.. not here yet but would like to decouple too) All reconnection/heartbeat/whatever code goes in the endpoints. Signer[Remote/Service] do not need to know about that. I agree Endpoint may not be the perfect name. I tried to find something "Go-ish" enough. It is a common name in go-kit, kubernetes, etc. Right now: SignerValidatorEndpoint: handles the listener contains SignerRemote Implements the PrivValidator interface connects and sets a connection object in a contained SignerRemote delegates PrivValidator some calls to SignerRemote which in turn uses the conn object that was set externally SignerRemote: Implements the PrivValidator interface read/writes from a connection object directly handles heartbeats SignerServiceEndpoint: Does most things in a single place delegates to a PrivValidator IIRC. * cleanup * Refactoring step 1 * Refactoring step 2 * move messages to another file * mark for future work / next steps * mark deprecated classes in docs * Fix linter problems * additional linter fixes
5 years ago
privval: do not use old proposal timestamp (#7621) After #7592, @cmwaters noticed that the logic for re-using old timestamps for proposals may not work with proposer-based timestamps. This change removes the logic to re-use old proposal timestamps since it is no longer correct. Two proposals with different timestamps can no longer be treated as equivalent. Signing a proposal that only differs by timestamp in the new algorithm can be thought of as roughly equivalent to signing a proposal that only differs by `BlockID` in the old scheme. I also investigated the codebase and checked for any place we updated a timestamp using the pattern `(Timestamp = |Timestamp: )` and saw no additional places where we are updating the timestamp of a proposal message. Here is the output of that search: ``` privval/file.go:372: vote.Timestamp = timestamp privval/file.go:453: lastVote.Timestamp = now privval/file.go:454: newVote.Timestamp = now internal/test/factory/commit.go:25: Timestamp: now, internal/test/factory/vote.go:34: Timestamp: time, internal/consensus/state.go:2261: Timestamp: cs.voteTime(), internal/consensus/state.go:2286: vote.Timestamp = v.Timestamp light/detector.go:414: ev.Timestamp = common.Time light/detector.go:418: ev.Timestamp = trusted.Time types/block.go:616: Timestamp: ts, types/block.go:725: Timestamp: cs.Timestamp, types/block.go:736: cs.Timestamp = csp.Timestamp types/block.go:800: Timestamp: commitSig.Timestamp, types/evidence.go:84: Timestamp: blockTime, types/evidence.go:190: dve.Timestamp = evidenceTime types/evidence.go:202: Timestamp: dve.Timestamp, types/evidence.go:228: Timestamp: pb.Timestamp, types/evidence.go:382: Timestamp: %v}#%X`, types/evidence.go:491: l.Timestamp = evidenceTime types/evidence.go:517: Timestamp: l.Timestamp, types/evidence.go:546: Timestamp: lpb.Timestamp, types/evidence.go:722: Timestamp: time, types/vote.go:80: Timestamp: vote.Timestamp, types/vote.go:216: Timestamp: vote.Timestamp, types/vote.go:240: vote.Timestamp = pv.Timestamp types/test_util.go:27: Timestamp: now, types/proposal.go:44: Timestamp: tmtime.Now(), types/proposal.go:132: pb.Timestamp = p.Timestamp types/proposal.go:157: p.Timestamp = pp.Timestamp types/canonical.go:49: Timestamp: proposal.Timestamp, types/canonical.go:62: Timestamp: vote.Timestamp, test/e2e/runner/evidence.go:186: Timestamp: evTime, ```
2 years ago
privval: do not use old proposal timestamp (#7621) After #7592, @cmwaters noticed that the logic for re-using old timestamps for proposals may not work with proposer-based timestamps. This change removes the logic to re-use old proposal timestamps since it is no longer correct. Two proposals with different timestamps can no longer be treated as equivalent. Signing a proposal that only differs by timestamp in the new algorithm can be thought of as roughly equivalent to signing a proposal that only differs by `BlockID` in the old scheme. I also investigated the codebase and checked for any place we updated a timestamp using the pattern `(Timestamp = |Timestamp: )` and saw no additional places where we are updating the timestamp of a proposal message. Here is the output of that search: ``` privval/file.go:372: vote.Timestamp = timestamp privval/file.go:453: lastVote.Timestamp = now privval/file.go:454: newVote.Timestamp = now internal/test/factory/commit.go:25: Timestamp: now, internal/test/factory/vote.go:34: Timestamp: time, internal/consensus/state.go:2261: Timestamp: cs.voteTime(), internal/consensus/state.go:2286: vote.Timestamp = v.Timestamp light/detector.go:414: ev.Timestamp = common.Time light/detector.go:418: ev.Timestamp = trusted.Time types/block.go:616: Timestamp: ts, types/block.go:725: Timestamp: cs.Timestamp, types/block.go:736: cs.Timestamp = csp.Timestamp types/block.go:800: Timestamp: commitSig.Timestamp, types/evidence.go:84: Timestamp: blockTime, types/evidence.go:190: dve.Timestamp = evidenceTime types/evidence.go:202: Timestamp: dve.Timestamp, types/evidence.go:228: Timestamp: pb.Timestamp, types/evidence.go:382: Timestamp: %v}#%X`, types/evidence.go:491: l.Timestamp = evidenceTime types/evidence.go:517: Timestamp: l.Timestamp, types/evidence.go:546: Timestamp: lpb.Timestamp, types/evidence.go:722: Timestamp: time, types/vote.go:80: Timestamp: vote.Timestamp, types/vote.go:216: Timestamp: vote.Timestamp, types/vote.go:240: vote.Timestamp = pv.Timestamp types/test_util.go:27: Timestamp: now, types/proposal.go:44: Timestamp: tmtime.Now(), types/proposal.go:132: pb.Timestamp = p.Timestamp types/proposal.go:157: p.Timestamp = pp.Timestamp types/canonical.go:49: Timestamp: proposal.Timestamp, types/canonical.go:62: Timestamp: vote.Timestamp, test/e2e/runner/evidence.go:186: Timestamp: evTime, ```
2 years ago
privval: do not use old proposal timestamp (#7621) After #7592, @cmwaters noticed that the logic for re-using old timestamps for proposals may not work with proposer-based timestamps. This change removes the logic to re-use old proposal timestamps since it is no longer correct. Two proposals with different timestamps can no longer be treated as equivalent. Signing a proposal that only differs by timestamp in the new algorithm can be thought of as roughly equivalent to signing a proposal that only differs by `BlockID` in the old scheme. I also investigated the codebase and checked for any place we updated a timestamp using the pattern `(Timestamp = |Timestamp: )` and saw no additional places where we are updating the timestamp of a proposal message. Here is the output of that search: ``` privval/file.go:372: vote.Timestamp = timestamp privval/file.go:453: lastVote.Timestamp = now privval/file.go:454: newVote.Timestamp = now internal/test/factory/commit.go:25: Timestamp: now, internal/test/factory/vote.go:34: Timestamp: time, internal/consensus/state.go:2261: Timestamp: cs.voteTime(), internal/consensus/state.go:2286: vote.Timestamp = v.Timestamp light/detector.go:414: ev.Timestamp = common.Time light/detector.go:418: ev.Timestamp = trusted.Time types/block.go:616: Timestamp: ts, types/block.go:725: Timestamp: cs.Timestamp, types/block.go:736: cs.Timestamp = csp.Timestamp types/block.go:800: Timestamp: commitSig.Timestamp, types/evidence.go:84: Timestamp: blockTime, types/evidence.go:190: dve.Timestamp = evidenceTime types/evidence.go:202: Timestamp: dve.Timestamp, types/evidence.go:228: Timestamp: pb.Timestamp, types/evidence.go:382: Timestamp: %v}#%X`, types/evidence.go:491: l.Timestamp = evidenceTime types/evidence.go:517: Timestamp: l.Timestamp, types/evidence.go:546: Timestamp: lpb.Timestamp, types/evidence.go:722: Timestamp: time, types/vote.go:80: Timestamp: vote.Timestamp, types/vote.go:216: Timestamp: vote.Timestamp, types/vote.go:240: vote.Timestamp = pv.Timestamp types/test_util.go:27: Timestamp: now, types/proposal.go:44: Timestamp: tmtime.Now(), types/proposal.go:132: pb.Timestamp = p.Timestamp types/proposal.go:157: p.Timestamp = pp.Timestamp types/canonical.go:49: Timestamp: proposal.Timestamp, types/canonical.go:62: Timestamp: vote.Timestamp, test/e2e/runner/evidence.go:186: Timestamp: evTime, ```
2 years ago
privval: do not use old proposal timestamp (#7621) After #7592, @cmwaters noticed that the logic for re-using old timestamps for proposals may not work with proposer-based timestamps. This change removes the logic to re-use old proposal timestamps since it is no longer correct. Two proposals with different timestamps can no longer be treated as equivalent. Signing a proposal that only differs by timestamp in the new algorithm can be thought of as roughly equivalent to signing a proposal that only differs by `BlockID` in the old scheme. I also investigated the codebase and checked for any place we updated a timestamp using the pattern `(Timestamp = |Timestamp: )` and saw no additional places where we are updating the timestamp of a proposal message. Here is the output of that search: ``` privval/file.go:372: vote.Timestamp = timestamp privval/file.go:453: lastVote.Timestamp = now privval/file.go:454: newVote.Timestamp = now internal/test/factory/commit.go:25: Timestamp: now, internal/test/factory/vote.go:34: Timestamp: time, internal/consensus/state.go:2261: Timestamp: cs.voteTime(), internal/consensus/state.go:2286: vote.Timestamp = v.Timestamp light/detector.go:414: ev.Timestamp = common.Time light/detector.go:418: ev.Timestamp = trusted.Time types/block.go:616: Timestamp: ts, types/block.go:725: Timestamp: cs.Timestamp, types/block.go:736: cs.Timestamp = csp.Timestamp types/block.go:800: Timestamp: commitSig.Timestamp, types/evidence.go:84: Timestamp: blockTime, types/evidence.go:190: dve.Timestamp = evidenceTime types/evidence.go:202: Timestamp: dve.Timestamp, types/evidence.go:228: Timestamp: pb.Timestamp, types/evidence.go:382: Timestamp: %v}#%X`, types/evidence.go:491: l.Timestamp = evidenceTime types/evidence.go:517: Timestamp: l.Timestamp, types/evidence.go:546: Timestamp: lpb.Timestamp, types/evidence.go:722: Timestamp: time, types/vote.go:80: Timestamp: vote.Timestamp, types/vote.go:216: Timestamp: vote.Timestamp, types/vote.go:240: vote.Timestamp = pv.Timestamp types/test_util.go:27: Timestamp: now, types/proposal.go:44: Timestamp: tmtime.Now(), types/proposal.go:132: pb.Timestamp = p.Timestamp types/proposal.go:157: p.Timestamp = pp.Timestamp types/canonical.go:49: Timestamp: proposal.Timestamp, types/canonical.go:62: Timestamp: vote.Timestamp, test/e2e/runner/evidence.go:186: Timestamp: evTime, ```
2 years ago
  1. package privval
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "os"
  9. "time"
  10. "github.com/gogo/protobuf/proto"
  11. "github.com/tendermint/tendermint/crypto"
  12. "github.com/tendermint/tendermint/crypto/ed25519"
  13. "github.com/tendermint/tendermint/crypto/secp256k1"
  14. "github.com/tendermint/tendermint/internal/jsontypes"
  15. "github.com/tendermint/tendermint/internal/libs/protoio"
  16. "github.com/tendermint/tendermint/internal/libs/tempfile"
  17. tmbytes "github.com/tendermint/tendermint/libs/bytes"
  18. tmos "github.com/tendermint/tendermint/libs/os"
  19. tmtime "github.com/tendermint/tendermint/libs/time"
  20. tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
  21. "github.com/tendermint/tendermint/types"
  22. )
  23. // TODO: type ?
  24. const (
  25. stepNone int8 = 0 // Used to distinguish the initial state
  26. stepPropose int8 = 1
  27. stepPrevote int8 = 2
  28. stepPrecommit int8 = 3
  29. )
  30. // A vote is either stepPrevote or stepPrecommit.
  31. func voteToStep(vote *tmproto.Vote) (int8, error) {
  32. switch vote.Type {
  33. case tmproto.PrevoteType:
  34. return stepPrevote, nil
  35. case tmproto.PrecommitType:
  36. return stepPrecommit, nil
  37. default:
  38. return 0, fmt.Errorf("unknown vote type: %v", vote.Type)
  39. }
  40. }
  41. //-------------------------------------------------------------------------------
  42. // FilePVKey stores the immutable part of PrivValidator.
  43. type FilePVKey struct {
  44. Address types.Address
  45. PubKey crypto.PubKey
  46. PrivKey crypto.PrivKey
  47. filePath string
  48. }
  49. type filePVKeyJSON struct {
  50. Address types.Address `json:"address"`
  51. PubKey json.RawMessage `json:"pub_key"`
  52. PrivKey json.RawMessage `json:"priv_key"`
  53. }
  54. func (pvKey FilePVKey) MarshalJSON() ([]byte, error) {
  55. pubk, err := jsontypes.Marshal(pvKey.PubKey)
  56. if err != nil {
  57. return nil, err
  58. }
  59. privk, err := jsontypes.Marshal(pvKey.PrivKey)
  60. if err != nil {
  61. return nil, err
  62. }
  63. return json.Marshal(filePVKeyJSON{
  64. Address: pvKey.Address, PubKey: pubk, PrivKey: privk,
  65. })
  66. }
  67. func (pvKey *FilePVKey) UnmarshalJSON(data []byte) error {
  68. var key filePVKeyJSON
  69. if err := json.Unmarshal(data, &key); err != nil {
  70. return err
  71. }
  72. if err := jsontypes.Unmarshal(key.PubKey, &pvKey.PubKey); err != nil {
  73. return fmt.Errorf("decoding PubKey: %w", err)
  74. }
  75. if err := jsontypes.Unmarshal(key.PrivKey, &pvKey.PrivKey); err != nil {
  76. return fmt.Errorf("decoding PrivKey: %w", err)
  77. }
  78. pvKey.Address = key.Address
  79. return nil
  80. }
  81. // Save persists the FilePVKey to its filePath.
  82. func (pvKey FilePVKey) Save() error {
  83. outFile := pvKey.filePath
  84. if outFile == "" {
  85. return errors.New("cannot save PrivValidator key: filePath not set")
  86. }
  87. data, err := json.MarshalIndent(pvKey, "", " ")
  88. if err != nil {
  89. return err
  90. }
  91. return tempfile.WriteFileAtomic(outFile, data, 0600)
  92. }
  93. //-------------------------------------------------------------------------------
  94. // FilePVLastSignState stores the mutable part of PrivValidator.
  95. type FilePVLastSignState struct {
  96. Height int64 `json:"height,string"`
  97. Round int32 `json:"round"`
  98. Step int8 `json:"step"`
  99. Signature []byte `json:"signature,omitempty"`
  100. SignBytes tmbytes.HexBytes `json:"signbytes,omitempty"`
  101. filePath string
  102. }
  103. // checkHRS checks the given height, round, step (HRS) against that of the
  104. // FilePVLastSignState. It returns an error if the arguments constitute a regression,
  105. // or if they match but the SignBytes are empty.
  106. // The returned boolean indicates whether the last Signature should be reused -
  107. // it returns true if the HRS matches the arguments and the SignBytes are not empty (indicating
  108. // we have already signed for this HRS, and can reuse the existing signature).
  109. // It panics if the HRS matches the arguments, there's a SignBytes, but no Signature.
  110. func (lss *FilePVLastSignState) checkHRS(height int64, round int32, step int8) (bool, error) {
  111. if lss.Height > height {
  112. return false, fmt.Errorf("height regression. Got %v, last height %v", height, lss.Height)
  113. }
  114. if lss.Height == height {
  115. if lss.Round > round {
  116. return false, fmt.Errorf("round regression at height %v. Got %v, last round %v", height, round, lss.Round)
  117. }
  118. if lss.Round == round {
  119. if lss.Step > step {
  120. return false, fmt.Errorf(
  121. "step regression at height %v round %v. Got %v, last step %v",
  122. height,
  123. round,
  124. step,
  125. lss.Step,
  126. )
  127. } else if lss.Step == step {
  128. if lss.SignBytes != nil {
  129. if lss.Signature == nil {
  130. panic("pv: Signature is nil but SignBytes is not!")
  131. }
  132. return true, nil
  133. }
  134. return false, errors.New("no SignBytes found")
  135. }
  136. }
  137. }
  138. return false, nil
  139. }
  140. // Save persists the FilePvLastSignState to its filePath.
  141. func (lss *FilePVLastSignState) Save() error {
  142. outFile := lss.filePath
  143. if outFile == "" {
  144. return errors.New("cannot save FilePVLastSignState: filePath not set")
  145. }
  146. jsonBytes, err := json.MarshalIndent(lss, "", " ")
  147. if err != nil {
  148. return err
  149. }
  150. return tempfile.WriteFileAtomic(outFile, jsonBytes, 0600)
  151. }
  152. //-------------------------------------------------------------------------------
  153. // FilePV implements PrivValidator using data persisted to disk
  154. // to prevent double signing.
  155. // NOTE: the directories containing pv.Key.filePath and pv.LastSignState.filePath must already exist.
  156. // It includes the LastSignature and LastSignBytes so we don't lose the signature
  157. // if the process crashes after signing but before the resulting consensus message is processed.
  158. type FilePV struct {
  159. Key FilePVKey
  160. LastSignState FilePVLastSignState
  161. }
  162. var _ types.PrivValidator = (*FilePV)(nil)
  163. // NewFilePV generates a new validator from the given key and paths.
  164. func NewFilePV(privKey crypto.PrivKey, keyFilePath, stateFilePath string) *FilePV {
  165. return &FilePV{
  166. Key: FilePVKey{
  167. Address: privKey.PubKey().Address(),
  168. PubKey: privKey.PubKey(),
  169. PrivKey: privKey,
  170. filePath: keyFilePath,
  171. },
  172. LastSignState: FilePVLastSignState{
  173. Step: stepNone,
  174. filePath: stateFilePath,
  175. },
  176. }
  177. }
  178. // GenFilePV generates a new validator with randomly generated private key
  179. // and sets the filePaths, but does not call Save().
  180. func GenFilePV(keyFilePath, stateFilePath, keyType string) (*FilePV, error) {
  181. switch keyType {
  182. case types.ABCIPubKeyTypeSecp256k1:
  183. return NewFilePV(secp256k1.GenPrivKey(), keyFilePath, stateFilePath), nil
  184. case "", types.ABCIPubKeyTypeEd25519:
  185. return NewFilePV(ed25519.GenPrivKey(), keyFilePath, stateFilePath), nil
  186. default:
  187. return nil, fmt.Errorf("key type: %s is not supported", keyType)
  188. }
  189. }
  190. // LoadFilePV loads a FilePV from the filePaths. The FilePV handles double
  191. // signing prevention by persisting data to the stateFilePath. If either file path
  192. // does not exist, the program will exit.
  193. func LoadFilePV(keyFilePath, stateFilePath string) (*FilePV, error) {
  194. return loadFilePV(keyFilePath, stateFilePath, true)
  195. }
  196. // LoadFilePVEmptyState loads a FilePV from the given keyFilePath, with an empty LastSignState.
  197. // If the keyFilePath does not exist, the program will exit.
  198. func LoadFilePVEmptyState(keyFilePath, stateFilePath string) (*FilePV, error) {
  199. return loadFilePV(keyFilePath, stateFilePath, false)
  200. }
  201. // If loadState is true, we load from the stateFilePath. Otherwise, we use an empty LastSignState.
  202. func loadFilePV(keyFilePath, stateFilePath string, loadState bool) (*FilePV, error) {
  203. keyJSONBytes, err := os.ReadFile(keyFilePath)
  204. if err != nil {
  205. return nil, err
  206. }
  207. pvKey := FilePVKey{}
  208. err = json.Unmarshal(keyJSONBytes, &pvKey)
  209. if err != nil {
  210. return nil, fmt.Errorf("error reading PrivValidator key from %v: %w", keyFilePath, err)
  211. }
  212. // overwrite pubkey and address for convenience
  213. pvKey.PubKey = pvKey.PrivKey.PubKey()
  214. pvKey.Address = pvKey.PubKey.Address()
  215. pvKey.filePath = keyFilePath
  216. pvState := FilePVLastSignState{}
  217. if loadState {
  218. stateJSONBytes, err := os.ReadFile(stateFilePath)
  219. if err != nil {
  220. return nil, err
  221. }
  222. err = json.Unmarshal(stateJSONBytes, &pvState)
  223. if err != nil {
  224. return nil, fmt.Errorf("error reading PrivValidator state from %v: %w", stateFilePath, err)
  225. }
  226. }
  227. pvState.filePath = stateFilePath
  228. return &FilePV{
  229. Key: pvKey,
  230. LastSignState: pvState,
  231. }, nil
  232. }
  233. // LoadOrGenFilePV loads a FilePV from the given filePaths
  234. // or else generates a new one and saves it to the filePaths.
  235. func LoadOrGenFilePV(keyFilePath, stateFilePath string) (*FilePV, error) {
  236. if tmos.FileExists(keyFilePath) {
  237. pv, err := LoadFilePV(keyFilePath, stateFilePath)
  238. if err != nil {
  239. return nil, err
  240. }
  241. return pv, nil
  242. }
  243. pv, err := GenFilePV(keyFilePath, stateFilePath, "")
  244. if err != nil {
  245. return nil, err
  246. }
  247. if err := pv.Save(); err != nil {
  248. return nil, err
  249. }
  250. return pv, nil
  251. }
  252. // GetAddress returns the address of the validator.
  253. // Implements PrivValidator.
  254. func (pv *FilePV) GetAddress() types.Address {
  255. return pv.Key.Address
  256. }
  257. // GetPubKey returns the public key of the validator.
  258. // Implements PrivValidator.
  259. func (pv *FilePV) GetPubKey(ctx context.Context) (crypto.PubKey, error) {
  260. return pv.Key.PubKey, nil
  261. }
  262. // SignVote signs a canonical representation of the vote, along with the
  263. // chainID. Implements PrivValidator.
  264. func (pv *FilePV) SignVote(ctx context.Context, chainID string, vote *tmproto.Vote) error {
  265. if err := pv.signVote(chainID, vote); err != nil {
  266. return fmt.Errorf("error signing vote: %w", err)
  267. }
  268. return nil
  269. }
  270. // SignProposal signs a canonical representation of the proposal, along with
  271. // the chainID. Implements PrivValidator.
  272. func (pv *FilePV) SignProposal(ctx context.Context, chainID string, proposal *tmproto.Proposal) error {
  273. if err := pv.signProposal(chainID, proposal); err != nil {
  274. return fmt.Errorf("error signing proposal: %w", err)
  275. }
  276. return nil
  277. }
  278. // Save persists the FilePV to disk.
  279. func (pv *FilePV) Save() error {
  280. if err := pv.Key.Save(); err != nil {
  281. return err
  282. }
  283. return pv.LastSignState.Save()
  284. }
  285. // Reset resets all fields in the FilePV.
  286. // NOTE: Unsafe!
  287. func (pv *FilePV) Reset() error {
  288. var sig []byte
  289. pv.LastSignState.Height = 0
  290. pv.LastSignState.Round = 0
  291. pv.LastSignState.Step = 0
  292. pv.LastSignState.Signature = sig
  293. pv.LastSignState.SignBytes = nil
  294. return pv.Save()
  295. }
  296. // String returns a string representation of the FilePV.
  297. func (pv *FilePV) String() string {
  298. return fmt.Sprintf(
  299. "PrivValidator{%v LH:%v, LR:%v, LS:%v}",
  300. pv.GetAddress(),
  301. pv.LastSignState.Height,
  302. pv.LastSignState.Round,
  303. pv.LastSignState.Step,
  304. )
  305. }
  306. //------------------------------------------------------------------------------------
  307. // signVote checks if the vote is good to sign and sets the vote signature.
  308. // It may need to set the timestamp as well if the vote is otherwise the same as
  309. // a previously signed vote (ie. we crashed after signing but before the vote hit the WAL).
  310. func (pv *FilePV) signVote(chainID string, vote *tmproto.Vote) error {
  311. step, err := voteToStep(vote)
  312. if err != nil {
  313. return err
  314. }
  315. height := vote.Height
  316. round := vote.Round
  317. lss := pv.LastSignState
  318. sameHRS, err := lss.checkHRS(height, round, step)
  319. if err != nil {
  320. return err
  321. }
  322. signBytes := types.VoteSignBytes(chainID, vote)
  323. // We might crash before writing to the wal,
  324. // causing us to try to re-sign for the same HRS.
  325. // If signbytes are the same, use the last signature.
  326. // If they only differ by timestamp, use last timestamp and signature
  327. // Otherwise, return error
  328. if sameHRS {
  329. if bytes.Equal(signBytes, lss.SignBytes) {
  330. vote.Signature = lss.Signature
  331. } else {
  332. timestamp, ok, err := checkVotesOnlyDifferByTimestamp(lss.SignBytes, signBytes)
  333. if err != nil {
  334. return err
  335. }
  336. if !ok {
  337. return errors.New("conflicting data")
  338. }
  339. vote.Timestamp = timestamp
  340. vote.Signature = lss.Signature
  341. }
  342. return nil
  343. }
  344. // It passed the checks. Sign the vote
  345. sig, err := pv.Key.PrivKey.Sign(signBytes)
  346. if err != nil {
  347. return err
  348. }
  349. if err := pv.saveSigned(height, round, step, signBytes, sig); err != nil {
  350. return err
  351. }
  352. vote.Signature = sig
  353. return nil
  354. }
  355. // signProposal checks if the proposal is good to sign and sets the proposal signature.
  356. func (pv *FilePV) signProposal(chainID string, proposal *tmproto.Proposal) error {
  357. height, round, step := proposal.Height, proposal.Round, stepPropose
  358. lss := pv.LastSignState
  359. sameHRS, err := lss.checkHRS(height, round, step)
  360. if err != nil {
  361. return err
  362. }
  363. signBytes := types.ProposalSignBytes(chainID, proposal)
  364. // We might crash before writing to the wal,
  365. // causing us to try to re-sign for the same HRS.
  366. // If signbytes are the same, use the last signature.
  367. if sameHRS {
  368. if !bytes.Equal(signBytes, lss.SignBytes) {
  369. return errors.New("conflicting data")
  370. }
  371. proposal.Signature = lss.Signature
  372. return nil
  373. }
  374. // It passed the checks. Sign the proposal
  375. sig, err := pv.Key.PrivKey.Sign(signBytes)
  376. if err != nil {
  377. return err
  378. }
  379. if err := pv.saveSigned(height, round, step, signBytes, sig); err != nil {
  380. return err
  381. }
  382. proposal.Signature = sig
  383. return nil
  384. }
  385. // Persist height/round/step and signature
  386. func (pv *FilePV) saveSigned(height int64, round int32, step int8, signBytes []byte, sig []byte) error {
  387. pv.LastSignState.Height = height
  388. pv.LastSignState.Round = round
  389. pv.LastSignState.Step = step
  390. pv.LastSignState.Signature = sig
  391. pv.LastSignState.SignBytes = signBytes
  392. return pv.LastSignState.Save()
  393. }
  394. //-----------------------------------------------------------------------------------------
  395. // returns the timestamp from the lastSignBytes.
  396. // returns true if the only difference in the votes is their timestamp.
  397. func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool, error) {
  398. var lastVote, newVote tmproto.CanonicalVote
  399. if err := protoio.UnmarshalDelimited(lastSignBytes, &lastVote); err != nil {
  400. return time.Time{}, false, fmt.Errorf("LastSignBytes cannot be unmarshalled into vote: %w", err)
  401. }
  402. if err := protoio.UnmarshalDelimited(newSignBytes, &newVote); err != nil {
  403. return time.Time{}, false, fmt.Errorf("signBytes cannot be unmarshalled into vote: %w", err)
  404. }
  405. lastTime := lastVote.Timestamp
  406. // set the times to the same value and check equality
  407. now := tmtime.Now()
  408. lastVote.Timestamp = now
  409. newVote.Timestamp = now
  410. return lastTime, proto.Equal(&newVote, &lastVote), nil
  411. }