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
|
|
}
|
|
}
|
|
}
|