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"
|
|
|
|
p2pconn "github.com/tendermint/tendermint/p2p/conn"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
func TestSocketPVAddress(t *testing.T) {
|
|
var (
|
|
chainID = cmn.RandStr(12)
|
|
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV())
|
|
)
|
|
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) {
|
|
var (
|
|
chainID = cmn.RandStr(12)
|
|
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV())
|
|
)
|
|
defer sc.Stop()
|
|
defer rs.Stop()
|
|
|
|
clientKey, err := sc.getPubKey()
|
|
require.NoError(t, err)
|
|
|
|
privvalPubKey := rs.privVal.GetPubKey()
|
|
|
|
assert.Equal(t, privvalPubKey, clientKey)
|
|
}
|
|
|
|
func TestSocketPVProposal(t *testing.T) {
|
|
var (
|
|
chainID = cmn.RandStr(12)
|
|
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV())
|
|
|
|
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) {
|
|
var (
|
|
chainID = cmn.RandStr(12)
|
|
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV())
|
|
|
|
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) {
|
|
var (
|
|
chainID = cmn.RandStr(12)
|
|
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV())
|
|
|
|
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(3 * time.Millisecond)
|
|
|
|
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(3 * time.Millisecond)
|
|
|
|
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) {
|
|
var (
|
|
chainID = cmn.RandStr(12)
|
|
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV())
|
|
|
|
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(10 * time.Millisecond)
|
|
|
|
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) {
|
|
var (
|
|
addr = testFreeAddr(t)
|
|
listenc = make(chan struct{})
|
|
sc = NewTCPVal(
|
|
log.TestingLogger(),
|
|
addr,
|
|
ed25519.GenPrivKey(),
|
|
)
|
|
)
|
|
|
|
TCPValConnTimeout(100 * time.Millisecond)(sc)
|
|
|
|
go func(sc *TCPVal) {
|
|
defer close(listenc)
|
|
|
|
assert.Equal(t, sc.Start().(cmn.Error).Data(), ErrConnTimeout)
|
|
|
|
assert.False(t, sc.IsRunning())
|
|
}(sc)
|
|
|
|
for {
|
|
conn, err := cmn.Connect(addr)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
_, err = p2pconn.MakeSecretConnection(
|
|
conn,
|
|
ed25519.GenPrivKey(),
|
|
)
|
|
if err == nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
<-listenc
|
|
}
|
|
|
|
func TestRemoteSignerRetry(t *testing.T) {
|
|
var (
|
|
attemptc = make(chan int)
|
|
retries = 2
|
|
)
|
|
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
require.NoError(t, err)
|
|
|
|
go func(ln net.Listener, attemptc chan<- int) {
|
|
attempts := 0
|
|
|
|
for {
|
|
conn, err := ln.Accept()
|
|
require.NoError(t, err)
|
|
|
|
err = conn.Close()
|
|
require.NoError(t, err)
|
|
|
|
attempts++
|
|
|
|
if attempts == retries {
|
|
attemptc <- attempts
|
|
break
|
|
}
|
|
}
|
|
}(ln, attemptc)
|
|
|
|
rs := NewRemoteSigner(
|
|
log.TestingLogger(),
|
|
cmn.RandStr(12),
|
|
ln.Addr().String(),
|
|
types.NewMockPV(),
|
|
ed25519.GenPrivKey(),
|
|
)
|
|
defer rs.Stop()
|
|
|
|
RemoteSignerConnDeadline(time.Millisecond)(rs)
|
|
RemoteSignerConnRetries(retries)(rs)
|
|
|
|
assert.Equal(t, rs.Start(), ErrDialRetryMax)
|
|
|
|
select {
|
|
case attempts := <-attemptc:
|
|
assert.Equal(t, retries, attempts)
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Error("expected remote to observe connection attempts")
|
|
}
|
|
}
|
|
|
|
func TestRemoteSignVoteErrors(t *testing.T) {
|
|
var (
|
|
chainID = cmn.RandStr(12)
|
|
sc, rs = testSetupSocketPair(t, chainID, types.NewErroringMockPV())
|
|
|
|
ts = time.Now()
|
|
vType = types.PrecommitType
|
|
vote = &types.Vote{Timestamp: ts, Type: vType}
|
|
)
|
|
defer sc.Stop()
|
|
defer rs.Stop()
|
|
|
|
err := writeMsg(sc.conn, &SignVoteRequest{Vote: vote})
|
|
require.NoError(t, err)
|
|
|
|
res, err := readMsg(sc.conn)
|
|
require.NoError(t, err)
|
|
|
|
resp := *res.(*SignedVoteResponse)
|
|
require.NotNil(t, resp.Error)
|
|
require.Equal(t, resp.Error.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) {
|
|
var (
|
|
chainID = cmn.RandStr(12)
|
|
sc, rs = testSetupSocketPair(t, chainID, types.NewErroringMockPV())
|
|
|
|
ts = time.Now()
|
|
proposal = &types.Proposal{Timestamp: ts}
|
|
)
|
|
defer sc.Stop()
|
|
defer rs.Stop()
|
|
|
|
err := writeMsg(sc.conn, &SignProposalRequest{Proposal: proposal})
|
|
require.NoError(t, err)
|
|
|
|
res, err := readMsg(sc.conn)
|
|
require.NoError(t, err)
|
|
|
|
resp := *res.(*SignedProposalResponse)
|
|
require.NotNil(t, resp.Error)
|
|
require.Equal(t, resp.Error.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) {
|
|
var (
|
|
addr = testFreeAddr(t)
|
|
logger = log.TestingLogger()
|
|
chainID = cmn.RandStr(12)
|
|
readyc = make(chan struct{})
|
|
errc = make(chan error, 1)
|
|
|
|
rs = NewRemoteSigner(
|
|
logger,
|
|
chainID,
|
|
addr,
|
|
types.NewMockPV(),
|
|
ed25519.GenPrivKey(),
|
|
)
|
|
sc = NewTCPVal(
|
|
logger,
|
|
addr,
|
|
ed25519.GenPrivKey(),
|
|
)
|
|
)
|
|
|
|
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 testSetupSocketPair(
|
|
t *testing.T,
|
|
chainID string,
|
|
privValidator types.PrivValidator,
|
|
) (*TCPVal, *RemoteSigner) {
|
|
var (
|
|
addr = testFreeAddr(t)
|
|
logger = log.TestingLogger()
|
|
privVal = privValidator
|
|
readyc = make(chan struct{})
|
|
rs = NewRemoteSigner(
|
|
logger,
|
|
chainID,
|
|
addr,
|
|
privVal,
|
|
ed25519.GenPrivKey(),
|
|
)
|
|
sc = NewTCPVal(
|
|
logger,
|
|
addr,
|
|
ed25519.GenPrivKey(),
|
|
)
|
|
)
|
|
|
|
TCPValConnTimeout(5 * time.Millisecond)(sc)
|
|
TCPValHeartbeat(2 * time.Millisecond)(sc)
|
|
RemoteSignerConnDeadline(5 * time.Millisecond)(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 *TCPVal) {
|
|
go func(sc *TCPVal) {
|
|
require.NoError(t, sc.Start())
|
|
assert.True(t, sc.IsRunning())
|
|
|
|
readyc <- struct{}{}
|
|
}(sc)
|
|
}
|
|
|
|
// testFreeAddr claims a free port so we don't block on listener being ready.
|
|
func testFreeAddr(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)
|
|
}
|