package privval import ( "io" "net" "time" "github.com/tendermint/tendermint/crypto/ed25519" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" p2pconn "github.com/tendermint/tendermint/p2p/conn" "github.com/tendermint/tendermint/types" ) // RemoteSignerOption sets an optional parameter on the RemoteSigner. type RemoteSignerOption func(*RemoteSigner) // RemoteSignerConnDeadline sets the read and write deadline for connections // from external signing processes. func RemoteSignerConnDeadline(deadline time.Duration) RemoteSignerOption { return func(ss *RemoteSigner) { ss.connDeadline = deadline } } // RemoteSignerConnRetries sets the amount of attempted retries to connect. func RemoteSignerConnRetries(retries int) RemoteSignerOption { return func(ss *RemoteSigner) { ss.connRetries = retries } } // RemoteSigner implements PrivValidator by dialing to a socket. type RemoteSigner struct { cmn.BaseService addr string chainID string connDeadline time.Duration connRetries int privKey ed25519.PrivKeyEd25519 privVal types.PrivValidator conn net.Conn } // NewRemoteSigner returns an instance of RemoteSigner. func NewRemoteSigner( logger log.Logger, chainID, socketAddr string, privVal types.PrivValidator, privKey ed25519.PrivKeyEd25519, ) *RemoteSigner { rs := &RemoteSigner{ addr: socketAddr, chainID: chainID, connDeadline: time.Second * defaultConnDeadlineSeconds, connRetries: defaultDialRetries, privKey: privKey, privVal: privVal, } rs.BaseService = *cmn.NewBaseService(logger, "RemoteSigner", rs) return rs } // OnStart implements cmn.Service. func (rs *RemoteSigner) OnStart() error { conn, err := rs.connect() if err != nil { rs.Logger.Error("OnStart", "err", err) return err } go rs.handleConnection(conn) return nil } // OnStop implements cmn.Service. func (rs *RemoteSigner) OnStop() { if rs.conn == nil { return } if err := rs.conn.Close(); err != nil { rs.Logger.Error("OnStop", "err", cmn.ErrorWrap(err, "closing listener failed")) } } func (rs *RemoteSigner) connect() (net.Conn, error) { for retries := rs.connRetries; retries > 0; retries-- { // Don't sleep if it is the first retry. if retries != rs.connRetries { time.Sleep(rs.connDeadline) } conn, err := cmn.Connect(rs.addr) if err != nil { rs.Logger.Error( "connect", "addr", rs.addr, "err", err, ) continue } if err := conn.SetDeadline(time.Now().Add(connTimeout)); err != nil { rs.Logger.Error( "connect", "err", err, ) continue } conn, err = p2pconn.MakeSecretConnection(conn, rs.privKey) if err != nil { rs.Logger.Error( "connect", "err", err, ) continue } return conn, nil } return nil, ErrDialRetryMax } func (rs *RemoteSigner) handleConnection(conn net.Conn) { for { if !rs.IsRunning() { return // Ignore error from listener closing. } // Reset the connection deadline conn.SetDeadline(time.Now().Add(rs.connDeadline)) req, err := readMsg(conn) if err != nil { if err != io.EOF { rs.Logger.Error("handleConnection", "err", err) } return } res, err := handleRequest(req, rs.chainID, rs.privVal) if err != nil { // only log the error; we'll reply with an error in res rs.Logger.Error("handleConnection", "err", err) } err = writeMsg(conn, res) if err != nil { rs.Logger.Error("handleConnection", "err", err) return } } }