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.

131 lines
3.4 KiB

privval: refactor Remote signers (#3370) This PR is related to #3107 and a continuation of #3351 It is important to emphasise that in the privval original design, client/server and listening/dialing roles are inverted and do not follow a conventional interaction. Given two hosts A and B: Host A is listener/client Host B is dialer/server (contains the secret key) When A requires a signature, it needs to wait for B to dial in before it can issue a request. A only accepts a single connection and any failure leads to dropping the connection and waiting for B to reconnect. The original rationale behind this design was based on security. Host B only allows outbound connections to a list of whitelisted hosts. It is not possible to reach B unless B dials in. There are no listening/open ports in B. This PR results in the following changes: Refactors ping/heartbeat to avoid previously existing race conditions. Separates transport (dialer/listener) from signing (client/server) concerns to simplify workflow. Unifies and abstracts away the differences between unix and tcp sockets. A single signer endpoint implementation unifies connection handling code (read/write/close/connection obj) The signer request handler (server side) is customizable to increase testability. Updates and extends unit tests A high level overview of the classes is as follows: Transport (endpoints): The following classes take care of establishing a connection SignerDialerEndpoint SignerListeningEndpoint SignerEndpoint groups common functionality (read/write/timeouts/etc.) Signing (client/server): The following classes take care of exchanging request/responses SignerClient SignerServer This PR also closes #3601 Commits: * refactoring - work in progress * reworking unit tests * Encapsulating and fixing unit tests * Improve tests * Clean up * Fix/improve unit tests * clean up tests * Improving service endpoint * fixing unit test * fix linter issues * avoid invalid cache values (improve later?) * complete implementation * wip * improved connection loop * Improve reconnections + fixing unit tests * addressing comments * small formatting changes * clean up * Update node/node.go Co-Authored-By: jleni <juan.leni@zondax.ch> * Update privval/signer_client.go Co-Authored-By: jleni <juan.leni@zondax.ch> * Update privval/signer_client_test.go Co-Authored-By: jleni <juan.leni@zondax.ch> * check during initialization * dropping connecting when writing fails * removing break * use t.log instead * unifying and using cmn.GetFreePort() * review fixes * reordering and unifying drop connection * closing instead of signalling * refactored service loop * removed superfluous brackets * GetPubKey can return errors * Revert "GetPubKey can return errors" This reverts commit 68c06f19b4650389d7e5ab1659b318889028202c. * adding entry to changelog * Update CHANGELOG_PENDING.md Co-Authored-By: jleni <juan.leni@zondax.ch> * Update privval/signer_client.go Co-Authored-By: jleni <juan.leni@zondax.ch> * Update privval/signer_dialer_endpoint.go Co-Authored-By: jleni <juan.leni@zondax.ch> * Update privval/signer_dialer_endpoint.go Co-Authored-By: jleni <juan.leni@zondax.ch> * Update privval/signer_dialer_endpoint.go Co-Authored-By: jleni <juan.leni@zondax.ch> * Update privval/signer_dialer_endpoint.go Co-Authored-By: jleni <juan.leni@zondax.ch> * Update privval/signer_listener_endpoint_test.go Co-Authored-By: jleni <juan.leni@zondax.ch> * updating node.go * review fixes * fixes linter * fixing unit test * small fixes in comments * addressing review comments * addressing review comments 2 * reverting suggestion * Update privval/signer_client_test.go Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com> * Update privval/signer_client_test.go Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com> * Update privval/signer_listener_endpoint_test.go Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com> * do not expose brokenSignerDialerEndpoint * clean up logging * unifying methods shorten test time signer also drops * reenabling pings * improving testability + unit test * fixing go fmt + unit test * remove unused code * Addressing review comments * simplifying connection workflow * fix linter/go import issue * using base service quit * updating comment * Simplifying design + adjusting names * fixing linter issues * refactoring test harness + fixes * Addressing review comments * cleaning up * adding additional error check
5 years ago
  1. package privval
  2. import (
  3. "time"
  4. "github.com/pkg/errors"
  5. "github.com/tendermint/tendermint/crypto"
  6. "github.com/tendermint/tendermint/types"
  7. )
  8. // SignerClient implements PrivValidator.
  9. // Handles remote validator connections that provide signing services
  10. type SignerClient struct {
  11. endpoint *SignerListenerEndpoint
  12. }
  13. var _ types.PrivValidator = (*SignerClient)(nil)
  14. // NewSignerClient returns an instance of SignerClient.
  15. // it will start the endpoint (if not already started)
  16. func NewSignerClient(endpoint *SignerListenerEndpoint) (*SignerClient, error) {
  17. if !endpoint.IsRunning() {
  18. if err := endpoint.Start(); err != nil {
  19. return nil, errors.Wrap(err, "failed to start listener endpoint")
  20. }
  21. }
  22. return &SignerClient{endpoint: endpoint}, nil
  23. }
  24. // Close closes the underlying connection
  25. func (sc *SignerClient) Close() error {
  26. return sc.endpoint.Close()
  27. }
  28. // IsConnected indicates with the signer is connected to a remote signing service
  29. func (sc *SignerClient) IsConnected() bool {
  30. return sc.endpoint.IsConnected()
  31. }
  32. // WaitForConnection waits maxWait for a connection or returns a timeout error
  33. func (sc *SignerClient) WaitForConnection(maxWait time.Duration) error {
  34. return sc.endpoint.WaitForConnection(maxWait)
  35. }
  36. //--------------------------------------------------------
  37. // Implement PrivValidator
  38. // Ping sends a ping request to the remote signer
  39. func (sc *SignerClient) Ping() error {
  40. response, err := sc.endpoint.SendRequest(&PingRequest{})
  41. if err != nil {
  42. sc.endpoint.Logger.Error("SignerClient::Ping", "err", err)
  43. return nil
  44. }
  45. _, ok := response.(*PingResponse)
  46. if !ok {
  47. sc.endpoint.Logger.Error("SignerClient::Ping", "err", "response != PingResponse")
  48. return err
  49. }
  50. return nil
  51. }
  52. // GetPubKey retrieves a public key from a remote signer
  53. func (sc *SignerClient) GetPubKey() crypto.PubKey {
  54. response, err := sc.endpoint.SendRequest(&PubKeyRequest{})
  55. if err != nil {
  56. sc.endpoint.Logger.Error("SignerClient::GetPubKey", "err", err)
  57. return nil
  58. }
  59. pubKeyResp, ok := response.(*PubKeyResponse)
  60. if !ok {
  61. sc.endpoint.Logger.Error("SignerClient::GetPubKey", "err", "response != PubKeyResponse")
  62. return nil
  63. }
  64. if pubKeyResp.Error != nil {
  65. sc.endpoint.Logger.Error("failed to get private validator's public key", "err", pubKeyResp.Error)
  66. return nil
  67. }
  68. return pubKeyResp.PubKey
  69. }
  70. // SignVote requests a remote signer to sign a vote
  71. func (sc *SignerClient) SignVote(chainID string, vote *types.Vote) error {
  72. response, err := sc.endpoint.SendRequest(&SignVoteRequest{Vote: vote})
  73. if err != nil {
  74. sc.endpoint.Logger.Error("SignerClient::SignVote", "err", err)
  75. return err
  76. }
  77. resp, ok := response.(*SignedVoteResponse)
  78. if !ok {
  79. sc.endpoint.Logger.Error("SignerClient::GetPubKey", "err", "response != SignedVoteResponse")
  80. return ErrUnexpectedResponse
  81. }
  82. if resp.Error != nil {
  83. return resp.Error
  84. }
  85. *vote = *resp.Vote
  86. return nil
  87. }
  88. // SignProposal requests a remote signer to sign a proposal
  89. func (sc *SignerClient) SignProposal(chainID string, proposal *types.Proposal) error {
  90. response, err := sc.endpoint.SendRequest(&SignProposalRequest{Proposal: proposal})
  91. if err != nil {
  92. sc.endpoint.Logger.Error("SignerClient::SignProposal", "err", err)
  93. return err
  94. }
  95. resp, ok := response.(*SignedProposalResponse)
  96. if !ok {
  97. sc.endpoint.Logger.Error("SignerClient::SignProposal", "err", "response != SignedProposalResponse")
  98. return ErrUnexpectedResponse
  99. }
  100. if resp.Error != nil {
  101. return resp.Error
  102. }
  103. *proposal = *resp.Proposal
  104. return nil
  105. }