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 }