mirror of
https://github.com/fluencelabs/tendermint
synced 2025-04-25 14:52:17 +00:00
This issue is related to #3107 This is a first renaming/refactoring step before reworking and removing heartbeats. As discussed with @Liamsi , we preferred to go for a couple of independent and separate PRs to simplify review work. The changes: Help to clarify the relation between the validator and remote signer endpoints Differentiate between timeouts and deadlines Prepare to encapsulate networking related code behind RemoteSigner in the next PR My intention is to separate and encapsulate the "network related" code from the actual signer. SignerRemote ---(uses/contains)--> SignerValidatorEndpoint <--(connects to)--> SignerServiceEndpoint ---> SignerService (future.. not here yet but would like to decouple too) All reconnection/heartbeat/whatever code goes in the endpoints. Signer[Remote/Service] do not need to know about that. I agree Endpoint may not be the perfect name. I tried to find something "Go-ish" enough. It is a common name in go-kit, kubernetes, etc. Right now: SignerValidatorEndpoint: handles the listener contains SignerRemote Implements the PrivValidator interface connects and sets a connection object in a contained SignerRemote delegates PrivValidator some calls to SignerRemote which in turn uses the conn object that was set externally SignerRemote: Implements the PrivValidator interface read/writes from a connection object directly handles heartbeats SignerServiceEndpoint: Does most things in a single place delegates to a PrivValidator IIRC. * cleanup * Refactoring step 1 * Refactoring step 2 * move messages to another file * mark for future work / next steps * mark deprecated classes in docs * Fix linter problems * additional linter fixes
231 lines
5.9 KiB
Go
231 lines
5.9 KiB
Go
package privval
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/tendermint/tendermint/crypto"
|
|
cmn "github.com/tendermint/tendermint/libs/common"
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
const (
|
|
defaultHeartbeatSeconds = 2
|
|
defaultMaxDialRetries = 10
|
|
)
|
|
|
|
var (
|
|
heartbeatPeriod = time.Second * defaultHeartbeatSeconds
|
|
)
|
|
|
|
// SignerValidatorEndpointOption sets an optional parameter on the SocketVal.
|
|
type SignerValidatorEndpointOption func(*SignerValidatorEndpoint)
|
|
|
|
// SignerValidatorEndpointSetHeartbeat sets the period on which to check the liveness of the
|
|
// connected Signer connections.
|
|
func SignerValidatorEndpointSetHeartbeat(period time.Duration) SignerValidatorEndpointOption {
|
|
return func(sc *SignerValidatorEndpoint) { sc.heartbeatPeriod = period }
|
|
}
|
|
|
|
// SocketVal implements PrivValidator.
|
|
// It listens for an external process to dial in and uses
|
|
// the socket to request signatures.
|
|
type SignerValidatorEndpoint struct {
|
|
cmn.BaseService
|
|
|
|
listener net.Listener
|
|
|
|
// ping
|
|
cancelPingCh chan struct{}
|
|
pingTicker *time.Ticker
|
|
heartbeatPeriod time.Duration
|
|
|
|
// signer is mutable since it can be reset if the connection fails.
|
|
// failures are detected by a background ping routine.
|
|
// All messages are request/response, so we hold the mutex
|
|
// so only one request/response pair can happen at a time.
|
|
// Methods on the underlying net.Conn itself are already goroutine safe.
|
|
mtx sync.Mutex
|
|
|
|
// TODO: Signer should encapsulate and hide the endpoint completely. Invert the relation
|
|
signer *SignerRemote
|
|
}
|
|
|
|
// Check that SignerValidatorEndpoint implements PrivValidator.
|
|
var _ types.PrivValidator = (*SignerValidatorEndpoint)(nil)
|
|
|
|
// NewSignerValidatorEndpoint returns an instance of SignerValidatorEndpoint.
|
|
func NewSignerValidatorEndpoint(logger log.Logger, listener net.Listener) *SignerValidatorEndpoint {
|
|
sc := &SignerValidatorEndpoint{
|
|
listener: listener,
|
|
heartbeatPeriod: heartbeatPeriod,
|
|
}
|
|
|
|
sc.BaseService = *cmn.NewBaseService(logger, "SignerValidatorEndpoint", sc)
|
|
|
|
return sc
|
|
}
|
|
|
|
//--------------------------------------------------------
|
|
// Implement PrivValidator
|
|
|
|
// GetPubKey implements PrivValidator.
|
|
func (ve *SignerValidatorEndpoint) GetPubKey() crypto.PubKey {
|
|
ve.mtx.Lock()
|
|
defer ve.mtx.Unlock()
|
|
return ve.signer.GetPubKey()
|
|
}
|
|
|
|
// SignVote implements PrivValidator.
|
|
func (ve *SignerValidatorEndpoint) SignVote(chainID string, vote *types.Vote) error {
|
|
ve.mtx.Lock()
|
|
defer ve.mtx.Unlock()
|
|
return ve.signer.SignVote(chainID, vote)
|
|
}
|
|
|
|
// SignProposal implements PrivValidator.
|
|
func (ve *SignerValidatorEndpoint) SignProposal(chainID string, proposal *types.Proposal) error {
|
|
ve.mtx.Lock()
|
|
defer ve.mtx.Unlock()
|
|
return ve.signer.SignProposal(chainID, proposal)
|
|
}
|
|
|
|
//--------------------------------------------------------
|
|
// More thread safe methods proxied to the signer
|
|
|
|
// Ping is used to check connection health.
|
|
func (ve *SignerValidatorEndpoint) Ping() error {
|
|
ve.mtx.Lock()
|
|
defer ve.mtx.Unlock()
|
|
return ve.signer.Ping()
|
|
}
|
|
|
|
// Close closes the underlying net.Conn.
|
|
func (ve *SignerValidatorEndpoint) Close() {
|
|
ve.mtx.Lock()
|
|
defer ve.mtx.Unlock()
|
|
if ve.signer != nil {
|
|
if err := ve.signer.Close(); err != nil {
|
|
ve.Logger.Error("OnStop", "err", err)
|
|
}
|
|
}
|
|
|
|
if ve.listener != nil {
|
|
if err := ve.listener.Close(); err != nil {
|
|
ve.Logger.Error("OnStop", "err", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------
|
|
// Service start and stop
|
|
|
|
// OnStart implements cmn.Service.
|
|
func (ve *SignerValidatorEndpoint) OnStart() error {
|
|
if closed, err := ve.reset(); err != nil {
|
|
ve.Logger.Error("OnStart", "err", err)
|
|
return err
|
|
} else if closed {
|
|
return fmt.Errorf("listener is closed")
|
|
}
|
|
|
|
// Start a routine to keep the connection alive
|
|
ve.cancelPingCh = make(chan struct{}, 1)
|
|
ve.pingTicker = time.NewTicker(ve.heartbeatPeriod)
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-ve.pingTicker.C:
|
|
err := ve.Ping()
|
|
if err != nil {
|
|
ve.Logger.Error("Ping", "err", err)
|
|
if err == ErrUnexpectedResponse {
|
|
return
|
|
}
|
|
|
|
closed, err := ve.reset()
|
|
if err != nil {
|
|
ve.Logger.Error("Reconnecting to remote signer failed", "err", err)
|
|
continue
|
|
}
|
|
if closed {
|
|
ve.Logger.Info("listener is closing")
|
|
return
|
|
}
|
|
|
|
ve.Logger.Info("Re-created connection to remote signer", "impl", ve)
|
|
}
|
|
case <-ve.cancelPingCh:
|
|
ve.pingTicker.Stop()
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
// OnStop implements cmn.Service.
|
|
func (ve *SignerValidatorEndpoint) OnStop() {
|
|
if ve.cancelPingCh != nil {
|
|
close(ve.cancelPingCh)
|
|
}
|
|
ve.Close()
|
|
}
|
|
|
|
//--------------------------------------------------------
|
|
// Connection and signer management
|
|
|
|
// waits to accept and sets a new connection.
|
|
// connection is closed in OnStop.
|
|
// returns true if the listener is closed
|
|
// (ie. it returns a nil conn).
|
|
func (ve *SignerValidatorEndpoint) reset() (closed bool, err error) {
|
|
ve.mtx.Lock()
|
|
defer ve.mtx.Unlock()
|
|
|
|
// first check if the conn already exists and close it.
|
|
if ve.signer != nil {
|
|
if tmpErr := ve.signer.Close(); tmpErr != nil {
|
|
ve.Logger.Error("error closing socket val connection during reset", "err", tmpErr)
|
|
}
|
|
}
|
|
|
|
// wait for a new conn
|
|
conn, err := ve.acceptConnection()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
// listener is closed
|
|
if conn == nil {
|
|
return true, nil
|
|
}
|
|
|
|
ve.signer, err = NewSignerRemote(conn)
|
|
if err != nil {
|
|
// failed to fetch the pubkey. close out the connection.
|
|
if tmpErr := conn.Close(); tmpErr != nil {
|
|
ve.Logger.Error("error closing connection", "err", tmpErr)
|
|
}
|
|
return false, err
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// Attempt to accept a connection.
|
|
// Times out after the listener's timeoutAccept
|
|
func (ve *SignerValidatorEndpoint) acceptConnection() (net.Conn, error) {
|
|
conn, err := ve.listener.Accept()
|
|
if err != nil {
|
|
if !ve.IsRunning() {
|
|
return nil, nil // Ignore error from listener closing.
|
|
}
|
|
return nil, err
|
|
}
|
|
return conn, nil
|
|
}
|