github.com/DFWallet/tendermint-cosmos@v0.0.2/privval/signer_listener_endpoint.go (about)

     1  package privval
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"time"
     7  
     8  	"github.com/DFWallet/tendermint-cosmos/libs/log"
     9  	"github.com/DFWallet/tendermint-cosmos/libs/service"
    10  	tmsync "github.com/DFWallet/tendermint-cosmos/libs/sync"
    11  	privvalproto "github.com/DFWallet/tendermint-cosmos/proto/tendermint/privval"
    12  )
    13  
    14  // SignerListenerEndpointOption sets an optional parameter on the SignerListenerEndpoint.
    15  type SignerListenerEndpointOption func(*SignerListenerEndpoint)
    16  
    17  // SignerListenerEndpointTimeoutReadWrite sets the read and write timeout for
    18  // connections from external signing processes.
    19  //
    20  // Default: 5s
    21  func SignerListenerEndpointTimeoutReadWrite(timeout time.Duration) SignerListenerEndpointOption {
    22  	return func(sl *SignerListenerEndpoint) { sl.signerEndpoint.timeoutReadWrite = timeout }
    23  }
    24  
    25  // SignerListenerEndpoint listens for an external process to dial in and keeps
    26  // the connection alive by dropping and reconnecting.
    27  //
    28  // The process will send pings every ~3s (read/write timeout * 2/3) to keep the
    29  // connection alive.
    30  type SignerListenerEndpoint struct {
    31  	signerEndpoint
    32  
    33  	listener              net.Listener
    34  	connectRequestCh      chan struct{}
    35  	connectionAvailableCh chan net.Conn
    36  
    37  	timeoutAccept time.Duration
    38  	pingTimer     *time.Ticker
    39  	pingInterval  time.Duration
    40  
    41  	instanceMtx tmsync.Mutex // Ensures instance public methods access, i.e. SendRequest
    42  }
    43  
    44  // NewSignerListenerEndpoint returns an instance of SignerListenerEndpoint.
    45  func NewSignerListenerEndpoint(
    46  	logger log.Logger,
    47  	listener net.Listener,
    48  	options ...SignerListenerEndpointOption,
    49  ) *SignerListenerEndpoint {
    50  	sl := &SignerListenerEndpoint{
    51  		listener:      listener,
    52  		timeoutAccept: defaultTimeoutAcceptSeconds * time.Second,
    53  	}
    54  
    55  	sl.BaseService = *service.NewBaseService(logger, "SignerListenerEndpoint", sl)
    56  	sl.signerEndpoint.timeoutReadWrite = defaultTimeoutReadWriteSeconds * time.Second
    57  
    58  	for _, optionFunc := range options {
    59  		optionFunc(sl)
    60  	}
    61  
    62  	return sl
    63  }
    64  
    65  // OnStart implements service.Service.
    66  func (sl *SignerListenerEndpoint) OnStart() error {
    67  	sl.connectRequestCh = make(chan struct{})
    68  	sl.connectionAvailableCh = make(chan net.Conn)
    69  
    70  	// NOTE: ping timeout must be less than read/write timeout
    71  	sl.pingInterval = time.Duration(sl.signerEndpoint.timeoutReadWrite.Milliseconds()*2/3) * time.Millisecond
    72  	sl.pingTimer = time.NewTicker(sl.pingInterval)
    73  
    74  	go sl.serviceLoop()
    75  	go sl.pingLoop()
    76  
    77  	sl.connectRequestCh <- struct{}{}
    78  
    79  	return nil
    80  }
    81  
    82  // OnStop implements service.Service
    83  func (sl *SignerListenerEndpoint) OnStop() {
    84  	sl.instanceMtx.Lock()
    85  	defer sl.instanceMtx.Unlock()
    86  	_ = sl.Close()
    87  
    88  	// Stop listening
    89  	if sl.listener != nil {
    90  		if err := sl.listener.Close(); err != nil {
    91  			sl.Logger.Error("Closing Listener", "err", err)
    92  			sl.listener = nil
    93  		}
    94  	}
    95  
    96  	sl.pingTimer.Stop()
    97  }
    98  
    99  // WaitForConnection waits maxWait for a connection or returns a timeout error
   100  func (sl *SignerListenerEndpoint) WaitForConnection(maxWait time.Duration) error {
   101  	sl.instanceMtx.Lock()
   102  	defer sl.instanceMtx.Unlock()
   103  	return sl.ensureConnection(maxWait)
   104  }
   105  
   106  // SendRequest ensures there is a connection, sends a request and waits for a response
   107  func (sl *SignerListenerEndpoint) SendRequest(request privvalproto.Message) (*privvalproto.Message, error) {
   108  	sl.instanceMtx.Lock()
   109  	defer sl.instanceMtx.Unlock()
   110  
   111  	err := sl.ensureConnection(sl.timeoutAccept)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	err = sl.WriteMessage(request)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	res, err := sl.ReadMessage()
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	// Reset pingTimer to avoid sending unnecessary pings.
   127  	sl.pingTimer.Reset(sl.pingInterval)
   128  
   129  	return &res, nil
   130  }
   131  
   132  func (sl *SignerListenerEndpoint) ensureConnection(maxWait time.Duration) error {
   133  	if sl.IsConnected() {
   134  		return nil
   135  	}
   136  
   137  	// Is there a connection ready? then use it
   138  	if sl.GetAvailableConnection(sl.connectionAvailableCh) {
   139  		return nil
   140  	}
   141  
   142  	// block until connected or timeout
   143  	sl.Logger.Info("SignerListener: Blocking for connection")
   144  	sl.triggerConnect()
   145  	err := sl.WaitConnection(sl.connectionAvailableCh, maxWait)
   146  	if err != nil {
   147  		return err
   148  	}
   149  
   150  	return nil
   151  }
   152  
   153  func (sl *SignerListenerEndpoint) acceptNewConnection() (net.Conn, error) {
   154  	if !sl.IsRunning() || sl.listener == nil {
   155  		return nil, fmt.Errorf("endpoint is closing")
   156  	}
   157  
   158  	// wait for a new conn
   159  	sl.Logger.Info("SignerListener: Listening for new connection")
   160  	conn, err := sl.listener.Accept()
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	return conn, nil
   166  }
   167  
   168  func (sl *SignerListenerEndpoint) triggerConnect() {
   169  	select {
   170  	case sl.connectRequestCh <- struct{}{}:
   171  	default:
   172  	}
   173  }
   174  
   175  func (sl *SignerListenerEndpoint) triggerReconnect() {
   176  	sl.DropConnection()
   177  	sl.triggerConnect()
   178  }
   179  
   180  func (sl *SignerListenerEndpoint) serviceLoop() {
   181  	for {
   182  		select {
   183  		case <-sl.connectRequestCh:
   184  			{
   185  				conn, err := sl.acceptNewConnection()
   186  				if err == nil {
   187  					sl.Logger.Info("SignerListener: Connected")
   188  
   189  					// We have a good connection, wait for someone that needs one otherwise cancellation
   190  					select {
   191  					case sl.connectionAvailableCh <- conn:
   192  					case <-sl.Quit():
   193  						return
   194  					}
   195  				}
   196  
   197  				select {
   198  				case sl.connectRequestCh <- struct{}{}:
   199  				default:
   200  				}
   201  			}
   202  		case <-sl.Quit():
   203  			return
   204  		}
   205  	}
   206  }
   207  
   208  func (sl *SignerListenerEndpoint) pingLoop() {
   209  	for {
   210  		select {
   211  		case <-sl.pingTimer.C:
   212  			{
   213  				_, err := sl.SendRequest(mustWrapMsg(&privvalproto.PingRequest{}))
   214  				if err != nil {
   215  					sl.Logger.Error("SignerListener: Ping timeout")
   216  					sl.triggerReconnect()
   217  				}
   218  			}
   219  		case <-sl.Quit():
   220  			return
   221  		}
   222  	}
   223  }