package privval import ( "net" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/libs/log" tmnet "github.com/tendermint/tendermint/libs/net" tmrand "github.com/tendermint/tendermint/libs/rand" "github.com/tendermint/tendermint/types" ) var ( testTimeoutAccept = defaultTimeoutAcceptSeconds * time.Second testTimeoutReadWrite = 100 * time.Millisecond testTimeoutReadWrite2o3 = 60 * time.Millisecond // 2/3 of the other one ) type dialerTestCase struct { addr string dialer SocketDialer } // TestSignerRemoteRetryTCPOnly will test connection retry attempts over TCP. We // don't need this for Unix sockets because the OS instantly knows the state of // both ends of the socket connection. This basically causes the // SignerDialerEndpoint.dialer() call inside SignerDialerEndpoint.acceptNewConnection() to return // successfully immediately, putting an instant stop to any retry attempts. func TestSignerRemoteRetryTCPOnly(t *testing.T) { var ( attemptCh = make(chan int) retries = 10 ) ln, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err) // Continuously Accept connection and close {attempts} times go func(ln net.Listener, attemptCh chan<- int) { attempts := 0 for { conn, err := ln.Accept() require.NoError(t, err) err = conn.Close() require.NoError(t, err) attempts++ if attempts == retries { attemptCh <- attempts break } } }(ln, attemptCh) dialerEndpoint := NewSignerDialerEndpoint( log.TestingLogger(), DialTCPFn(ln.Addr().String(), testTimeoutReadWrite, ed25519.GenPrivKey()), ) SignerDialerEndpointTimeoutReadWrite(time.Millisecond)(dialerEndpoint) SignerDialerEndpointConnRetries(retries)(dialerEndpoint) chainID := tmrand.Str(12) mockPV := types.NewMockPV() signerServer := NewSignerServer(dialerEndpoint, chainID, mockPV) err = signerServer.Start() require.NoError(t, err) t.Cleanup(func() { if err := signerServer.Stop(); err != nil { t.Error(err) } }) select { case attempts := <-attemptCh: assert.Equal(t, retries, attempts) case <-time.After(1500 * time.Millisecond): t.Error("expected remote to observe connection attempts") } } func TestRetryConnToRemoteSigner(t *testing.T) { for _, tc := range getDialerTestCases(t) { var ( logger = log.TestingLogger() chainID = tmrand.Str(12) mockPV = types.NewMockPV() endpointIsOpenCh = make(chan struct{}) thisConnTimeout = testTimeoutReadWrite listenerEndpoint = newSignerListenerEndpoint(logger, tc.addr, thisConnTimeout) ) dialerEndpoint := NewSignerDialerEndpoint( logger, tc.dialer, ) SignerDialerEndpointTimeoutReadWrite(testTimeoutReadWrite)(dialerEndpoint) SignerDialerEndpointConnRetries(10)(dialerEndpoint) signerServer := NewSignerServer(dialerEndpoint, chainID, mockPV) startListenerEndpointAsync(t, listenerEndpoint, endpointIsOpenCh) t.Cleanup(func() { if err := listenerEndpoint.Stop(); err != nil { t.Error(err) } }) require.NoError(t, signerServer.Start()) assert.True(t, signerServer.IsRunning()) <-endpointIsOpenCh if err := signerServer.Stop(); err != nil { t.Error(err) } dialerEndpoint2 := NewSignerDialerEndpoint( logger, tc.dialer, ) signerServer2 := NewSignerServer(dialerEndpoint2, chainID, mockPV) // let some pings pass require.NoError(t, signerServer2.Start()) assert.True(t, signerServer2.IsRunning()) t.Cleanup(func() { if err := signerServer2.Stop(); err != nil { t.Error(err) } }) // 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(testTimeoutReadWrite * 2) } } func newSignerListenerEndpoint(logger log.Logger, addr string, timeoutReadWrite time.Duration) *SignerListenerEndpoint { proto, address := tmnet.ProtocolAndAddress(addr) ln, err := net.Listen(proto, address) logger.Info("SignerListener: Listening", "proto", proto, "address", address) if err != nil { panic(err) } var listener net.Listener if proto == "unix" { unixLn := NewUnixListener(ln) UnixListenerTimeoutAccept(testTimeoutAccept)(unixLn) UnixListenerTimeoutReadWrite(timeoutReadWrite)(unixLn) listener = unixLn } else { tcpLn := NewTCPListener(ln, ed25519.GenPrivKey()) TCPListenerTimeoutAccept(testTimeoutAccept)(tcpLn) TCPListenerTimeoutReadWrite(timeoutReadWrite)(tcpLn) listener = tcpLn } return NewSignerListenerEndpoint( logger, listener, SignerListenerEndpointTimeoutReadWrite(testTimeoutReadWrite), ) } func startListenerEndpointAsync(t *testing.T, sle *SignerListenerEndpoint, endpointIsOpenCh chan struct{}) { go func(sle *SignerListenerEndpoint) { require.NoError(t, sle.Start()) assert.True(t, sle.IsRunning()) close(endpointIsOpenCh) }(sle) } func getMockEndpoints( t *testing.T, addr string, socketDialer SocketDialer, ) (*SignerListenerEndpoint, *SignerDialerEndpoint) { var ( logger = log.TestingLogger() endpointIsOpenCh = make(chan struct{}) dialerEndpoint = NewSignerDialerEndpoint( logger, socketDialer, ) listenerEndpoint = newSignerListenerEndpoint(logger, addr, testTimeoutReadWrite) ) SignerDialerEndpointTimeoutReadWrite(testTimeoutReadWrite)(dialerEndpoint) SignerDialerEndpointConnRetries(1e6)(dialerEndpoint) startListenerEndpointAsync(t, listenerEndpoint, endpointIsOpenCh) require.NoError(t, dialerEndpoint.Start()) assert.True(t, dialerEndpoint.IsRunning()) <-endpointIsOpenCh return listenerEndpoint, dialerEndpoint }