github.com/mydexchain/tendermint@v0.0.4/privval/signer_listener_endpoint.go (about) 1 package privval 2 3 import ( 4 "fmt" 5 "net" 6 "time" 7 8 "github.com/mydexchain/tendermint/libs/log" 9 "github.com/mydexchain/tendermint/libs/service" 10 tmsync "github.com/mydexchain/tendermint/libs/sync" 11 privvalproto "github.com/mydexchain/tendermint/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 }