mirror of
https://github.com/fluencelabs/tendermint
synced 2025-04-24 22:32:15 +00:00
* Consolidates deadline tests for privval Unix/TCP Following on from #3115 and #3132, this converts fundamental timeout errors from the client's `acceptConnection()` method so that these can be detected by the test for the TCP connection. Timeout deadlines are now tested for both TCP and Unix domain socket connections. There is also no need for the additional secret connection code: the connection will time out at the `acceptConnection()` phase for TCP connections, and will time out when attempting to obtain the `RemoteSigner`'s public key for Unix domain socket connections. * Removes extraneous logging * Adds IsConnTimeout helper function This commit adds a helper function to detect whether an error is either a fundamental networking timeout error, or an `ErrConnTimeout` error specific to the `RemoteSigner` class. * Adds a test for the IsConnTimeout() helper function * Separates tests logically for IsConnTimeout
462 lines
12 KiB
Go
462 lines
12 KiB
Go
package privval
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/tendermint/tendermint/crypto/ed25519"
|
|
cmn "github.com/tendermint/tendermint/libs/common"
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
var (
|
|
testAcceptDeadline = defaultAcceptDeadlineSeconds * time.Second
|
|
|
|
testConnDeadline = 100 * time.Millisecond
|
|
testConnDeadline2o3 = 66 * time.Millisecond // 2/3 of the other one
|
|
|
|
testHeartbeatTimeout = 10 * time.Millisecond
|
|
testHeartbeatTimeout3o2 = 6 * time.Millisecond // 3/2 of the other one
|
|
)
|
|
|
|
type socketTestCase struct {
|
|
addr string
|
|
dialer Dialer
|
|
}
|
|
|
|
func socketTestCases(t *testing.T) []socketTestCase {
|
|
tcpAddr := fmt.Sprintf("tcp://%s", testFreeTCPAddr(t))
|
|
unixFilePath, err := testUnixAddr()
|
|
require.NoError(t, err)
|
|
unixAddr := fmt.Sprintf("unix://%s", unixFilePath)
|
|
return []socketTestCase{
|
|
socketTestCase{
|
|
addr: tcpAddr,
|
|
dialer: DialTCPFn(tcpAddr, testConnDeadline, ed25519.GenPrivKey()),
|
|
},
|
|
socketTestCase{
|
|
addr: unixAddr,
|
|
dialer: DialUnixFn(unixFilePath),
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestSocketPVAddress(t *testing.T) {
|
|
for _, tc := range socketTestCases(t) {
|
|
// Execute the test within a closure to ensure the deferred statements
|
|
// are called between each for loop iteration, for isolated test cases.
|
|
func() {
|
|
var (
|
|
chainID = cmn.RandStr(12)
|
|
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer)
|
|
)
|
|
defer sc.Stop()
|
|
defer rs.Stop()
|
|
|
|
serverAddr := rs.privVal.GetPubKey().Address()
|
|
clientAddr := sc.GetPubKey().Address()
|
|
|
|
assert.Equal(t, serverAddr, clientAddr)
|
|
}()
|
|
}
|
|
}
|
|
|
|
func TestSocketPVPubKey(t *testing.T) {
|
|
for _, tc := range socketTestCases(t) {
|
|
func() {
|
|
var (
|
|
chainID = cmn.RandStr(12)
|
|
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer)
|
|
)
|
|
defer sc.Stop()
|
|
defer rs.Stop()
|
|
|
|
clientKey := sc.GetPubKey()
|
|
|
|
privvalPubKey := rs.privVal.GetPubKey()
|
|
|
|
assert.Equal(t, privvalPubKey, clientKey)
|
|
}()
|
|
}
|
|
}
|
|
|
|
func TestSocketPVProposal(t *testing.T) {
|
|
for _, tc := range socketTestCases(t) {
|
|
func() {
|
|
var (
|
|
chainID = cmn.RandStr(12)
|
|
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer)
|
|
|
|
ts = time.Now()
|
|
privProposal = &types.Proposal{Timestamp: ts}
|
|
clientProposal = &types.Proposal{Timestamp: ts}
|
|
)
|
|
defer sc.Stop()
|
|
defer rs.Stop()
|
|
|
|
require.NoError(t, rs.privVal.SignProposal(chainID, privProposal))
|
|
require.NoError(t, sc.SignProposal(chainID, clientProposal))
|
|
assert.Equal(t, privProposal.Signature, clientProposal.Signature)
|
|
}()
|
|
}
|
|
}
|
|
|
|
func TestSocketPVVote(t *testing.T) {
|
|
for _, tc := range socketTestCases(t) {
|
|
func() {
|
|
var (
|
|
chainID = cmn.RandStr(12)
|
|
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer)
|
|
|
|
ts = time.Now()
|
|
vType = types.PrecommitType
|
|
want = &types.Vote{Timestamp: ts, Type: vType}
|
|
have = &types.Vote{Timestamp: ts, Type: vType}
|
|
)
|
|
defer sc.Stop()
|
|
defer rs.Stop()
|
|
|
|
require.NoError(t, rs.privVal.SignVote(chainID, want))
|
|
require.NoError(t, sc.SignVote(chainID, have))
|
|
assert.Equal(t, want.Signature, have.Signature)
|
|
}()
|
|
}
|
|
}
|
|
|
|
func TestSocketPVVoteResetDeadline(t *testing.T) {
|
|
for _, tc := range socketTestCases(t) {
|
|
func() {
|
|
var (
|
|
chainID = cmn.RandStr(12)
|
|
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer)
|
|
|
|
ts = time.Now()
|
|
vType = types.PrecommitType
|
|
want = &types.Vote{Timestamp: ts, Type: vType}
|
|
have = &types.Vote{Timestamp: ts, Type: vType}
|
|
)
|
|
defer sc.Stop()
|
|
defer rs.Stop()
|
|
|
|
time.Sleep(testConnDeadline2o3)
|
|
|
|
require.NoError(t, rs.privVal.SignVote(chainID, want))
|
|
require.NoError(t, sc.SignVote(chainID, have))
|
|
assert.Equal(t, want.Signature, have.Signature)
|
|
|
|
// This would exceed the deadline if it was not extended by the previous message
|
|
time.Sleep(testConnDeadline2o3)
|
|
|
|
require.NoError(t, rs.privVal.SignVote(chainID, want))
|
|
require.NoError(t, sc.SignVote(chainID, have))
|
|
assert.Equal(t, want.Signature, have.Signature)
|
|
}()
|
|
}
|
|
}
|
|
|
|
func TestSocketPVVoteKeepalive(t *testing.T) {
|
|
for _, tc := range socketTestCases(t) {
|
|
func() {
|
|
var (
|
|
chainID = cmn.RandStr(12)
|
|
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer)
|
|
|
|
ts = time.Now()
|
|
vType = types.PrecommitType
|
|
want = &types.Vote{Timestamp: ts, Type: vType}
|
|
have = &types.Vote{Timestamp: ts, Type: vType}
|
|
)
|
|
defer sc.Stop()
|
|
defer rs.Stop()
|
|
|
|
time.Sleep(testConnDeadline * 2)
|
|
|
|
require.NoError(t, rs.privVal.SignVote(chainID, want))
|
|
require.NoError(t, sc.SignVote(chainID, have))
|
|
assert.Equal(t, want.Signature, have.Signature)
|
|
}()
|
|
}
|
|
}
|
|
|
|
func TestSocketPVDeadline(t *testing.T) {
|
|
for _, tc := range socketTestCases(t) {
|
|
func() {
|
|
var (
|
|
listenc = make(chan struct{})
|
|
thisConnTimeout = 100 * time.Millisecond
|
|
sc = newSocketVal(log.TestingLogger(), tc.addr, thisConnTimeout)
|
|
)
|
|
|
|
go func(sc *SocketVal) {
|
|
defer close(listenc)
|
|
|
|
// Note: the TCP connection times out at the accept() phase,
|
|
// whereas the Unix domain sockets connection times out while
|
|
// attempting to fetch the remote signer's public key.
|
|
assert.True(t, IsConnTimeout(sc.Start()))
|
|
|
|
assert.False(t, sc.IsRunning())
|
|
}(sc)
|
|
|
|
for {
|
|
_, err := cmn.Connect(tc.addr)
|
|
if err == nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
<-listenc
|
|
}()
|
|
}
|
|
}
|
|
|
|
func TestRemoteSignVoteErrors(t *testing.T) {
|
|
for _, tc := range socketTestCases(t) {
|
|
func() {
|
|
var (
|
|
chainID = cmn.RandStr(12)
|
|
sc, rs = testSetupSocketPair(t, chainID, types.NewErroringMockPV(), tc.addr, tc.dialer)
|
|
|
|
ts = time.Now()
|
|
vType = types.PrecommitType
|
|
vote = &types.Vote{Timestamp: ts, Type: vType}
|
|
)
|
|
defer sc.Stop()
|
|
defer rs.Stop()
|
|
|
|
err := sc.SignVote("", vote)
|
|
require.Equal(t, err.(*RemoteSignerError).Description, types.ErroringMockPVErr.Error())
|
|
|
|
err = rs.privVal.SignVote(chainID, vote)
|
|
require.Error(t, err)
|
|
err = sc.SignVote(chainID, vote)
|
|
require.Error(t, err)
|
|
}()
|
|
}
|
|
}
|
|
|
|
func TestRemoteSignProposalErrors(t *testing.T) {
|
|
for _, tc := range socketTestCases(t) {
|
|
func() {
|
|
var (
|
|
chainID = cmn.RandStr(12)
|
|
sc, rs = testSetupSocketPair(t, chainID, types.NewErroringMockPV(), tc.addr, tc.dialer)
|
|
|
|
ts = time.Now()
|
|
proposal = &types.Proposal{Timestamp: ts}
|
|
)
|
|
defer sc.Stop()
|
|
defer rs.Stop()
|
|
|
|
err := sc.SignProposal("", proposal)
|
|
require.Equal(t, err.(*RemoteSignerError).Description, types.ErroringMockPVErr.Error())
|
|
|
|
err = rs.privVal.SignProposal(chainID, proposal)
|
|
require.Error(t, err)
|
|
|
|
err = sc.SignProposal(chainID, proposal)
|
|
require.Error(t, err)
|
|
}()
|
|
}
|
|
}
|
|
|
|
func TestErrUnexpectedResponse(t *testing.T) {
|
|
for _, tc := range socketTestCases(t) {
|
|
func() {
|
|
var (
|
|
logger = log.TestingLogger()
|
|
chainID = cmn.RandStr(12)
|
|
readyc = make(chan struct{})
|
|
errc = make(chan error, 1)
|
|
|
|
rs = NewRemoteSigner(
|
|
logger,
|
|
chainID,
|
|
types.NewMockPV(),
|
|
tc.dialer,
|
|
)
|
|
sc = newSocketVal(logger, tc.addr, testConnDeadline)
|
|
)
|
|
|
|
testStartSocketPV(t, readyc, sc)
|
|
defer sc.Stop()
|
|
RemoteSignerConnDeadline(time.Millisecond)(rs)
|
|
RemoteSignerConnRetries(100)(rs)
|
|
// we do not want to Start() the remote signer here and instead use the connection to
|
|
// reply with intentionally wrong replies below:
|
|
rsConn, err := rs.connect()
|
|
defer rsConn.Close()
|
|
require.NoError(t, err)
|
|
require.NotNil(t, rsConn)
|
|
// send over public key to get the remote signer running:
|
|
go testReadWriteResponse(t, &PubKeyResponse{}, rsConn)
|
|
<-readyc
|
|
|
|
// Proposal:
|
|
go func(errc chan error) {
|
|
errc <- sc.SignProposal(chainID, &types.Proposal{})
|
|
}(errc)
|
|
// read request and write wrong response:
|
|
go testReadWriteResponse(t, &SignedVoteResponse{}, rsConn)
|
|
err = <-errc
|
|
require.Error(t, err)
|
|
require.Equal(t, err, ErrUnexpectedResponse)
|
|
|
|
// Vote:
|
|
go func(errc chan error) {
|
|
errc <- sc.SignVote(chainID, &types.Vote{})
|
|
}(errc)
|
|
// read request and write wrong response:
|
|
go testReadWriteResponse(t, &SignedProposalResponse{}, rsConn)
|
|
err = <-errc
|
|
require.Error(t, err)
|
|
require.Equal(t, err, ErrUnexpectedResponse)
|
|
}()
|
|
}
|
|
}
|
|
|
|
func TestRetryConnToRemoteSigner(t *testing.T) {
|
|
for _, tc := range socketTestCases(t) {
|
|
func() {
|
|
var (
|
|
logger = log.TestingLogger()
|
|
chainID = cmn.RandStr(12)
|
|
readyc = make(chan struct{})
|
|
|
|
rs = NewRemoteSigner(
|
|
logger,
|
|
chainID,
|
|
types.NewMockPV(),
|
|
tc.dialer,
|
|
)
|
|
thisConnTimeout = testConnDeadline
|
|
sc = newSocketVal(logger, tc.addr, thisConnTimeout)
|
|
)
|
|
// Ping every:
|
|
SocketValHeartbeat(testHeartbeatTimeout)(sc)
|
|
|
|
RemoteSignerConnDeadline(testConnDeadline)(rs)
|
|
RemoteSignerConnRetries(10)(rs)
|
|
|
|
testStartSocketPV(t, readyc, sc)
|
|
defer sc.Stop()
|
|
require.NoError(t, rs.Start())
|
|
assert.True(t, rs.IsRunning())
|
|
|
|
<-readyc
|
|
time.Sleep(testHeartbeatTimeout * 2)
|
|
|
|
rs.Stop()
|
|
rs2 := NewRemoteSigner(
|
|
logger,
|
|
chainID,
|
|
types.NewMockPV(),
|
|
tc.dialer,
|
|
)
|
|
// let some pings pass
|
|
time.Sleep(testHeartbeatTimeout3o2)
|
|
require.NoError(t, rs2.Start())
|
|
assert.True(t, rs2.IsRunning())
|
|
defer rs2.Stop()
|
|
|
|
// give the client some time to re-establish the conn to the remote signer
|
|
// should see sth like this in the logs:
|
|
//
|
|
// E[10016-01-10|17:12:46.128] Ping err="remote signer timed out"
|
|
// I[10016-01-10|17:16:42.447] Re-created connection to remote signer impl=SocketVal
|
|
time.Sleep(testConnDeadline * 2)
|
|
}()
|
|
}
|
|
}
|
|
|
|
func newSocketVal(logger log.Logger, addr string, connDeadline time.Duration) *SocketVal {
|
|
proto, address := cmn.ProtocolAndAddress(addr)
|
|
ln, err := net.Listen(proto, address)
|
|
logger.Info("Listening at", "proto", proto, "address", address)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
var svln net.Listener
|
|
if proto == "unix" {
|
|
unixLn := NewUnixListener(ln)
|
|
UnixListenerAcceptDeadline(testAcceptDeadline)(unixLn)
|
|
UnixListenerConnDeadline(connDeadline)(unixLn)
|
|
svln = unixLn
|
|
} else {
|
|
tcpLn := NewTCPListener(ln, ed25519.GenPrivKey())
|
|
TCPListenerAcceptDeadline(testAcceptDeadline)(tcpLn)
|
|
TCPListenerConnDeadline(connDeadline)(tcpLn)
|
|
svln = tcpLn
|
|
}
|
|
return NewSocketVal(logger, svln)
|
|
}
|
|
|
|
func testSetupSocketPair(
|
|
t *testing.T,
|
|
chainID string,
|
|
privValidator types.PrivValidator,
|
|
addr string,
|
|
dialer Dialer,
|
|
) (*SocketVal, *RemoteSigner) {
|
|
var (
|
|
logger = log.TestingLogger()
|
|
privVal = privValidator
|
|
readyc = make(chan struct{})
|
|
rs = NewRemoteSigner(
|
|
logger,
|
|
chainID,
|
|
privVal,
|
|
dialer,
|
|
)
|
|
|
|
thisConnTimeout = testConnDeadline
|
|
sc = newSocketVal(logger, addr, thisConnTimeout)
|
|
)
|
|
|
|
SocketValHeartbeat(testHeartbeatTimeout)(sc)
|
|
RemoteSignerConnDeadline(testConnDeadline)(rs)
|
|
RemoteSignerConnRetries(1e6)(rs)
|
|
|
|
testStartSocketPV(t, readyc, sc)
|
|
|
|
require.NoError(t, rs.Start())
|
|
assert.True(t, rs.IsRunning())
|
|
|
|
<-readyc
|
|
|
|
return sc, rs
|
|
}
|
|
|
|
func testReadWriteResponse(t *testing.T, resp RemoteSignerMsg, rsConn net.Conn) {
|
|
_, err := readMsg(rsConn)
|
|
require.NoError(t, err)
|
|
|
|
err = writeMsg(rsConn, resp)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func testStartSocketPV(t *testing.T, readyc chan struct{}, sc *SocketVal) {
|
|
go func(sc *SocketVal) {
|
|
require.NoError(t, sc.Start())
|
|
assert.True(t, sc.IsRunning())
|
|
|
|
readyc <- struct{}{}
|
|
}(sc)
|
|
}
|
|
|
|
// testFreeTCPAddr claims a free port so we don't block on listener being ready.
|
|
func testFreeTCPAddr(t *testing.T) string {
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
require.NoError(t, err)
|
|
defer ln.Close()
|
|
|
|
return fmt.Sprintf("127.0.0.1:%d", ln.Addr().(*net.TCPAddr).Port)
|
|
}
|