diff --git a/docs/tools/README.md b/docs/tools/README.md index 5d778f470..b6e08162b 100644 --- a/docs/tools/README.md +++ b/docs/tools/README.md @@ -12,7 +12,6 @@ Tendermint has some tools that are associated with it for: - [Debugging](./debugging/pro.md) - [Benchmarking](#benchmarking) - [Testnets](#testnets) -- [Validation of remote signers](./remote-signer-validation.md) ## Benchmarking diff --git a/docs/tools/remote-signer-validation.md b/docs/tools/remote-signer-validation.md deleted file mode 100644 index ab5500860..000000000 --- a/docs/tools/remote-signer-validation.md +++ /dev/null @@ -1,156 +0,0 @@ -# Remote Signer - -Located under the `tools/tm-signer-harness` folder in the [Tendermint -repository](https://github.com/tendermint/tendermint). - -The Tendermint remote signer test harness facilitates integration testing -between Tendermint and remote signers such as -[tkkms](https://github.com/iqlusioninc/tmkms). Such remote signers allow for signing -of important Tendermint messages using -[HSMs](https://en.wikipedia.org/wiki/Hardware_security_module), providing -additional security. - -When executed, `tm-signer-harness`: - -1. Runs a listener (either TCP or Unix sockets). -2. Waits for a connection from the remote signer. -3. Upon connection from the remote signer, executes a number of automated tests - to ensure compatibility. -4. Upon successful validation, the harness process exits with a 0 exit code. - Upon validation failure, it exits with a particular exit code related to the - error. - -## Prerequisites - -Requires the same prerequisites as for building -[Tendermint](https://github.com/tendermint/tendermint). - -## Building - -From the `tools/tm-signer-harness` directory in your Tendermint source -repository, simply run: - -```bash -make - -# To have global access to this executable -make install -``` - -## Docker Image - -To build a Docker image containing the `tm-signer-harness`, also from the -`tools/tm-signer-harness` directory of your Tendermint source repo, simply run: - -```bash -make docker-image -``` - -## Running against KMS - -As an example of how to use `tm-signer-harness`, the following instructions show -you how to execute its tests against [tkkms](https://github.com/iqlusioninc/tmkms). -For this example, we will make use of the **software signing module in KMS**, as -the hardware signing module requires a physical -[YubiHSM](https://www.yubico.com/products/yubihsm/) device. - -### Step 1: Install KMS on your local machine - -See the [tkkms repo](https://github.com/iqlusioninc/tmkms) for details on how to set -KMS up on your local machine. - -If you have [Rust](https://www.rust-lang.org/) installed on your local machine, -you can simply install KMS by: - -```bash -cargo install tmkms -``` - -### Step 2: Make keys for KMS - -The KMS software signing module needs a key with which to sign messages. In our -example, we will simply export a signing key from our local Tendermint instance. - -```bash -# Will generate all necessary Tendermint configuration files, including: -# - ~/.tendermint/config/priv_validator_key.json -# - ~/.tendermint/data/priv_validator_state.json -tendermint init validator - -# Extract the signing key from our local Tendermint instance -tm-signer-harness extract_key \ # Use the "extract_key" command - -tmhome ~/.tendermint \ # Where to find the Tendermint home directory - -output ./signing.key # Where to write the key -``` - -Also, because we want KMS to connect to `tm-signer-harness`, we will need to -provide a secret connection key from KMS' side: - -```bash -tmkms keygen secret_connection.key -``` - -### Step 3: Configure and run KMS - -KMS needs some configuration to tell it to use the softer signing module as well -as the `signing.key` file we just generated. Save the following to a file called -`tmkms.toml`: - -```toml -[[validator]] -addr = "tcp://127.0.0.1:61219" # This is where we will find tm-signer-harness. -chain_id = "test-chain-0XwP5E" # The Tendermint chain ID for which KMS will be signing (found in ~/.tendermint/config/genesis.json). -reconnect = true # true is the default -secret_key = "./secret_connection.key" # Where to find our secret connection key. - -[[providers.softsign]] -id = "test-chain-0XwP5E" # The Tendermint chain ID for which KMS will be signing (same as validator.chain_id above). -path = "./signing.key" # The signing key we extracted earlier. -``` - -Then run KMS with this configuration: - -```bash -tmkms start -c tmkms.toml -``` - -This will start KMS, which will repeatedly try to connect to -`tcp://127.0.0.1:61219` until it is successful. - -### Step 4: Run tm-signer-harness - -Now we get to run the signer test harness: - -```bash -tm-signer-harness run \ # The "run" command executes the tests - -addr tcp://127.0.0.1:61219 \ # The address we promised KMS earlier - -tmhome ~/.tendermint # Where to find our Tendermint configuration/data files. -``` - -If the current version of Tendermint and KMS are compatible, `tm-signer-harness` -should now exit with a 0 exit code. If they are somehow not compatible, it -should exit with a meaningful non-zero exit code (see the exit codes below). - -### Step 5: Shut down KMS - -Simply hit Ctrl+Break on your KMS instance (or use the `kill` command in Linux) -to terminate it gracefully. - -## Exit Code Meanings - -The following list shows the various exit codes from `tm-signer-harness` and -their meanings: - -| Exit Code | Description | -| --- | --- | -| 0 | Success! | -| 1 | Invalid command line parameters supplied to `tm-signer-harness` | -| 2 | Maximum number of accept retries reached (the `-accept-retries` parameter) | -| 3 | Failed to load `${TMHOME}/config/genesis.json` | -| 4 | Failed to create listener specified by `-addr` parameter | -| 5 | Failed to start listener | -| 6 | Interrupted by `SIGINT` (e.g. when hitting Ctrl+Break or Ctrl+C) | -| 7 | Other unknown error | -| 8 | Test 1 failed: public key mismatch | -| 9 | Test 2 failed: signing of proposals failed | -| 10 | Test 3 failed: signing of votes failed | diff --git a/tools/tm-signer-harness/Dockerfile b/tools/tm-signer-harness/Dockerfile deleted file mode 100644 index 83f57a3d5..000000000 --- a/tools/tm-signer-harness/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -ARG TENDERMINT_VERSION=latest -FROM tendermint/tendermint:${TENDERMINT_VERSION} - -COPY tm-signer-harness /usr/bin/tm-signer-harness diff --git a/tools/tm-signer-harness/Makefile b/tools/tm-signer-harness/Makefile deleted file mode 100644 index fc4157108..000000000 --- a/tools/tm-signer-harness/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -.PHONY: build install docker-image - -TENDERMINT_VERSION?=latest -BUILD_TAGS?='tendermint' -VERSION := $(shell git describe --always) -BUILD_FLAGS = -ldflags "-X github.com/tendermint/tendermint/version.TMCoreSemVer=$(VERSION)" - -.DEFAULT_GOAL := build - -build: - CGO_ENABLED=0 go build $(BUILD_FLAGS) -tags $(BUILD_TAGS) -o ../../build/tm-signer-harness main.go - -install: - CGO_ENABLED=0 go install $(BUILD_FLAGS) -tags $(BUILD_TAGS) . - -docker-image: - GOOS=linux GOARCH=amd64 go build $(BUILD_FLAGS) -tags $(BUILD_TAGS) -o tm-signer-harness main.go - docker build \ - --build-arg TENDERMINT_VERSION=$(TENDERMINT_VERSION) \ - -t tendermint/tm-signer-harness:$(TENDERMINT_VERSION) . - rm -rf tm-signer-harness diff --git a/tools/tm-signer-harness/README.md b/tools/tm-signer-harness/README.md deleted file mode 100644 index 7add3a997..000000000 --- a/tools/tm-signer-harness/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# tm-signer-harness - -See the [`tm-signer-harness` -documentation](https://tendermint.com/docs/tools/remote-signer-validation.html) -for more details. diff --git a/tools/tm-signer-harness/internal/test_harness.go b/tools/tm-signer-harness/internal/test_harness.go deleted file mode 100644 index c8b5cd81d..000000000 --- a/tools/tm-signer-harness/internal/test_harness.go +++ /dev/null @@ -1,427 +0,0 @@ -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" -) - -// Test harness error codes (which act as exit codes when the test harness fails). -const ( - NoError int = iota // 0 - ErrInvalidParameters // 1 - ErrMaxAcceptRetriesReached // 2 - ErrFailedToLoadGenesisFile // 3 - ErrFailedToCreateListener // 4 - ErrFailedToStartListener // 5 - ErrInterrupted // 6 - ErrOther // 7 - ErrTestPublicKeyFailed // 8 - ErrTestSignProposalFailed // 9 - ErrTestSignVoteFailed // 10 -) - -var voteTypes = []tmproto.SignedMsgType{tmproto.PrevoteType, tmproto.PrecommitType} - -// TestHarnessError allows us to keep track of which exit code should be used -// when exiting the main program. -type TestHarnessError struct { - Code int // The exit code to return - Err error // The original error - Info string // Any additional information -} - -var _ error = (*TestHarnessError)(nil) - -// TestHarness allows for testing of a remote signer to ensure compatibility -// with this version of Tendermint. -type TestHarness struct { - addr string - signerClient *privval.SignerClient - fpv *privval.FilePV - chainID string - acceptRetries int - logger log.Logger - exitWhenComplete bool - exitCode int -} - -// TestHarnessConfig provides configuration to set up a remote signer test -// harness. -type TestHarnessConfig struct { - BindAddr string - - KeyFile string - StateFile string - GenesisFile string - - AcceptDeadline time.Duration - ConnDeadline time.Duration - AcceptRetries int - - SecretConnKey ed25519.PrivKey - - ExitWhenComplete bool // Whether or not to call os.Exit when the harness has completed. -} - -// timeoutError can be used to check if an error returned from the netp package -// was due to a timeout. -type timeoutError interface { - Timeout() bool -} - -// NewTestHarness will load Tendermint data from the given files (including -// validator public/private keypairs and chain details) and create a new -// harness. -func NewTestHarness(ctx context.Context, 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) - // NOTE: LoadFilePV ultimately calls os.Exit on failure. No error will be - // returned if this call fails. - 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(ctx, 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 -} - -// Run will execute the tests associated with this test harness. The intention -// here is to call this from one's `main` function, as the way it succeeds or -// fails at present is to call os.Exit() with an exit code related to the error -// that caused the tests to fail, or exit code 0 on success. -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 it wasn't a timeout error - if _, ok := err.(timeoutError); !ok { - th.logger.Error("Failed to start listener", "err", err) - th.Shutdown(newTestHarnessError(ErrFailedToStartListener, err, "")) - // we need the return statements in case this is being run - // from a unit test - otherwise this function will just die - // when os.Exit is called - 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 - } - - // Run the tests - 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) -} - -// 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. -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 -} - -// TestSignProposal makes sure the remote signer can successfully sign -// proposals. -func (th *TestHarness) TestSignProposal() error { - th.logger.Info("TEST: Signing of proposals") - // sha256 hash of "hash" - hash := tmhash.Sum([]byte("hash")) - prop := &types.Proposal{ - Type: tmproto.ProposalType, - Height: 100, - Round: 0, - POLRound: -1, - BlockID: types.BlockID{ - Hash: hash, - PartSetHeader: types.PartSetHeader{ - Hash: hash, - Total: 1000000, - }, - }, - Timestamp: time.Now(), - } - p := prop.ToProto() - propBytes := types.ProposalSignBytes(th.chainID, p) - if err := th.signerClient.SignProposal(context.Background(), th.chainID, p); err != nil { - th.logger.Error("FAILED: Signing of proposal", "err", err) - return newTestHarnessError(ErrTestSignProposalFailed, err, "") - } - prop.Signature = p.Signature - th.logger.Debug("Signed proposal", "prop", prop) - // first check that it's a basically valid proposal - if err := prop.ValidateBasic(); err != nil { - th.logger.Error("FAILED: Signed proposal is invalid", "err", err) - return newTestHarnessError(ErrTestSignProposalFailed, err, "") - } - sck, err := th.signerClient.GetPubKey(context.Background()) - if err != nil { - return err - } - // now validate the signature on the proposal - if sck.VerifySignature(propBytes, prop.Signature) { - th.logger.Info("Successfully validated proposal signature") - } else { - th.logger.Error("FAILED: Proposal signature validation failed") - return newTestHarnessError(ErrTestSignProposalFailed, nil, "signature validation failed") - } - return nil -} - -// TestSignVote makes sure the remote signer can successfully sign all kinds of -// votes. -func (th *TestHarness) TestSignVote() error { - th.logger.Info("TEST: Signing of votes") - for _, voteType := range voteTypes { - th.logger.Info("Testing vote type", "type", voteType) - hash := tmhash.Sum([]byte("hash")) - vote := &types.Vote{ - Type: voteType, - Height: 101, - Round: 0, - BlockID: types.BlockID{ - Hash: hash, - PartSetHeader: types.PartSetHeader{ - Hash: hash, - Total: 1000000, - }, - }, - ValidatorIndex: 0, - ValidatorAddress: tmhash.SumTruncated([]byte("addr")), - Timestamp: time.Now(), - } - v := vote.ToProto() - voteBytes := types.VoteSignBytes(th.chainID, v) - // sign the vote - if err := th.signerClient.SignVote(context.Background(), th.chainID, v); err != nil { - th.logger.Error("FAILED: Signing of vote", "err", err) - return newTestHarnessError(ErrTestSignVoteFailed, err, fmt.Sprintf("voteType=%d", voteType)) - } - vote.Signature = v.Signature - th.logger.Debug("Signed vote", "vote", vote) - // validate the contents of the vote - if err := vote.ValidateBasic(); err != nil { - th.logger.Error("FAILED: Signed vote is invalid", "err", err) - return newTestHarnessError(ErrTestSignVoteFailed, err, fmt.Sprintf("voteType=%d", voteType)) - } - sck, err := th.signerClient.GetPubKey(context.Background()) - if err != nil { - return err - } - - // now validate the signature on the proposal - if sck.VerifySignature(voteBytes, vote.Signature) { - th.logger.Info("Successfully validated vote signature", "type", voteType) - } else { - th.logger.Error("FAILED: Vote signature validation failed", "type", voteType) - return newTestHarnessError(ErrTestSignVoteFailed, nil, "signature validation failed") - } - } - return nil -} - -// Shutdown will kill the test harness and attempt to close all open sockets -// gracefully. If the supplied error is nil, it is assumed that the exit code -// should be 0. If err is not nil, it will exit with an exit code related to the -// error. -func (th *TestHarness) Shutdown(err error) { - var exitCode int - - if err == nil { - exitCode = NoError - } else if therr, ok := err.(*TestHarnessError); ok { - exitCode = therr.Code - } else { - exitCode = ErrOther - } - th.exitCode = exitCode - - // in case sc.Stop() takes too long - if th.exitWhenComplete { - go func() { - time.Sleep(time.Duration(5) * time.Second) - th.logger.Error("Forcibly exiting program after timeout") - os.Exit(exitCode) - }() - } - - err = th.signerClient.Close() - if err != nil { - th.logger.Error("Failed to cleanly stop listener: %s", err.Error()) - } - - if th.exitWhenComplete { - os.Exit(exitCode) - } -} - -// newTestHarnessListener creates our client instance which we will use for testing. -func newTestHarnessListener(logger log.Logger, cfg TestHarnessConfig) (*privval.SignerListenerEndpoint, error) { - proto, addr := tmnet.ProtocolAndAddress(cfg.BindAddr) - if proto == "unix" { - // make sure the socket doesn't exist - if so, try to delete it - if tmos.FileExists(addr) { - if err := os.Remove(addr); err != nil { - logger.Error("Failed to remove existing Unix domain socket", "addr", addr) - return nil, err - } - } - } - ln, err := net.Listen(proto, addr) - if err != nil { - return nil, err - } - logger.Info("Listening", "proto", proto, "addr", addr) - var svln net.Listener - switch proto { - case "unix": - unixLn := privval.NewUnixListener(ln) - privval.UnixListenerTimeoutAccept(cfg.AcceptDeadline)(unixLn) - privval.UnixListenerTimeoutReadWrite(cfg.ConnDeadline)(unixLn) - svln = unixLn - case "tcp": - tcpLn := privval.NewTCPListener(ln, cfg.SecretConnKey) - privval.TCPListenerTimeoutAccept(cfg.AcceptDeadline)(tcpLn) - privval.TCPListenerTimeoutReadWrite(cfg.ConnDeadline)(tcpLn) - logger.Info("Resolved TCP address for listener", "addr", tcpLn.Addr()) - svln = tcpLn - default: - _ = ln.Close() - logger.Error("Unsupported protocol (must be unix:// or tcp://)", "proto", proto) - return nil, newTestHarnessError(ErrInvalidParameters, nil, fmt.Sprintf("Unsupported protocol: %s", proto)) - } - return privval.NewSignerListenerEndpoint(logger, svln), nil -} - -func newTestHarnessError(code int, err error, info string) *TestHarnessError { - return &TestHarnessError{ - Code: code, - Err: err, - Info: info, - } -} - -func (e *TestHarnessError) Error() string { - var msg string - switch e.Code { - case ErrInvalidParameters: - msg = "Invalid parameters supplied to application" - case ErrMaxAcceptRetriesReached: - msg = "Maximum accept retries reached" - case ErrFailedToLoadGenesisFile: - msg = "Failed to load genesis file" - case ErrFailedToCreateListener: - msg = "Failed to create listener" - case ErrFailedToStartListener: - msg = "Failed to start listener" - case ErrInterrupted: - msg = "Interrupted" - case ErrTestPublicKeyFailed: - msg = "Public key validation test failed" - case ErrTestSignProposalFailed: - msg = "Proposal signing validation test failed" - case ErrTestSignVoteFailed: - msg = "Vote signing validation test failed" - default: - msg = "Unknown error" - } - if len(e.Info) > 0 { - msg = fmt.Sprintf("%s: %s", msg, e.Info) - } - if e.Err != nil { - msg = fmt.Sprintf("%s (original error: %s)", msg, e.Err.Error()) - } - return msg -} diff --git a/tools/tm-signer-harness/internal/test_harness_test.go b/tools/tm-signer-harness/internal/test_harness_test.go deleted file mode 100644 index ca54e5473..000000000 --- a/tools/tm-signer-harness/internal/test_harness_test.go +++ /dev/null @@ -1,202 +0,0 @@ -package internal - -import ( - "context" - "fmt" - "os" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/ed25519" - tmjson "github.com/tendermint/tendermint/libs/json" - "github.com/tendermint/tendermint/libs/log" - tmrand "github.com/tendermint/tendermint/libs/rand" - "github.com/tendermint/tendermint/privval" - "github.com/tendermint/tendermint/types" -) - -const ( - defaultConnDeadline = 100 -) - -func TestRemoteSignerTestHarnessMaxAcceptRetriesReached(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - cfg := makeConfig(t, 1, 2) - defer cleanup(cfg) - - th, err := NewTestHarness(ctx, log.TestingLogger(), cfg) - require.NoError(t, err) - th.Run() - assert.Equal(t, ErrMaxAcceptRetriesReached, th.exitCode) -} - -func TestRemoteSignerTestHarnessSuccessfulRun(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - harnessTest( - ctx, - t, - func(th *TestHarness) *privval.SignerServer { - return newMockSignerServer(t, th, th.fpv.Key.PrivKey, false, false) - }, - NoError, - ) -} - -func TestRemoteSignerPublicKeyCheckFailed(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - harnessTest( - ctx, - t, - func(th *TestHarness) *privval.SignerServer { - return newMockSignerServer(t, th, ed25519.GenPrivKey(), false, false) - }, - ErrTestPublicKeyFailed, - ) -} - -func TestRemoteSignerProposalSigningFailed(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - harnessTest( - ctx, - t, - func(th *TestHarness) *privval.SignerServer { - return newMockSignerServer(t, th, th.fpv.Key.PrivKey, true, false) - }, - ErrTestSignProposalFailed, - ) -} - -func TestRemoteSignerVoteSigningFailed(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - harnessTest( - ctx, - t, - func(th *TestHarness) *privval.SignerServer { - return newMockSignerServer(t, th, th.fpv.Key.PrivKey, false, true) - }, - ErrTestSignVoteFailed, - ) -} - -func newMockSignerServer( - t *testing.T, - th *TestHarness, - privKey crypto.PrivKey, - breakProposalSigning bool, - breakVoteSigning bool, -) *privval.SignerServer { - mockPV := types.NewMockPVWithParams(privKey, breakProposalSigning, breakVoteSigning) - - dialerEndpoint := privval.NewSignerDialerEndpoint( - th.logger, - privval.DialTCPFn( - th.addr, - time.Duration(defaultConnDeadline)*time.Millisecond, - ed25519.GenPrivKey(), - ), - ) - - return privval.NewSignerServer(dialerEndpoint, th.chainID, mockPV) -} - -// For running relatively standard tests. -func harnessTest( - ctx context.Context, - t *testing.T, - signerServerMaker func(th *TestHarness) *privval.SignerServer, - expectedExitCode int, -) { - cfg := makeConfig(t, 100, 3) - defer cleanup(cfg) - - th, err := NewTestHarness(ctx, log.TestingLogger(), cfg) - require.NoError(t, err) - donec := make(chan struct{}) - go func() { - defer close(donec) - th.Run() - }() - - ss := signerServerMaker(th) - require.NoError(t, ss.Start(ctx)) - assert.True(t, ss.IsRunning()) - defer ss.Stop() //nolint:errcheck // ignore for tests - - <-donec - assert.Equal(t, expectedExitCode, th.exitCode) -} - -func makeConfig(t *testing.T, acceptDeadline, acceptRetries int) TestHarnessConfig { - t.Helper() - const keyFilename = "tm-testharness-keyfile" - const stateFilename = "tm-testharness-statefile" - pvFile, err := privval.GenFilePV(keyFilename, stateFilename, types.ABCIPubKeyTypeEd25519) - if err != nil { - panic(err) - } - pvGenDoc := types.GenesisDoc{ - ChainID: fmt.Sprintf("test-chain-%v", tmrand.Str(6)), - GenesisTime: time.Now(), - ConsensusParams: types.DefaultConsensusParams(), - Validators: []types.GenesisValidator{ - { - Address: pvFile.Key.Address, - PubKey: pvFile.Key.PubKey, - Power: 10, - }, - }, - } - - keyFileContents, err := tmjson.Marshal(pvFile.Key) - require.NoError(t, err) - stateFileContents, err := tmjson.Marshal(pvFile.LastSignState) - require.NoError(t, err) - genesisFileContents, err := tmjson.Marshal(pvGenDoc) - require.NoError(t, err) - return TestHarnessConfig{ - BindAddr: privval.GetFreeLocalhostAddrPort(), - KeyFile: makeTempFile(keyFilename, keyFileContents), - StateFile: makeTempFile(stateFilename, stateFileContents), - GenesisFile: makeTempFile("tm-testharness-genesisfile", genesisFileContents), - AcceptDeadline: time.Duration(acceptDeadline) * time.Millisecond, - ConnDeadline: time.Duration(defaultConnDeadline) * time.Millisecond, - AcceptRetries: acceptRetries, - SecretConnKey: ed25519.GenPrivKey(), - ExitWhenComplete: false, - } -} - -func cleanup(cfg TestHarnessConfig) { - os.Remove(cfg.KeyFile) - os.Remove(cfg.StateFile) - os.Remove(cfg.GenesisFile) -} - -func makeTempFile(name string, content []byte) string { - tempFile, err := os.CreateTemp("", fmt.Sprintf("%s-*", name)) - if err != nil { - panic(err) - } - if _, err := tempFile.Write(content); err != nil { - tempFile.Close() - panic(err) - } - if err := tempFile.Close(); err != nil { - panic(err) - } - return tempFile.Name() -} diff --git a/tools/tm-signer-harness/internal/utils.go b/tools/tm-signer-harness/internal/utils.go deleted file mode 100644 index 9783ca95b..000000000 --- a/tools/tm-signer-harness/internal/utils.go +++ /dev/null @@ -1,25 +0,0 @@ -package internal - -import ( - "os/user" - "path/filepath" - "strings" -) - -// ExpandPath will check if the given path begins with a "~" symbol, and if so, -// will expand it to become the user's home directory. If it fails to expand the -// path it will automatically return the original path itself. -func ExpandPath(path string) string { - usr, err := user.Current() - if err != nil { - return path - } - - if path == "~" { - return usr.HomeDir - } else if strings.HasPrefix(path, "~/") { - return filepath.Join(usr.HomeDir, path[2:]) - } - - return path -} diff --git a/tools/tm-signer-harness/main.go b/tools/tm-signer-harness/main.go deleted file mode 100644 index 4bf1933e0..000000000 --- a/tools/tm-signer-harness/main.go +++ /dev/null @@ -1,203 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "os" - "path/filepath" - "time" - - "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/libs/log" - "github.com/tendermint/tendermint/privval" - "github.com/tendermint/tendermint/tools/tm-signer-harness/internal" - "github.com/tendermint/tendermint/version" -) - -const ( - defaultAcceptRetries = 100 - defaultBindAddr = "tcp://127.0.0.1:0" - defaultAcceptDeadline = 1 - defaultConnDeadline = 3 - defaultExtractKeyOutput = "./signing.key" -) - -var defaultTMHome string - -var logger = log.MustNewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo, false) - -// Command line flags -var ( - flagAcceptRetries int - flagBindAddr string - flagTMHome string - flagKeyOutputPath string -) - -// Command line commands -var ( - rootCmd *flag.FlagSet - runCmd *flag.FlagSet - extractKeyCmd *flag.FlagSet - versionCmd *flag.FlagSet -) - -func init() { - rootCmd = flag.NewFlagSet("root", flag.ExitOnError) - rootCmd.Usage = func() { - fmt.Println(`Remote signer test harness for Tendermint. - -Usage: - tm-signer-harness [flags] - -Available Commands: - extract_key Extracts a signing key from a local Tendermint instance - help Help on the available commands - run Runs the test harness - version Display version information and exit - -Use "tm-signer-harness help " for more information about that command.`) - fmt.Println("") - } - - hd, err := os.UserHomeDir() - if err != nil { - fmt.Println("The UserHomeDir is not defined, setting the default TM Home PATH to \"~/.tendermint\"") - defaultTMHome = "~/.tendermint" - } else { - defaultTMHome = fmt.Sprintf("%s/.tendermint", hd) - } - - runCmd = flag.NewFlagSet("run", flag.ExitOnError) - runCmd.IntVar(&flagAcceptRetries, - "accept-retries", - defaultAcceptRetries, - "The number of attempts to listen for incoming connections") - runCmd.StringVar(&flagBindAddr, "addr", defaultBindAddr, "Bind to this address for the testing") - runCmd.StringVar(&flagTMHome, "tmhome", defaultTMHome, "Path to the Tendermint home directory") - runCmd.Usage = func() { - fmt.Println(`Runs the remote signer test harness for Tendermint. - -Usage: - tm-signer-harness run [flags] - -Flags:`) - runCmd.PrintDefaults() - fmt.Println("") - } - - extractKeyCmd = flag.NewFlagSet("extract_key", flag.ExitOnError) - extractKeyCmd.StringVar(&flagKeyOutputPath, - "output", - defaultExtractKeyOutput, - "Path to which signing key should be written") - extractKeyCmd.StringVar(&flagTMHome, "tmhome", defaultTMHome, "Path to the Tendermint home directory") - extractKeyCmd.Usage = func() { - fmt.Println(`Extracts a signing key from a local Tendermint instance for use in the remote -signer under test. - -Usage: - tm-signer-harness extract_key [flags] - -Flags:`) - extractKeyCmd.PrintDefaults() - fmt.Println("") - } - - versionCmd = flag.NewFlagSet("version", flag.ExitOnError) - versionCmd.Usage = func() { - fmt.Println(` -Prints the Tendermint version for which this remote signer harness was built. - -Usage: - tm-signer-harness version`) - fmt.Println("") - } -} - -func runTestHarness(ctx context.Context, acceptRetries int, bindAddr, tmhome string) { - tmhome = internal.ExpandPath(tmhome) - cfg := internal.TestHarnessConfig{ - BindAddr: bindAddr, - KeyFile: filepath.Join(tmhome, "config", "priv_validator_key.json"), - StateFile: filepath.Join(tmhome, "data", "priv_validator_state.json"), - GenesisFile: filepath.Join(tmhome, "config", "genesis.json"), - AcceptDeadline: time.Duration(defaultAcceptDeadline) * time.Second, - AcceptRetries: acceptRetries, - ConnDeadline: time.Duration(defaultConnDeadline) * time.Second, - SecretConnKey: ed25519.GenPrivKey(), - ExitWhenComplete: true, - } - harness, err := internal.NewTestHarness(ctx, logger, cfg) - if err != nil { - logger.Error(err.Error()) - if therr, ok := err.(*internal.TestHarnessError); ok { - os.Exit(therr.Code) - } - os.Exit(internal.ErrOther) - } - harness.Run() -} - -func extractKey(tmhome, outputPath string) { - keyFile := filepath.Join(internal.ExpandPath(tmhome), "config", "priv_validator_key.json") - stateFile := filepath.Join(internal.ExpandPath(tmhome), "data", "priv_validator_state.json") - fpv, err := privval.LoadFilePV(keyFile, stateFile) - if err != nil { - logger.Error("Can't load file pv", "err", err) - os.Exit(1) - } - pkb := []byte(fpv.Key.PrivKey.(ed25519.PrivKey)) - if err := os.WriteFile(internal.ExpandPath(outputPath), pkb[:32], 0600); err != nil { - logger.Info("Failed to write private key", "output", outputPath, "err", err) - os.Exit(1) - } - logger.Info("Successfully wrote private key", "output", outputPath) -} - -func main() { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - if err := rootCmd.Parse(os.Args[1:]); err != nil { - fmt.Printf("Error parsing flags: %v\n", err) - os.Exit(1) - } - if rootCmd.NArg() == 0 || (rootCmd.NArg() == 1 && rootCmd.Arg(0) == "help") { - rootCmd.Usage() - os.Exit(0) - } - - switch rootCmd.Arg(0) { - case "help": - switch rootCmd.Arg(1) { - case "run": - runCmd.Usage() - case "extract_key": - extractKeyCmd.Usage() - case "version": - versionCmd.Usage() - default: - fmt.Printf("Unrecognized command: %s\n", rootCmd.Arg(1)) - os.Exit(1) - } - case "run": - if err := runCmd.Parse(os.Args[2:]); err != nil { - fmt.Printf("Error parsing flags: %v\n", err) - os.Exit(1) - } - runTestHarness(ctx, flagAcceptRetries, flagBindAddr, flagTMHome) - case "extract_key": - if err := extractKeyCmd.Parse(os.Args[2:]); err != nil { - fmt.Printf("Error parsing flags: %v\n", err) - os.Exit(1) - } - extractKey(flagTMHome, flagKeyOutputPath) - case "version": - fmt.Println(version.TMVersion) - default: - fmt.Printf("Unrecognized command: %s\n", flag.Arg(0)) - os.Exit(1) - } -}