mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-08 14:21:14 +00:00
tools: remove tm-signer-harness (#7370)
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
ARG TENDERMINT_VERSION=latest
|
||||
FROM tendermint/tendermint:${TENDERMINT_VERSION}
|
||||
|
||||
COPY tm-signer-harness /usr/bin/tm-signer-harness
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 <command> [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 <command>" 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user