github.com/line/ostracon@v1.0.10-0.20230328032236-7f20145f065d/privval/signer_listener_endpoint.go (about)

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