package types import ( "fmt" "io" "net" "time" "github.com/pkg/errors" crypto "github.com/tendermint/go-crypto" wire "github.com/tendermint/go-wire" cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" "golang.org/x/net/netutil" p2pconn "github.com/tendermint/tendermint/p2p/conn" "github.com/tendermint/tendermint/types" ) const ( defaultConnDeadlineSeconds = 3 defaultDialRetryIntervalSeconds = 1 defaultDialRetryMax = 10 ) // Socket errors. var ( ErrDialRetryMax = errors.New("Error max client retries") ) var ( connDeadline = time.Second * defaultConnDeadlineSeconds ) // SocketClientOption sets an optional parameter on the SocketClient. type SocketClientOption func(*socketClient) // SocketClientTimeout sets the timeout for connecting to the external socket // address. func SocketClientTimeout(timeout time.Duration) SocketClientOption { return func(sc *socketClient) { sc.connectTimeout = timeout } } // socketClient implements PrivValidator, it uses a socket to request signatures // from an external process. type socketClient struct { cmn.BaseService conn net.Conn privKey *crypto.PrivKeyEd25519 addr string connectTimeout time.Duration } // Check that socketClient implements PrivValidator2. var _ types.PrivValidator2 = (*socketClient)(nil) // NewsocketClient returns an instance of socketClient. func NewSocketClient( logger log.Logger, socketAddr string, privKey *crypto.PrivKeyEd25519, ) *socketClient { sc := &socketClient{ addr: socketAddr, connectTimeout: time.Second * defaultConnDeadlineSeconds, privKey: privKey, } sc.BaseService = *cmn.NewBaseService(logger, "privValidatorsocketClient", sc) return sc } // OnStart implements cmn.Service. func (sc *socketClient) OnStart() error { if err := sc.BaseService.OnStart(); err != nil { return err } conn, err := sc.connect() if err != nil { return err } sc.conn = conn return nil } // OnStop implements cmn.Service. func (sc *socketClient) OnStop() { sc.BaseService.OnStop() if sc.conn != nil { sc.conn.Close() } } // GetAddress implements PrivValidator. // TODO(xla): Remove when PrivValidator2 replaced PrivValidator. func (sc *socketClient) GetAddress() types.Address { addr, err := sc.Address() if err != nil { panic(err) } return addr } // Address is an alias for PubKey().Address(). func (sc *socketClient) Address() (cmn.HexBytes, error) { p, err := sc.PubKey() if err != nil { return nil, err } return p.Address(), nil } // GetPubKey implements PrivValidator. // TODO(xla): Remove when PrivValidator2 replaced PrivValidator. func (sc *socketClient) GetPubKey() crypto.PubKey { pubKey, err := sc.PubKey() if err != nil { panic(err) } return pubKey } // PubKey implements PrivValidator2. func (sc *socketClient) PubKey() (crypto.PubKey, error) { err := writeMsg(sc.conn, &PubKeyMsg{}) if err != nil { return crypto.PubKey{}, err } res, err := readMsg(sc.conn) if err != nil { return crypto.PubKey{}, err } return res.(*PubKeyMsg).PubKey, nil } // SignVote implements PrivValidator2. func (sc *socketClient) SignVote(chainID string, vote *types.Vote) error { err := writeMsg(sc.conn, &SignVoteMsg{Vote: vote}) if err != nil { return err } res, err := readMsg(sc.conn) if err != nil { return err } *vote = *res.(*SignVoteMsg).Vote return nil } // SignProposal implements PrivValidator2. func (sc *socketClient) SignProposal(chainID string, proposal *types.Proposal) error { err := writeMsg(sc.conn, &SignProposalMsg{Proposal: proposal}) if err != nil { return err } res, err := readMsg(sc.conn) if err != nil { return err } *proposal = *res.(*SignProposalMsg).Proposal return nil } // SignHeartbeat implements PrivValidator2. func (sc *socketClient) SignHeartbeat(chainID string, heartbeat *types.Heartbeat) error { err := writeMsg(sc.conn, &SignHeartbeatMsg{Heartbeat: heartbeat}) if err != nil { return err } res, err := readMsg(sc.conn) if err != nil { return err } *heartbeat = *res.(*SignHeartbeatMsg).Heartbeat return nil } func (sc *socketClient) connect() (net.Conn, error) { retries := defaultDialRetryMax RETRY_LOOP: for retries > 0 { if retries != defaultDialRetryMax { time.Sleep(sc.connectTimeout) } retries-- conn, err := cmn.Connect(sc.addr) if err != nil { sc.Logger.Error( "sc connect", "addr", sc.addr, "err", errors.Wrap(err, "connection failed"), ) continue RETRY_LOOP } if err := conn.SetDeadline(time.Now().Add(connDeadline)); err != nil { sc.Logger.Error( "sc connect", "err", errors.Wrap(err, "setting connection timeout failed"), ) continue } if sc.privKey != nil { conn, err = p2pconn.MakeSecretConnection(conn, sc.privKey.Wrap()) if err != nil { sc.Logger.Error( "sc connect", "err", errors.Wrap(err, "encrypting connection failed"), ) continue RETRY_LOOP } } return conn, nil } return nil, ErrDialRetryMax } //--------------------------------------------------------- // PrivValidatorSocketServer implements PrivValidator. // It responds to requests over a socket type PrivValidatorSocketServer struct { cmn.BaseService proto, addr string listener net.Listener maxConnections int privKey *crypto.PrivKeyEd25519 privVal PrivValidator chainID string } // NewPrivValidatorSocketServer returns an instance of // PrivValidatorSocketServer. func NewPrivValidatorSocketServer( logger log.Logger, chainID, socketAddr string, maxConnections int, privVal PrivValidator, privKey *crypto.PrivKeyEd25519, ) *PrivValidatorSocketServer { proto, addr := cmn.ProtocolAndAddress(socketAddr) pvss := &PrivValidatorSocketServer{ proto: proto, addr: addr, maxConnections: maxConnections, privKey: privKey, privVal: privVal, chainID: chainID, } pvss.BaseService = *cmn.NewBaseService(logger, "privValidatorSocketServer", pvss) return pvss } // OnStart implements cmn.Service. func (pvss *PrivValidatorSocketServer) OnStart() error { ln, err := net.Listen(pvss.proto, pvss.addr) if err != nil { return err } pvss.listener = netutil.LimitListener(ln, pvss.maxConnections) go pvss.acceptConnections() return nil } // OnStop implements cmn.Service. func (pvss *PrivValidatorSocketServer) OnStop() { if pvss.listener == nil { return } if err := pvss.listener.Close(); err != nil { pvss.Logger.Error("OnStop", "err", errors.Wrap(err, "closing listener failed")) } } func (pvss *PrivValidatorSocketServer) acceptConnections() { for { conn, err := pvss.listener.Accept() if err != nil { if !pvss.IsRunning() { return // Ignore error from listener closing. } pvss.Logger.Error( "accpetConnections", "err", errors.Wrap(err, "failed to accept connection"), ) continue } if err := conn.SetDeadline(time.Now().Add(connDeadline)); err != nil { pvss.Logger.Error( "acceptConnetions", "err", errors.Wrap(err, "setting connection timeout failed"), ) continue } if pvss.privKey != nil { conn, err = p2pconn.MakeSecretConnection(conn, pvss.privKey.Wrap()) if err != nil { pvss.Logger.Error( "acceptConnections", "err", errors.Wrap(err, "secret connection failed"), ) continue } } go pvss.handleConnection(conn) } } func (pvss *PrivValidatorSocketServer) handleConnection(conn net.Conn) { defer conn.Close() for { if !pvss.IsRunning() { return // Ignore error from listener closing. } req, err := readMsg(conn) if err != nil { if err != io.EOF { pvss.Logger.Error("handleConnection", "err", err) } return } var res PrivValidatorSocketMsg switch r := req.(type) { case *PubKeyMsg: var p crypto.PubKey p, err = pvss.privVal.PubKey() res = &PubKeyMsg{p} case *SignVoteMsg: err = pvss.privVal.SignVote(pvss.chainID, r.Vote) res = &SignVoteMsg{r.Vote} case *SignProposalMsg: err = pvss.privVal.SignProposal(pvss.chainID, r.Proposal) res = &SignProposalMsg{r.Proposal} case *SignHeartbeatMsg: err = pvss.privVal.SignHeartbeat(pvss.chainID, r.Heartbeat) res = &SignHeartbeatMsg{r.Heartbeat} default: err = fmt.Errorf("unknown msg: %v", r) } if err != nil { pvss.Logger.Error("handleConnection", "err", err) return } err = writeMsg(conn, res) if err != nil { pvss.Logger.Error("handleConnection", "err", err) return } } } //--------------------------------------------------------- const ( msgTypePubKey = byte(0x01) msgTypeSignVote = byte(0x10) msgTypeSignProposal = byte(0x11) msgTypeSignHeartbeat = byte(0x12) ) // PrivValidatorSocketMsg is a message sent between PrivValidatorSocket client // and server. type PrivValidatorSocketMsg interface{} var _ = wire.RegisterInterface( struct{ PrivValidatorSocketMsg }{}, wire.ConcreteType{&PubKeyMsg{}, msgTypePubKey}, wire.ConcreteType{&SignVoteMsg{}, msgTypeSignVote}, wire.ConcreteType{&SignProposalMsg{}, msgTypeSignProposal}, wire.ConcreteType{&SignHeartbeatMsg{}, msgTypeSignHeartbeat}, ) // PubKeyMsg is a PrivValidatorSocket message containing the public key. type PubKeyMsg struct { PubKey crypto.PubKey } // SignVoteMsg is a PrivValidatorSocket message containing a vote. type SignVoteMsg struct { Vote *types.Vote } // SignProposalMsg is a PrivValidatorSocket message containing a Proposal. type SignProposalMsg struct { Proposal *types.Proposal } // SignHeartbeatMsg is a PrivValidatorSocket message containing a Heartbeat. type SignHeartbeatMsg struct { Heartbeat *types.Heartbeat } func readMsg(r io.Reader) (PrivValidatorSocketMsg, error) { var ( n int err error ) read := wire.ReadBinary(struct{ PrivValidatorSocketMsg }{}, r, 0, &n, &err) if err != nil { return nil, err } w, ok := read.(struct{ PrivValidatorSocketMsg }) if !ok { return nil, errors.New("unknwon type") } return w.PrivValidatorSocketMsg, nil } func writeMsg(w io.Writer, msg interface{}) error { var ( err error n int ) // TODO(xla): This extra wrap should be gone with the sdk-2 update. wire.WriteBinary(struct{ PrivValidatorSocketMsg }{msg}, w, &n, &err) return err }