func (th *TestHarness) TestPublicKey() error {
	th.logger.Info("TEST: Public key of remote signer")
	fpvk, err := th.fpv.GetPubKey(context.Background())
	if err != nil {
		return err
	}
	th.logger.Info("Local", "pubKey", fpvk)
	sck, err := th.signerClient.GetPubKey(context.Background())
	if err != nil {
		return err
	}
	th.logger.Info("Remote", "pubKey", sck)
	if !bytes.Equal(fpvk.Bytes(), sck.Bytes()) {
		th.logger.Error("FAILED: Local and remote public keys do not match")
		return newTestHarnessError(ErrTestPublicKeyFailed, nil, "")
	}
	return nil
}
TestPublicKey just validates that we can (1) fetch the public key from the
remote signer, and (2) it matches the public key we've configured for our
local Tendermint version.
package internal
import (
	"bytes"
	"context"
	"fmt"
	"net"
	"os"
	"os/signal"
	"time"
	"github.com/tendermint/tendermint/crypto/tmhash"
	"github.com/tendermint/tendermint/crypto/ed25519"
	"github.com/tendermint/tendermint/internal/state"
	"github.com/tendermint/tendermint/privval"
	"github.com/tendermint/tendermint/libs/log"
	tmnet "github.com/tendermint/tendermint/libs/net"
	tmos "github.com/tendermint/tendermint/libs/os"
	tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
	"github.com/tendermint/tendermint/types"
)
const (
	NoError                    int = iota  
	ErrInvalidParameters                   
	ErrMaxAcceptRetriesReached             
	ErrFailedToLoadGenesisFile             
	ErrFailedToCreateListener              
	ErrFailedToStartListener               
	ErrInterrupted                         
	ErrOther                               
	ErrTestPublicKeyFailed                 
	ErrTestSignProposalFailed              
	ErrTestSignVoteFailed                  
)
var voteTypes = []tmproto.SignedMsgType{tmproto.PrevoteType, tmproto.PrecommitType}
type TestHarnessError struct {
	Code int     
	Err  error   
	Info string  
}
var _ error = (*TestHarnessError)(nil)
type TestHarness struct {
	addr             string
	signerClient     *privval.SignerClient
	fpv              *privval.FilePV
	chainID          string
	acceptRetries    int
	logger           log.Logger
	exitWhenComplete bool
	exitCode         int
}
type TestHarnessConfig struct {
	BindAddr string
	KeyFile     string
	StateFile   string
	GenesisFile string
	AcceptDeadline time.Duration
	ConnDeadline   time.Duration
	AcceptRetries  int
	SecretConnKey ed25519.PrivKey
	ExitWhenComplete bool  
}
type timeoutError interface {
	Timeout() bool
}
func NewTestHarness(logger log.Logger, cfg TestHarnessConfig) (*TestHarness, error) {
	keyFile := ExpandPath(cfg.KeyFile)
	stateFile := ExpandPath(cfg.StateFile)
	logger.Info("Loading private validator configuration", "keyFile", keyFile, "stateFile", stateFile)
	fpv, err := privval.LoadFilePV(keyFile, stateFile)
	if err != nil {
		return nil, err
	}
	genesisFile := ExpandPath(cfg.GenesisFile)
	logger.Info("Loading chain ID from genesis file", "genesisFile", genesisFile)
	st, err := state.MakeGenesisDocFromFile(genesisFile)
	if err != nil {
		return nil, newTestHarnessError(ErrFailedToLoadGenesisFile, err, genesisFile)
	}
	logger.Info("Loaded genesis file", "chainID", st.ChainID)
	spv, err := newTestHarnessListener(logger, cfg)
	if err != nil {
		return nil, newTestHarnessError(ErrFailedToCreateListener, err, "")
	}
	signerClient, err := privval.NewSignerClient(spv, st.ChainID)
	if err != nil {
		return nil, newTestHarnessError(ErrFailedToCreateListener, err, "")
	}
	return &TestHarness{
		addr:             cfg.BindAddr,
		signerClient:     signerClient,
		fpv:              fpv,
		chainID:          st.ChainID,
		acceptRetries:    cfg.AcceptRetries,
		logger:           logger,
		exitWhenComplete: cfg.ExitWhenComplete,
		exitCode:         0,
	}, nil
}
func (th *TestHarness) Run() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for sig := range c {
			th.logger.Info("Caught interrupt, terminating...", "sig", sig)
			th.Shutdown(newTestHarnessError(ErrInterrupted, nil, ""))
		}
	}()
	th.logger.Info("Starting test harness")
	accepted := false
	var startErr error
	for acceptRetries := th.acceptRetries; acceptRetries > 0; acceptRetries-- {
		th.logger.Info("Attempting to accept incoming connection", "acceptRetries", acceptRetries)
		if err := th.signerClient.WaitForConnection(10 * time.Millisecond); err != nil {
			if _, ok := err.(timeoutError); !ok {
				th.logger.Error("Failed to start listener", "err", err)
				th.Shutdown(newTestHarnessError(ErrFailedToStartListener, err, ""))
				return
			}
			startErr = err
		} else {
			th.logger.Info("Accepted external connection")
			accepted = true
			break
		}
	}
	if !accepted {
		th.logger.Error("Maximum accept retries reached", "acceptRetries", th.acceptRetries)
		th.Shutdown(newTestHarnessError(ErrMaxAcceptRetriesReached, startErr, ""))
		return
	}
	if err := th.TestPublicKey(); err != nil {
		th.Shutdown(err)
		return
	}
	if err := th.TestSignProposal(); err != nil {
		th.Shutdown(err)
		return
	}
	if err := th.TestSignVote(); err != nil {
		th.Shutdown(err)
		return
	}
	th.logger.Info("SUCCESS! All tests passed.")
	th.Shutdown(nil)
}