Co-authored-by: Anton Kaliaev <anton.kalyaev@gmail.com>pull/5878/head
@ -0,0 +1,70 @@ | |||
--- | |||
order: 7 | |||
--- | |||
# Remote signer | |||
Tendermint provides a remote signer option for validators. A remote signer enables the operator to store the validator key on a different machine minimizing the attack surface if a server were to be compromised. | |||
The remote signer protocol implements a [client and server architecture](https://en.wikipedia.org/wiki/Client%E2%80%93server_model). When Tendermint requires the public key or signature for a proposal or vote it requests it from the remote signer. | |||
To run a secure validator and remote signer system it is recommended to use a VPC (virtual private cloud) or a private connection. | |||
There are two different configurations that can be used: Raw or gRPC. | |||
## Raw | |||
While both options use tcp or unix sockets the raw option uses tcp or unix sockets without http. The raw protocol sets up Tendermint as the server and the remote signer as the client. This aids in not exposing the remote signer to public network. | |||
> Warning: Raw will be deprecated in a future major release, we recommend implementing your key management server against the gRPC configuration. | |||
## gRPC | |||
[gRPC](https://grpc.io/) is an RPC framework built with [HTTP/2](https://en.wikipedia.org/wiki/HTTP/2), uses [Protocol Buffers](https://developers.google.com/protocol-buffers) to define services and has been standardized within the cloud infrastructure community. gRPC provides a language agnostic way to implement services. This aids developers in the writing key management servers in various different languages. | |||
GRPC utilizes [TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security), another widely standardized protocol, to secure connections. There are two forms of TLS to secure a connection, one-way and two-way. One way is when the client identifies the server but the server allows anyone to connect to it. Two-way is when the client identifies the server and the server identifies the client, prohibiting connections from unknown parties. | |||
When using gRPC Tendermint is setup as the client. Tendermint will make calls to the remote signer. We recommend not exposing the remote signer to the public network with the use of virtual private cloud. | |||
Securing your remote signers connection is highly recommended, but we provide the option to run it with a insecure connection. | |||
### Generating Certificates | |||
To run a secure connection with gRPC we need to generate certificates and keys. We will walkthrough how to self sign certificates for two-way TLS. | |||
There are two ways to generate certificates, [openssl](https://www.openssl.org/) and [certstarp](https://github.com/square/certstrap). Both of these options can be used but we will be covering `certstrap` because it provides a simpler process then openssl. | |||
- Install `Certstrap`: | |||
```sh | |||
go get github.com/square/certstrap@v1.2.0 | |||
``` | |||
- Create certificate authority for self signing. | |||
```sh | |||
# generate self signing ceritificate authority | |||
certstrap init --common-name "<name_CA>" --expires "20 years" | |||
``` | |||
- Request a certificate for the server. | |||
- For generalization purposes we set the ip to `127.0.0.1`, but for your node please use the servers IP. | |||
- Sign the servers certificate with your certificate authority | |||
```sh | |||
# generate server cerificate | |||
certstrap request-cert -cn server -ip 127.0.0.1 | |||
# self-sign server cerificate with rootCA | |||
certstrap sign server --CA "<name_CA>" 127.0.0.1 | |||
``` | |||
- Request a certificate for the client. | |||
- For generalization purposes we set the ip to `127.0.0.1`, but for your node please use the clients IP. | |||
- Sign the clients certificate with your certificate authority | |||
```sh | |||
# generate client cerificate | |||
certstrap request-cert -cn client -ip 127.0.0.1 | |||
# self-sign client cerificate with rootCA | |||
certstrap sign client --CA "<name_CA>" 127.0.0.1 | |||
``` |
@ -0,0 +1,26 @@ | |||
package node | |||
import ( | |||
"strings" | |||
) | |||
// splitAndTrimEmpty slices s into all subslices separated by sep and returns a | |||
// slice of the string s with all leading and trailing Unicode code points | |||
// contained in cutset removed. If sep is empty, SplitAndTrim splits after each | |||
// UTF-8 sequence. First part is equivalent to strings.SplitN with a count of | |||
// -1. also filter out empty strings, only return non-empty strings. | |||
func splitAndTrimEmpty(s, sep, cutset string) []string { | |||
if s == "" { | |||
return []string{} | |||
} | |||
spl := strings.Split(s, sep) | |||
nonEmptyStrings := make([]string, 0, len(spl)) | |||
for i := 0; i < len(spl); i++ { | |||
element := strings.Trim(spl[i], cutset) | |||
if element != "" { | |||
nonEmptyStrings = append(nonEmptyStrings, element) | |||
} | |||
} | |||
return nonEmptyStrings | |||
} |
@ -0,0 +1,108 @@ | |||
package grpc | |||
import ( | |||
"context" | |||
"time" | |||
grpc "google.golang.org/grpc" | |||
"google.golang.org/grpc/status" | |||
"github.com/tendermint/tendermint/crypto" | |||
cryptoenc "github.com/tendermint/tendermint/crypto/encoding" | |||
"github.com/tendermint/tendermint/libs/log" | |||
privvalproto "github.com/tendermint/tendermint/proto/tendermint/privval" | |||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
// SignerClient implements PrivValidator. | |||
// Handles remote validator connections that provide signing services | |||
type SignerClient struct { | |||
logger log.Logger | |||
client privvalproto.PrivValidatorAPIClient | |||
conn *grpc.ClientConn | |||
chainID string | |||
} | |||
var _ types.PrivValidator = (*SignerClient)(nil) | |||
// NewSignerClient returns an instance of SignerClient. | |||
// it will start the endpoint (if not already started) | |||
func NewSignerClient(conn *grpc.ClientConn, | |||
chainID string, log log.Logger) (*SignerClient, error) { | |||
sc := &SignerClient{ | |||
logger: log, | |||
chainID: chainID, | |||
client: privvalproto.NewPrivValidatorAPIClient(conn), // Create the Private Validator Client | |||
} | |||
return sc, nil | |||
} | |||
// Close closes the underlying connection | |||
func (sc *SignerClient) Close() error { | |||
sc.logger.Info("Stopping service") | |||
if sc.conn != nil { | |||
return sc.conn.Close() | |||
} | |||
return nil | |||
} | |||
//-------------------------------------------------------- | |||
// Implement PrivValidator | |||
// GetPubKey retrieves a public key from a remote signer | |||
// returns an error if client is not able to provide the key | |||
func (sc *SignerClient) GetPubKey() (crypto.PubKey, error) { | |||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) // Todo: should this be configurable? | |||
defer cancel() | |||
resp, err := sc.client.GetPubKey(ctx, &privvalproto.PubKeyRequest{ChainId: sc.chainID}) | |||
if err != nil { | |||
errStatus, _ := status.FromError(err) | |||
sc.logger.Error("SignerClient::GetPubKey", "err", errStatus.Message()) | |||
return nil, errStatus.Err() | |||
} | |||
pk, err := cryptoenc.PubKeyFromProto(resp.PubKey) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return pk, nil | |||
} | |||
// SignVote requests a remote signer to sign a vote | |||
func (sc *SignerClient) SignVote(chainID string, vote *tmproto.Vote) error { | |||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) | |||
defer cancel() | |||
resp, err := sc.client.SignVote(ctx, &privvalproto.SignVoteRequest{ChainId: sc.chainID, Vote: vote}) | |||
if err != nil { | |||
errStatus, _ := status.FromError(err) | |||
sc.logger.Error("Client SignVote", "err", errStatus.Message()) | |||
return errStatus.Err() | |||
} | |||
*vote = resp.Vote | |||
return nil | |||
} | |||
// SignProposal requests a remote signer to sign a proposal | |||
func (sc *SignerClient) SignProposal(chainID string, proposal *tmproto.Proposal) error { | |||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) | |||
defer cancel() | |||
resp, err := sc.client.SignProposal( | |||
ctx, &privvalproto.SignProposalRequest{ChainId: chainID, Proposal: proposal}) | |||
if err != nil { | |||
errStatus, _ := status.FromError(err) | |||
sc.logger.Error("SignerClient::SignProposal", "err", errStatus.Message()) | |||
return errStatus.Err() | |||
} | |||
*proposal = resp.Proposal | |||
return nil | |||
} |
@ -0,0 +1,168 @@ | |||
package grpc_test | |||
import ( | |||
"context" | |||
"net" | |||
"testing" | |||
"time" | |||
grpc "google.golang.org/grpc" | |||
"google.golang.org/grpc/test/bufconn" | |||
"github.com/stretchr/testify/assert" | |||
"github.com/stretchr/testify/require" | |||
"github.com/tendermint/tendermint/crypto" | |||
"github.com/tendermint/tendermint/crypto/tmhash" | |||
"github.com/tendermint/tendermint/libs/log" | |||
tmrand "github.com/tendermint/tendermint/libs/rand" | |||
tmgrpc "github.com/tendermint/tendermint/privval/grpc" | |||
privvalproto "github.com/tendermint/tendermint/proto/tendermint/privval" | |||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
const chainID = "chain-id" | |||
func dialer(pv types.PrivValidator, logger log.Logger) (*grpc.Server, func(context.Context, string) (net.Conn, error)) { | |||
listener := bufconn.Listen(1024 * 1024) | |||
server := grpc.NewServer() | |||
s := tmgrpc.NewSignerServer(chainID, pv, logger) | |||
privvalproto.RegisterPrivValidatorAPIServer(server, s) | |||
go func() { | |||
if err := server.Serve(listener); err != nil { | |||
panic(err) | |||
} | |||
}() | |||
return server, func(context.Context, string) (net.Conn, error) { | |||
return listener.Dial() | |||
} | |||
} | |||
func TestSignerClient_GetPubKey(t *testing.T) { | |||
ctx := context.Background() | |||
mockPV := types.NewMockPV() | |||
logger := log.TestingLogger() | |||
srv, dialer := dialer(mockPV, logger) | |||
defer srv.Stop() | |||
conn, err := grpc.DialContext(ctx, "", grpc.WithInsecure(), grpc.WithContextDialer(dialer)) | |||
if err != nil { | |||
panic(err) | |||
} | |||
defer conn.Close() | |||
client, err := tmgrpc.NewSignerClient(conn, chainID, logger) | |||
require.NoError(t, err) | |||
pk, err := client.GetPubKey() | |||
require.NoError(t, err) | |||
assert.Equal(t, mockPV.PrivKey.PubKey(), pk) | |||
} | |||
func TestSignerClient_SignVote(t *testing.T) { | |||
ctx := context.Background() | |||
mockPV := types.NewMockPV() | |||
logger := log.TestingLogger() | |||
srv, dialer := dialer(mockPV, logger) | |||
defer srv.Stop() | |||
conn, err := grpc.DialContext(ctx, "", grpc.WithInsecure(), grpc.WithContextDialer(dialer)) | |||
if err != nil { | |||
panic(err) | |||
} | |||
defer conn.Close() | |||
client, err := tmgrpc.NewSignerClient(conn, chainID, logger) | |||
require.NoError(t, err) | |||
ts := time.Now() | |||
hash := tmrand.Bytes(tmhash.Size) | |||
valAddr := tmrand.Bytes(crypto.AddressSize) | |||
want := &types.Vote{ | |||
Type: tmproto.PrecommitType, | |||
Height: 1, | |||
Round: 2, | |||
BlockID: types.BlockID{Hash: hash, PartSetHeader: types.PartSetHeader{Hash: hash, Total: 2}}, | |||
Timestamp: ts, | |||
ValidatorAddress: valAddr, | |||
ValidatorIndex: 1, | |||
} | |||
have := &types.Vote{ | |||
Type: tmproto.PrecommitType, | |||
Height: 1, | |||
Round: 2, | |||
BlockID: types.BlockID{Hash: hash, PartSetHeader: types.PartSetHeader{Hash: hash, Total: 2}}, | |||
Timestamp: ts, | |||
ValidatorAddress: valAddr, | |||
ValidatorIndex: 1, | |||
} | |||
pbHave := have.ToProto() | |||
err = client.SignVote(chainID, pbHave) | |||
require.NoError(t, err) | |||
pbWant := want.ToProto() | |||
require.NoError(t, mockPV.SignVote(chainID, pbWant)) | |||
assert.Equal(t, pbWant.Signature, pbHave.Signature) | |||
} | |||
func TestSignerClient_SignProposal(t *testing.T) { | |||
ctx := context.Background() | |||
mockPV := types.NewMockPV() | |||
logger := log.TestingLogger() | |||
srv, dialer := dialer(mockPV, logger) | |||
defer srv.Stop() | |||
conn, err := grpc.DialContext(ctx, "", grpc.WithInsecure(), grpc.WithContextDialer(dialer)) | |||
if err != nil { | |||
panic(err) | |||
} | |||
defer conn.Close() | |||
client, err := tmgrpc.NewSignerClient(conn, chainID, logger) | |||
require.NoError(t, err) | |||
ts := time.Now() | |||
hash := tmrand.Bytes(tmhash.Size) | |||
have := &types.Proposal{ | |||
Type: tmproto.ProposalType, | |||
Height: 1, | |||
Round: 2, | |||
POLRound: 2, | |||
BlockID: types.BlockID{Hash: hash, PartSetHeader: types.PartSetHeader{Hash: hash, Total: 2}}, | |||
Timestamp: ts, | |||
} | |||
want := &types.Proposal{ | |||
Type: tmproto.ProposalType, | |||
Height: 1, | |||
Round: 2, | |||
POLRound: 2, | |||
BlockID: types.BlockID{Hash: hash, PartSetHeader: types.PartSetHeader{Hash: hash, Total: 2}}, | |||
Timestamp: ts, | |||
} | |||
pbHave := have.ToProto() | |||
err = client.SignProposal(chainID, pbHave) | |||
require.NoError(t, err) | |||
pbWant := want.ToProto() | |||
require.NoError(t, mockPV.SignProposal(chainID, pbWant)) | |||
assert.Equal(t, pbWant.Signature, pbHave.Signature) | |||
} |
@ -0,0 +1,87 @@ | |||
package grpc | |||
import ( | |||
context "context" | |||
"google.golang.org/grpc/codes" | |||
"google.golang.org/grpc/status" | |||
"github.com/tendermint/tendermint/crypto" | |||
cryptoenc "github.com/tendermint/tendermint/crypto/encoding" | |||
"github.com/tendermint/tendermint/libs/log" | |||
privvalproto "github.com/tendermint/tendermint/proto/tendermint/privval" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
// SignerServer implements PrivValidatorAPIServer 9generated via protobuf services) | |||
// Handles remote validator connections that provide signing services | |||
type SignerServer struct { | |||
logger log.Logger | |||
chainID string | |||
privVal types.PrivValidator | |||
} | |||
func NewSignerServer(chainID string, | |||
privVal types.PrivValidator, log log.Logger) *SignerServer { | |||
return &SignerServer{ | |||
logger: log, | |||
chainID: chainID, | |||
privVal: privVal, | |||
} | |||
} | |||
var _ privvalproto.PrivValidatorAPIServer = (*SignerServer)(nil) | |||
// PubKey receives a request for the pubkey | |||
// returns the pubkey on success and error on failure | |||
func (ss *SignerServer) GetPubKey(ctx context.Context, req *privvalproto.PubKeyRequest) ( | |||
*privvalproto.PubKeyResponse, error) { | |||
var pubKey crypto.PubKey | |||
pubKey, err := ss.privVal.GetPubKey() | |||
if err != nil { | |||
return nil, status.Errorf(codes.NotFound, "error getting pubkey: %v", err) | |||
} | |||
pk, err := cryptoenc.PubKeyToProto(pubKey) | |||
if err != nil { | |||
return nil, status.Errorf(codes.Internal, "error transitioning pubkey to proto: %v", err) | |||
} | |||
ss.logger.Info("SignerServer: GetPubKey Success") | |||
return &privvalproto.PubKeyResponse{PubKey: pk}, nil | |||
} | |||
// SignVote receives a vote sign requests, attempts to sign it | |||
// returns SignedVoteResponse on success and error on failure | |||
func (ss *SignerServer) SignVote(ctx context.Context, req *privvalproto.SignVoteRequest) ( | |||
*privvalproto.SignedVoteResponse, error) { | |||
vote := req.Vote | |||
err := ss.privVal.SignVote(req.ChainId, vote) | |||
if err != nil { | |||
return nil, status.Errorf(codes.InvalidArgument, "error signing vote: %v", err) | |||
} | |||
ss.logger.Info("SignerServer: SignVote Success") | |||
return &privvalproto.SignedVoteResponse{Vote: *vote}, nil | |||
} | |||
// SignProposal receives a proposal sign requests, attempts to sign it | |||
// returns SignedProposalResponse on success and error on failure | |||
func (ss *SignerServer) SignProposal(ctx context.Context, req *privvalproto.SignProposalRequest) ( | |||
*privvalproto.SignedProposalResponse, error) { | |||
proposal := req.Proposal | |||
err := ss.privVal.SignProposal(req.ChainId, proposal) | |||
if err != nil { | |||
return nil, status.Errorf(codes.InvalidArgument, "error signing proposal: %v", err) | |||
} | |||
ss.logger.Info("SignerServer: SignProposal Success") | |||
return &privvalproto.SignedProposalResponse{Proposal: *proposal}, nil | |||
} |
@ -0,0 +1,187 @@ | |||
package grpc_test | |||
import ( | |||
"context" | |||
"testing" | |||
"time" | |||
"github.com/stretchr/testify/assert" | |||
"github.com/stretchr/testify/require" | |||
"github.com/tendermint/tendermint/crypto" | |||
"github.com/tendermint/tendermint/crypto/tmhash" | |||
"github.com/tendermint/tendermint/libs/log" | |||
tmrand "github.com/tendermint/tendermint/libs/rand" | |||
tmgrpc "github.com/tendermint/tendermint/privval/grpc" | |||
privvalproto "github.com/tendermint/tendermint/proto/tendermint/privval" | |||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
const ChainID = "123" | |||
func TestGetPubKey(t *testing.T) { | |||
testCases := []struct { | |||
name string | |||
pv types.PrivValidator | |||
err bool | |||
}{ | |||
{name: "valid", pv: types.NewMockPV(), err: false}, | |||
{name: "error on pubkey", pv: types.NewErroringMockPV(), err: true}, | |||
} | |||
for _, tc := range testCases { | |||
tc := tc | |||
t.Run(tc.name, func(t *testing.T) { | |||
s := tmgrpc.NewSignerServer(ChainID, tc.pv, log.TestingLogger()) | |||
req := &privvalproto.PubKeyRequest{ChainId: ChainID} | |||
resp, err := s.GetPubKey(context.Background(), req) | |||
if tc.err { | |||
require.Error(t, err) | |||
} else { | |||
pk, err := tc.pv.GetPubKey() | |||
require.NoError(t, err) | |||
assert.Equal(t, resp.PubKey.GetEd25519(), pk.Bytes()) | |||
} | |||
}) | |||
} | |||
} | |||
func TestSignVote(t *testing.T) { | |||
ts := time.Now() | |||
hash := tmrand.Bytes(tmhash.Size) | |||
valAddr := tmrand.Bytes(crypto.AddressSize) | |||
testCases := []struct { | |||
name string | |||
pv types.PrivValidator | |||
have, want *types.Vote | |||
err bool | |||
}{ | |||
{name: "valid", pv: types.NewMockPV(), have: &types.Vote{ | |||
Type: tmproto.PrecommitType, | |||
Height: 1, | |||
Round: 2, | |||
BlockID: types.BlockID{Hash: hash, PartSetHeader: types.PartSetHeader{Hash: hash, Total: 2}}, | |||
Timestamp: ts, | |||
ValidatorAddress: valAddr, | |||
ValidatorIndex: 1, | |||
}, want: &types.Vote{ | |||
Type: tmproto.PrecommitType, | |||
Height: 1, | |||
Round: 2, | |||
BlockID: types.BlockID{Hash: hash, PartSetHeader: types.PartSetHeader{Hash: hash, Total: 2}}, | |||
Timestamp: ts, | |||
ValidatorAddress: valAddr, | |||
ValidatorIndex: 1, | |||
}, | |||
err: false}, | |||
{name: "invalid vote", pv: types.NewErroringMockPV(), have: &types.Vote{ | |||
Type: tmproto.PrecommitType, | |||
Height: 1, | |||
Round: 2, | |||
BlockID: types.BlockID{Hash: hash, PartSetHeader: types.PartSetHeader{Hash: hash, Total: 2}}, | |||
Timestamp: ts, | |||
ValidatorAddress: valAddr, | |||
ValidatorIndex: 1, | |||
Signature: []byte("signed"), | |||
}, want: &types.Vote{ | |||
Type: tmproto.PrecommitType, | |||
Height: 1, | |||
Round: 2, | |||
BlockID: types.BlockID{Hash: hash, PartSetHeader: types.PartSetHeader{Hash: hash, Total: 2}}, | |||
Timestamp: ts, | |||
ValidatorAddress: valAddr, | |||
ValidatorIndex: 1, | |||
Signature: []byte("signed"), | |||
}, | |||
err: true}, | |||
} | |||
for _, tc := range testCases { | |||
tc := tc | |||
t.Run(tc.name, func(t *testing.T) { | |||
s := tmgrpc.NewSignerServer(ChainID, tc.pv, log.TestingLogger()) | |||
req := &privvalproto.SignVoteRequest{ChainId: ChainID, Vote: tc.have.ToProto()} | |||
resp, err := s.SignVote(context.Background(), req) | |||
if tc.err { | |||
require.Error(t, err) | |||
} else { | |||
pbVote := tc.want.ToProto() | |||
require.NoError(t, tc.pv.SignVote(ChainID, pbVote)) | |||
assert.Equal(t, pbVote.Signature, resp.Vote.Signature) | |||
} | |||
}) | |||
} | |||
} | |||
func TestSignProposal(t *testing.T) { | |||
ts := time.Now() | |||
hash := tmrand.Bytes(tmhash.Size) | |||
testCases := []struct { | |||
name string | |||
pv types.PrivValidator | |||
have, want *types.Proposal | |||
err bool | |||
}{ | |||
{name: "valid", pv: types.NewMockPV(), have: &types.Proposal{ | |||
Type: tmproto.ProposalType, | |||
Height: 1, | |||
Round: 2, | |||
POLRound: 2, | |||
BlockID: types.BlockID{Hash: hash, PartSetHeader: types.PartSetHeader{Hash: hash, Total: 2}}, | |||
Timestamp: ts, | |||
}, want: &types.Proposal{ | |||
Type: tmproto.ProposalType, | |||
Height: 1, | |||
Round: 2, | |||
POLRound: 2, | |||
BlockID: types.BlockID{Hash: hash, PartSetHeader: types.PartSetHeader{Hash: hash, Total: 2}}, | |||
Timestamp: ts, | |||
}, | |||
err: false}, | |||
{name: "invalid proposal", pv: types.NewErroringMockPV(), have: &types.Proposal{ | |||
Type: tmproto.ProposalType, | |||
Height: 1, | |||
Round: 2, | |||
POLRound: 2, | |||
BlockID: types.BlockID{Hash: hash, PartSetHeader: types.PartSetHeader{Hash: hash, Total: 2}}, | |||
Timestamp: ts, | |||
Signature: []byte("signed"), | |||
}, want: &types.Proposal{ | |||
Type: tmproto.ProposalType, | |||
Height: 1, | |||
Round: 2, | |||
POLRound: 2, | |||
BlockID: types.BlockID{Hash: hash, PartSetHeader: types.PartSetHeader{Hash: hash, Total: 2}}, | |||
Timestamp: ts, | |||
Signature: []byte("signed"), | |||
}, | |||
err: true}, | |||
} | |||
for _, tc := range testCases { | |||
tc := tc | |||
t.Run(tc.name, func(t *testing.T) { | |||
s := tmgrpc.NewSignerServer(ChainID, tc.pv, log.TestingLogger()) | |||
req := &privvalproto.SignProposalRequest{ChainId: ChainID, Proposal: tc.have.ToProto()} | |||
resp, err := s.SignProposal(context.Background(), req) | |||
if tc.err { | |||
require.Error(t, err) | |||
} else { | |||
pbProposal := tc.want.ToProto() | |||
require.NoError(t, tc.pv.SignProposal(ChainID, pbProposal)) | |||
assert.Equal(t, pbProposal.Signature, resp.Proposal.Signature) | |||
} | |||
}) | |||
} | |||
} |
@ -0,0 +1,82 @@ | |||
package grpc | |||
import ( | |||
"crypto/tls" | |||
"crypto/x509" | |||
"io/ioutil" | |||
"os" | |||
"time" | |||
grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry" | |||
"github.com/tendermint/tendermint/libs/log" | |||
grpc "google.golang.org/grpc" | |||
"google.golang.org/grpc/credentials" | |||
"google.golang.org/grpc/keepalive" | |||
) | |||
// DefaultDialOptions constructs a list of grpc dial options | |||
func DefaultDialOptions( | |||
extraOpts ...grpc.DialOption, | |||
) []grpc.DialOption { | |||
const ( | |||
retries = 50 // 50 * 100ms = 5s total | |||
timeout = 1 * time.Second | |||
maxCallRecvMsgSize = 1 << 20 // Default 5Mb | |||
) | |||
var kacp = keepalive.ClientParameters{ | |||
Time: 10 * time.Second, // send pings every 10 seconds if there is no activity | |||
Timeout: 2 * time.Second, // wait 2 seconds for ping ack before considering the connection dead | |||
} | |||
opts := []grpc_retry.CallOption{ | |||
grpc_retry.WithBackoff(grpc_retry.BackoffExponential(timeout)), | |||
} | |||
dialOpts := []grpc.DialOption{ | |||
grpc.WithKeepaliveParams(kacp), | |||
grpc.WithDefaultCallOptions( | |||
grpc.MaxCallRecvMsgSize(maxCallRecvMsgSize), | |||
grpc_retry.WithMax(retries), | |||
), | |||
grpc.WithUnaryInterceptor( | |||
grpc_retry.UnaryClientInterceptor(opts...), | |||
), | |||
} | |||
dialOpts = append(dialOpts, extraOpts...) | |||
return dialOpts | |||
} | |||
func GenerateTLS(certPath, keyPath, ca string, log log.Logger) grpc.DialOption { | |||
certificate, err := tls.LoadX509KeyPair( | |||
certPath, | |||
keyPath, | |||
) | |||
if err != nil { | |||
log.Error("error", err) | |||
os.Exit(1) | |||
} | |||
certPool := x509.NewCertPool() | |||
bs, err := ioutil.ReadFile(ca) | |||
if err != nil { | |||
log.Error("failed to read ca cert:", "error", err) | |||
os.Exit(1) | |||
} | |||
ok := certPool.AppendCertsFromPEM(bs) | |||
if !ok { | |||
log.Error("failed to append certs") | |||
os.Exit(1) | |||
} | |||
transportCreds := credentials.NewTLS(&tls.Config{ | |||
Certificates: []tls.Certificate{certificate}, | |||
RootCAs: certPool, | |||
MinVersion: tls.VersionTLS13, | |||
}) | |||
return grpc.WithTransportCredentials(transportCreds) | |||
} |
@ -0,0 +1,199 @@ | |||
// Code generated by protoc-gen-gogo. DO NOT EDIT. | |||
// source: tendermint/privval/service.proto | |||
package privval | |||
import ( | |||
context "context" | |||
fmt "fmt" | |||
proto "github.com/gogo/protobuf/proto" | |||
grpc "google.golang.org/grpc" | |||
codes "google.golang.org/grpc/codes" | |||
status "google.golang.org/grpc/status" | |||
math "math" | |||
) | |||
// Reference imports to suppress errors if they are not otherwise used. | |||
var _ = proto.Marshal | |||
var _ = fmt.Errorf | |||
var _ = math.Inf | |||
// This is a compile-time assertion to ensure that this generated file | |||
// is compatible with the proto package it is being compiled against. | |||
// A compilation error at this line likely means your copy of the | |||
// proto package needs to be updated. | |||
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package | |||
func init() { proto.RegisterFile("tendermint/privval/service.proto", fileDescriptor_7afe74f9f46d3dc9) } | |||
var fileDescriptor_7afe74f9f46d3dc9 = []byte{ | |||
// 251 bytes of a gzipped FileDescriptorProto | |||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x28, 0x49, 0xcd, 0x4b, | |||
0x49, 0x2d, 0xca, 0xcd, 0xcc, 0x2b, 0xd1, 0x2f, 0x28, 0xca, 0x2c, 0x2b, 0x4b, 0xcc, 0xd1, 0x2f, | |||
0x4e, 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x42, 0xa8, | |||
0xd0, 0x83, 0xaa, 0x90, 0x92, 0xc3, 0xa2, 0xab, 0xa4, 0xb2, 0x20, 0xb5, 0x18, 0xa2, 0xc7, 0x68, | |||
0x09, 0x13, 0x97, 0x40, 0x40, 0x51, 0x66, 0x59, 0x58, 0x62, 0x4e, 0x66, 0x4a, 0x62, 0x49, 0x7e, | |||
0x91, 0x63, 0x80, 0xa7, 0x50, 0x10, 0x17, 0xa7, 0x7b, 0x6a, 0x49, 0x40, 0x69, 0x92, 0x77, 0x6a, | |||
0xa5, 0x90, 0xa2, 0x1e, 0xa6, 0xb1, 0x7a, 0x10, 0xb9, 0xa0, 0xd4, 0xc2, 0xd2, 0xd4, 0xe2, 0x12, | |||
0x29, 0x25, 0x7c, 0x4a, 0x8a, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x85, 0xc2, 0xb9, 0x38, 0x82, 0x33, | |||
0xd3, 0xf3, 0xc2, 0xf2, 0x4b, 0x52, 0x85, 0x94, 0xb1, 0xa9, 0x87, 0xc9, 0xc2, 0x0c, 0x55, 0xc3, | |||
0xa5, 0x28, 0x35, 0x05, 0xa2, 0x0c, 0x6a, 0x70, 0x32, 0x17, 0x0f, 0x48, 0x34, 0xa0, 0x28, 0xbf, | |||
0x20, 0xbf, 0x38, 0x31, 0x47, 0x48, 0x1d, 0x97, 0x3e, 0x98, 0x0a, 0x98, 0x05, 0x5a, 0xb8, 0x2d, | |||
0x40, 0x28, 0x85, 0x58, 0xe2, 0x14, 0x7c, 0xe2, 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, 0x0f, | |||
0x1e, 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, 0xb1, 0x1c, 0xc3, 0x8d, 0xc7, 0x72, 0x0c, | |||
0x51, 0x96, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0xfa, 0x48, 0x61, 0x8d, | |||
0x12, 0xec, 0xf9, 0x25, 0xf9, 0xfa, 0x98, 0xf1, 0x90, 0xc4, 0x06, 0x96, 0x31, 0x06, 0x04, 0x00, | |||
0x00, 0xff, 0xff, 0x42, 0x60, 0x24, 0x48, 0xda, 0x01, 0x00, 0x00, | |||
} | |||
// Reference imports to suppress errors if they are not otherwise used. | |||
var _ context.Context | |||
var _ grpc.ClientConn | |||
// This is a compile-time assertion to ensure that this generated file | |||
// is compatible with the grpc package it is being compiled against. | |||
const _ = grpc.SupportPackageIsVersion4 | |||
// PrivValidatorAPIClient is the client API for PrivValidatorAPI service. | |||
// | |||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. | |||
type PrivValidatorAPIClient interface { | |||
GetPubKey(ctx context.Context, in *PubKeyRequest, opts ...grpc.CallOption) (*PubKeyResponse, error) | |||
SignVote(ctx context.Context, in *SignVoteRequest, opts ...grpc.CallOption) (*SignedVoteResponse, error) | |||
SignProposal(ctx context.Context, in *SignProposalRequest, opts ...grpc.CallOption) (*SignedProposalResponse, error) | |||
} | |||
type privValidatorAPIClient struct { | |||
cc *grpc.ClientConn | |||
} | |||
func NewPrivValidatorAPIClient(cc *grpc.ClientConn) PrivValidatorAPIClient { | |||
return &privValidatorAPIClient{cc} | |||
} | |||
func (c *privValidatorAPIClient) GetPubKey(ctx context.Context, in *PubKeyRequest, opts ...grpc.CallOption) (*PubKeyResponse, error) { | |||
out := new(PubKeyResponse) | |||
err := c.cc.Invoke(ctx, "/tendermint.privval.PrivValidatorAPI/GetPubKey", in, out, opts...) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return out, nil | |||
} | |||
func (c *privValidatorAPIClient) SignVote(ctx context.Context, in *SignVoteRequest, opts ...grpc.CallOption) (*SignedVoteResponse, error) { | |||
out := new(SignedVoteResponse) | |||
err := c.cc.Invoke(ctx, "/tendermint.privval.PrivValidatorAPI/SignVote", in, out, opts...) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return out, nil | |||
} | |||
func (c *privValidatorAPIClient) SignProposal(ctx context.Context, in *SignProposalRequest, opts ...grpc.CallOption) (*SignedProposalResponse, error) { | |||
out := new(SignedProposalResponse) | |||
err := c.cc.Invoke(ctx, "/tendermint.privval.PrivValidatorAPI/SignProposal", in, out, opts...) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return out, nil | |||
} | |||
// PrivValidatorAPIServer is the server API for PrivValidatorAPI service. | |||
type PrivValidatorAPIServer interface { | |||
GetPubKey(context.Context, *PubKeyRequest) (*PubKeyResponse, error) | |||
SignVote(context.Context, *SignVoteRequest) (*SignedVoteResponse, error) | |||
SignProposal(context.Context, *SignProposalRequest) (*SignedProposalResponse, error) | |||
} | |||
// UnimplementedPrivValidatorAPIServer can be embedded to have forward compatible implementations. | |||
type UnimplementedPrivValidatorAPIServer struct { | |||
} | |||
func (*UnimplementedPrivValidatorAPIServer) GetPubKey(ctx context.Context, req *PubKeyRequest) (*PubKeyResponse, error) { | |||
return nil, status.Errorf(codes.Unimplemented, "method GetPubKey not implemented") | |||
} | |||
func (*UnimplementedPrivValidatorAPIServer) SignVote(ctx context.Context, req *SignVoteRequest) (*SignedVoteResponse, error) { | |||
return nil, status.Errorf(codes.Unimplemented, "method SignVote not implemented") | |||
} | |||
func (*UnimplementedPrivValidatorAPIServer) SignProposal(ctx context.Context, req *SignProposalRequest) (*SignedProposalResponse, error) { | |||
return nil, status.Errorf(codes.Unimplemented, "method SignProposal not implemented") | |||
} | |||
func RegisterPrivValidatorAPIServer(s *grpc.Server, srv PrivValidatorAPIServer) { | |||
s.RegisterService(&_PrivValidatorAPI_serviceDesc, srv) | |||
} | |||
func _PrivValidatorAPI_GetPubKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | |||
in := new(PubKeyRequest) | |||
if err := dec(in); err != nil { | |||
return nil, err | |||
} | |||
if interceptor == nil { | |||
return srv.(PrivValidatorAPIServer).GetPubKey(ctx, in) | |||
} | |||
info := &grpc.UnaryServerInfo{ | |||
Server: srv, | |||
FullMethod: "/tendermint.privval.PrivValidatorAPI/GetPubKey", | |||
} | |||
handler := func(ctx context.Context, req interface{}) (interface{}, error) { | |||
return srv.(PrivValidatorAPIServer).GetPubKey(ctx, req.(*PubKeyRequest)) | |||
} | |||
return interceptor(ctx, in, info, handler) | |||
} | |||
func _PrivValidatorAPI_SignVote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | |||
in := new(SignVoteRequest) | |||
if err := dec(in); err != nil { | |||
return nil, err | |||
} | |||
if interceptor == nil { | |||
return srv.(PrivValidatorAPIServer).SignVote(ctx, in) | |||
} | |||
info := &grpc.UnaryServerInfo{ | |||
Server: srv, | |||
FullMethod: "/tendermint.privval.PrivValidatorAPI/SignVote", | |||
} | |||
handler := func(ctx context.Context, req interface{}) (interface{}, error) { | |||
return srv.(PrivValidatorAPIServer).SignVote(ctx, req.(*SignVoteRequest)) | |||
} | |||
return interceptor(ctx, in, info, handler) | |||
} | |||
func _PrivValidatorAPI_SignProposal_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | |||
in := new(SignProposalRequest) | |||
if err := dec(in); err != nil { | |||
return nil, err | |||
} | |||
if interceptor == nil { | |||
return srv.(PrivValidatorAPIServer).SignProposal(ctx, in) | |||
} | |||
info := &grpc.UnaryServerInfo{ | |||
Server: srv, | |||
FullMethod: "/tendermint.privval.PrivValidatorAPI/SignProposal", | |||
} | |||
handler := func(ctx context.Context, req interface{}) (interface{}, error) { | |||
return srv.(PrivValidatorAPIServer).SignProposal(ctx, req.(*SignProposalRequest)) | |||
} | |||
return interceptor(ctx, in, info, handler) | |||
} | |||
var _PrivValidatorAPI_serviceDesc = grpc.ServiceDesc{ | |||
ServiceName: "tendermint.privval.PrivValidatorAPI", | |||
HandlerType: (*PrivValidatorAPIServer)(nil), | |||
Methods: []grpc.MethodDesc{ | |||
{ | |||
MethodName: "GetPubKey", | |||
Handler: _PrivValidatorAPI_GetPubKey_Handler, | |||
}, | |||
{ | |||
MethodName: "SignVote", | |||
Handler: _PrivValidatorAPI_SignVote_Handler, | |||
}, | |||
{ | |||
MethodName: "SignProposal", | |||
Handler: _PrivValidatorAPI_SignProposal_Handler, | |||
}, | |||
}, | |||
Streams: []grpc.StreamDesc{}, | |||
Metadata: "tendermint/privval/service.proto", | |||
} |
@ -0,0 +1,14 @@ | |||
syntax = "proto3"; | |||
package tendermint.privval; | |||
option go_package = "github.com/tendermint/tendermint/proto/tendermint/privval"; | |||
import "tendermint/privval/types.proto"; | |||
//---------------------------------------- | |||
// Service Definition | |||
service PrivValidatorAPI { | |||
rpc GetPubKey(PubKeyRequest) returns (PubKeyResponse); | |||
rpc SignVote(SignVoteRequest) returns (SignedVoteResponse); | |||
rpc SignProposal(SignProposalRequest) returns (SignedProposalResponse); | |||
} |