Release/v0.28.0pull/3138/head v0.28.0
@ -1,40 +0,0 @@ | |||
{ | |||
"dependencies": { | |||
"prettier": "^1.13.7", | |||
"remark-cli": "^5.0.0", | |||
"remark-lint-no-dead-urls": "^0.3.0", | |||
"remark-lint-write-good": "^1.0.3", | |||
"textlint": "^10.2.1", | |||
"textlint-rule-stop-words": "^1.0.3" | |||
}, | |||
"name": "tendermint", | |||
"description": "Tendermint Core Documentation", | |||
"version": "0.0.1", | |||
"main": "README.md", | |||
"devDependencies": {}, | |||
"scripts": { | |||
"lint:json": "prettier \"**/*.json\" --write", | |||
"lint:md": "prettier \"**/*.md\" --write && remark . && textlint \"md/**\"", | |||
"lint": "yarn lint:json && yarn lint:md" | |||
}, | |||
"repository": { | |||
"type": "git", | |||
"url": "git+https://github.com/tendermint/tendermint.git" | |||
}, | |||
"keywords": [ | |||
"tendermint", | |||
"blockchain" | |||
], | |||
"author": "Tendermint", | |||
"license": "ISC", | |||
"bugs": { | |||
"url": "https://github.com/tendermint/tendermint/issues" | |||
}, | |||
"homepage": "https://tendermint.com/docs/", | |||
"remarkConfig": { | |||
"plugins": [ | |||
"remark-lint-no-dead-urls", | |||
"remark-lint-write-good" | |||
] | |||
} | |||
} |
@ -0,0 +1,205 @@ | |||
# Validator Signing | |||
Here we specify the rules for validating a proposal and vote before signing. | |||
First we include some general notes on validating data structures common to both types. | |||
We then provide specific validation rules for each. Finally, we include validation rules to prevent double-sigining. | |||
## SignedMsgType | |||
The `SignedMsgType` is a single byte that refers to the type of the message | |||
being signed. It is defined in Go as follows: | |||
``` | |||
// SignedMsgType is a type of signed message in the consensus. | |||
type SignedMsgType byte | |||
const ( | |||
// Votes | |||
PrevoteType SignedMsgType = 0x01 | |||
PrecommitType SignedMsgType = 0x02 | |||
// Proposals | |||
ProposalType SignedMsgType = 0x20 | |||
) | |||
``` | |||
All signed messages must correspond to one of these types. | |||
## Timestamp | |||
Timestamp validation is subtle and there are currently no bounds placed on the | |||
timestamp included in a proposal or vote. It is expected that validators will honestly | |||
report their local clock time. The median of all timestamps | |||
included in a commit is used as the timestamp for the next block height. | |||
Timestamps are expected to be strictly monotonic for a given validator, though | |||
this is not currently enforced. | |||
## ChainID | |||
ChainID is an unstructured string with a max length of 50-bytes. | |||
In the future, the ChainID may become structured, and may take on longer lengths. | |||
For now, it is recommended that signers be configured for a particular ChainID, | |||
and to only sign votes and proposals corresponding to that ChainID. | |||
## BlockID | |||
BlockID is the structure used to represent the block: | |||
``` | |||
type BlockID struct { | |||
Hash []byte | |||
PartsHeader PartSetHeader | |||
} | |||
type PartSetHeader struct { | |||
Hash []byte | |||
Total int | |||
} | |||
``` | |||
To be included in a valid vote or proposal, BlockID must either represent a `nil` block, or a complete one. | |||
We introduce two methods, `BlockID.IsNil()` and `BlockID.IsComplete()` for these cases, respectively. | |||
`BlockID.IsNil()` returns true for BlockID `b` if each of the following | |||
are true: | |||
``` | |||
b.Hash == nil | |||
b.PartsHeader.Total == 0 | |||
b.PartsHeader.Hash == nil | |||
``` | |||
`BlockID.IsComplete()` returns true for BlockID `b` if each of the following | |||
are true: | |||
``` | |||
len(b.Hash) == 32 | |||
b.PartsHeader.Total > 0 | |||
len(b.PartsHeader.Hash) == 32 | |||
``` | |||
## Proposals | |||
The structure of a propsal for signing looks like: | |||
``` | |||
type CanonicalProposal struct { | |||
Type SignedMsgType // type alias for byte | |||
Height int64 `binary:"fixed64"` | |||
Round int64 `binary:"fixed64"` | |||
POLRound int64 `binary:"fixed64"` | |||
BlockID BlockID | |||
Timestamp time.Time | |||
ChainID string | |||
} | |||
``` | |||
A proposal is valid if each of the following lines evaluates to true for proposal `p`: | |||
``` | |||
p.Type == 0x20 | |||
p.Height > 0 | |||
p.Round >= 0 | |||
p.POLRound >= -1 | |||
p.BlockID.IsComplete() | |||
``` | |||
In other words, a proposal is valid for signing if it contains the type of a Proposal | |||
(0x20), has a positive, non-zero height, a | |||
non-negative round, a POLRound not less than -1, and a complete BlockID. | |||
## Votes | |||
The structure of a vote for signing looks like: | |||
``` | |||
type CanonicalVote struct { | |||
Type SignedMsgType // type alias for byte | |||
Height int64 `binary:"fixed64"` | |||
Round int64 `binary:"fixed64"` | |||
Timestamp time.Time | |||
BlockID BlockID | |||
ChainID string | |||
} | |||
``` | |||
A vote is valid if each of the following lines evaluates to true for vote `v`: | |||
``` | |||
v.Type == 0x1 || v.Type == 0x2 | |||
v.Height > 0 | |||
v.Round >= 0 | |||
v.BlockID.IsNil() || v.BlockID.IsValid() | |||
``` | |||
In other words, a vote is valid for signing if it contains the type of a Prevote | |||
or Precommit (0x1 or 0x2, respectively), has a positive, non-zero height, a | |||
non-negative round, and an empty or valid BlockID. | |||
## Invalid Votes and Proposals | |||
Votes and proposals which do not satisfy the above rules are considered invalid. | |||
Peers gossipping invalid votes and proposals may be disconnected from other peers on the network. | |||
Note, however, that there is not currently any explicit mechanism to punish validators signing votes or proposals that fail | |||
these basic validation rules. | |||
## Double Signing | |||
Signers must be careful not to sign conflicting messages, also known as "double signing" or "equivocating". | |||
Tendermint has mechanisms to publish evidence of validators that signed conflicting votes, so they can be punished | |||
by the application. Note Tendermint does not currently handle evidence of conflciting proposals, though it may in the future. | |||
### State | |||
To prevent such double signing, signers must track the height, round, and type of the last message signed. | |||
Assume the signer keeps the following state, `s`: | |||
``` | |||
type LastSigned struct { | |||
Height int64 | |||
Round int64 | |||
Type SignedMsgType // byte | |||
} | |||
``` | |||
After signing a vote or proposal `m`, the signer sets: | |||
``` | |||
s.Height = m.Height | |||
s.Round = m.Round | |||
s.Type = m.Type | |||
``` | |||
### Proposals | |||
A signer should only sign a proposal `p` if any of the following lines are true: | |||
``` | |||
p.Height > s.Height | |||
p.Height == s.Height && p.Round > s.Round | |||
``` | |||
In other words, a proposal should only be signed if it's at a higher height, or a higher round for the same height. | |||
Once a proposal or vote has been signed for a given height and round, a proposal should never be signed for the same height and round. | |||
### Votes | |||
A signer should only sign a vote `v` if any of the following lines are true: | |||
``` | |||
v.Height > s.Height | |||
v.Height == s.Height && v.Round > s.Round | |||
v.Height == s.Height && v.Round == s.Round && v.Step == 0x1 && s.Step == 0x20 | |||
v.Height == s.Height && v.Round == s.Round && v.Step == 0x2 && s.Step != 0x2 | |||
``` | |||
In other words, a vote should only be signed if it's: | |||
- at a higher height | |||
- at a higher round for the same height | |||
- a prevote for the same height and round where we haven't signed a prevote or precommit (but have signed a proposal) | |||
- a precommit for the same height and round where we haven't signed a precommit (but have signed a proposal and/or a prevote) | |||
This means that once a validator signs a prevote for a given height and round, the only other message it can sign for that height and round is a precommit. | |||
And once a validator signs a precommit for a given height and round, it must not sign any other message for that same height and round. |
@ -0,0 +1,238 @@ | |||
package privval | |||
import ( | |||
"errors" | |||
"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 ( | |||
defaultConnHeartBeatSeconds = 2 | |||
defaultDialRetries = 10 | |||
) | |||
// Socket errors. | |||
var ( | |||
ErrUnexpectedResponse = errors.New("received unexpected response") | |||
) | |||
var ( | |||
connHeartbeat = time.Second * defaultConnHeartBeatSeconds | |||
) | |||
// SocketValOption sets an optional parameter on the SocketVal. | |||
type SocketValOption func(*SocketVal) | |||
// SocketValHeartbeat sets the period on which to check the liveness of the | |||
// connected Signer connections. | |||
func SocketValHeartbeat(period time.Duration) SocketValOption { | |||
return func(sc *SocketVal) { sc.connHeartbeat = period } | |||
} | |||
// SocketVal implements PrivValidator. | |||
// It listens for an external process to dial in and uses | |||
// the socket to request signatures. | |||
type SocketVal struct { | |||
cmn.BaseService | |||
listener net.Listener | |||
// ping | |||
cancelPing chan struct{} | |||
pingTicker *time.Ticker | |||
connHeartbeat time.Duration | |||
// signer is mutable since it can be | |||
// reset if the connection fails. | |||
// failures are detected by a background | |||
// ping routine. | |||
// Methods on the underlying net.Conn itself | |||
// are already gorountine safe. | |||
mtx sync.RWMutex | |||
signer *RemoteSignerClient | |||
} | |||
// Check that SocketVal implements PrivValidator. | |||
var _ types.PrivValidator = (*SocketVal)(nil) | |||
// NewSocketVal returns an instance of SocketVal. | |||
func NewSocketVal( | |||
logger log.Logger, | |||
listener net.Listener, | |||
) *SocketVal { | |||
sc := &SocketVal{ | |||
listener: listener, | |||
connHeartbeat: connHeartbeat, | |||
} | |||
sc.BaseService = *cmn.NewBaseService(logger, "SocketVal", sc) | |||
return sc | |||
} | |||
//-------------------------------------------------------- | |||
// Implement PrivValidator | |||
// GetPubKey implements PrivValidator. | |||
func (sc *SocketVal) GetPubKey() crypto.PubKey { | |||
sc.mtx.RLock() | |||
defer sc.mtx.RUnlock() | |||
return sc.signer.GetPubKey() | |||
} | |||
// SignVote implements PrivValidator. | |||
func (sc *SocketVal) SignVote(chainID string, vote *types.Vote) error { | |||
sc.mtx.RLock() | |||
defer sc.mtx.RUnlock() | |||
return sc.signer.SignVote(chainID, vote) | |||
} | |||
// SignProposal implements PrivValidator. | |||
func (sc *SocketVal) SignProposal(chainID string, proposal *types.Proposal) error { | |||
sc.mtx.RLock() | |||
defer sc.mtx.RUnlock() | |||
return sc.signer.SignProposal(chainID, proposal) | |||
} | |||
//-------------------------------------------------------- | |||
// More thread safe methods proxied to the signer | |||
// Ping is used to check connection health. | |||
func (sc *SocketVal) Ping() error { | |||
sc.mtx.RLock() | |||
defer sc.mtx.RUnlock() | |||
return sc.signer.Ping() | |||
} | |||
// Close closes the underlying net.Conn. | |||
func (sc *SocketVal) Close() { | |||
sc.mtx.RLock() | |||
defer sc.mtx.RUnlock() | |||
if sc.signer != nil { | |||
if err := sc.signer.Close(); err != nil { | |||
sc.Logger.Error("OnStop", "err", err) | |||
} | |||
} | |||
if sc.listener != nil { | |||
if err := sc.listener.Close(); err != nil { | |||
sc.Logger.Error("OnStop", "err", err) | |||
} | |||
} | |||
} | |||
//-------------------------------------------------------- | |||
// Service start and stop | |||
// OnStart implements cmn.Service. | |||
func (sc *SocketVal) OnStart() error { | |||
if closed, err := sc.reset(); err != nil { | |||
sc.Logger.Error("OnStart", "err", err) | |||
return err | |||
} else if closed { | |||
return fmt.Errorf("listener is closed") | |||
} | |||
// Start a routine to keep the connection alive | |||
sc.cancelPing = make(chan struct{}, 1) | |||
sc.pingTicker = time.NewTicker(sc.connHeartbeat) | |||
go func() { | |||
for { | |||
select { | |||
case <-sc.pingTicker.C: | |||
err := sc.Ping() | |||
if err != nil { | |||
sc.Logger.Error("Ping", "err", err) | |||
if err == ErrUnexpectedResponse { | |||
return | |||
} | |||
closed, err := sc.reset() | |||
if err != nil { | |||
sc.Logger.Error("Reconnecting to remote signer failed", "err", err) | |||
continue | |||
} | |||
if closed { | |||
sc.Logger.Info("listener is closing") | |||
return | |||
} | |||
sc.Logger.Info("Re-created connection to remote signer", "impl", sc) | |||
} | |||
case <-sc.cancelPing: | |||
sc.pingTicker.Stop() | |||
return | |||
} | |||
} | |||
}() | |||
return nil | |||
} | |||
// OnStop implements cmn.Service. | |||
func (sc *SocketVal) OnStop() { | |||
if sc.cancelPing != nil { | |||
close(sc.cancelPing) | |||
} | |||
sc.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 (sc *SocketVal) reset() (closed bool, err error) { | |||
sc.mtx.Lock() | |||
defer sc.mtx.Unlock() | |||
// first check if the conn already exists and close it. | |||
if sc.signer != nil { | |||
if err := sc.signer.Close(); err != nil { | |||
sc.Logger.Error("error closing socket val connection during reset", "err", err) | |||
} | |||
} | |||
// wait for a new conn | |||
conn, err := sc.acceptConnection() | |||
if err != nil { | |||
return false, err | |||
} | |||
// listener is closed | |||
if conn == nil { | |||
return true, nil | |||
} | |||
sc.signer, err = NewRemoteSignerClient(conn) | |||
if err != nil { | |||
// failed to fetch the pubkey. close out the connection. | |||
if err := conn.Close(); err != nil { | |||
sc.Logger.Error("error closing connection", "err", err) | |||
} | |||
return false, err | |||
} | |||
return false, nil | |||
} | |||
// Attempt to accept a connection. | |||
// Times out after the listener's acceptDeadline | |||
func (sc *SocketVal) acceptConnection() (net.Conn, error) { | |||
conn, err := sc.listener.Accept() | |||
if err != nil { | |||
if !sc.IsRunning() { | |||
return nil, nil // Ignore error from listener closing. | |||
} | |||
return nil, err | |||
} | |||
return conn, nil | |||
} |
@ -0,0 +1,466 @@ | |||
package privval | |||
import ( | |||
"fmt" | |||
"net" | |||
"testing" | |||
"time" | |||
"github.com/stretchr/testify/assert" | |||
"github.com/stretchr/testify/require" | |||
"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" | |||
) | |||
var ( | |||
testAcceptDeadline = defaultAcceptDeadlineSeconds * time.Second | |||
testConnDeadline = 100 * time.Millisecond | |||
testConnDeadline2o3 = 66 * time.Millisecond // 2/3 of the other one | |||
testHeartbeatTimeout = 10 * time.Millisecond | |||
testHeartbeatTimeout3o2 = 6 * time.Millisecond // 3/2 of the other one | |||
) | |||
type socketTestCase struct { | |||
addr string | |||
dialer Dialer | |||
} | |||
func socketTestCases(t *testing.T) []socketTestCase { | |||
tcpAddr := fmt.Sprintf("tcp://%s", testFreeTCPAddr(t)) | |||
unixFilePath, err := testUnixAddr() | |||
require.NoError(t, err) | |||
unixAddr := fmt.Sprintf("unix://%s", unixFilePath) | |||
return []socketTestCase{ | |||
socketTestCase{ | |||
addr: tcpAddr, | |||
dialer: DialTCPFn(tcpAddr, testConnDeadline, ed25519.GenPrivKey()), | |||
}, | |||
socketTestCase{ | |||
addr: unixAddr, | |||
dialer: DialUnixFn(unixFilePath), | |||
}, | |||
} | |||
} | |||
func TestSocketPVAddress(t *testing.T) { | |||
for _, tc := range socketTestCases(t) { | |||
// Execute the test within a closure to ensure the deferred statements | |||
// are called between each for loop iteration, for isolated test cases. | |||
func() { | |||
var ( | |||
chainID = cmn.RandStr(12) | |||
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer) | |||
) | |||
defer sc.Stop() | |||
defer rs.Stop() | |||
serverAddr := rs.privVal.GetPubKey().Address() | |||
clientAddr := sc.GetPubKey().Address() | |||
assert.Equal(t, serverAddr, clientAddr) | |||
}() | |||
} | |||
} | |||
func TestSocketPVPubKey(t *testing.T) { | |||
for _, tc := range socketTestCases(t) { | |||
func() { | |||
var ( | |||
chainID = cmn.RandStr(12) | |||
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer) | |||
) | |||
defer sc.Stop() | |||
defer rs.Stop() | |||
clientKey := sc.GetPubKey() | |||
privvalPubKey := rs.privVal.GetPubKey() | |||
assert.Equal(t, privvalPubKey, clientKey) | |||
}() | |||
} | |||
} | |||
func TestSocketPVProposal(t *testing.T) { | |||
for _, tc := range socketTestCases(t) { | |||
func() { | |||
var ( | |||
chainID = cmn.RandStr(12) | |||
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer) | |||
ts = time.Now() | |||
privProposal = &types.Proposal{Timestamp: ts} | |||
clientProposal = &types.Proposal{Timestamp: ts} | |||
) | |||
defer sc.Stop() | |||
defer rs.Stop() | |||
require.NoError(t, rs.privVal.SignProposal(chainID, privProposal)) | |||
require.NoError(t, sc.SignProposal(chainID, clientProposal)) | |||
assert.Equal(t, privProposal.Signature, clientProposal.Signature) | |||
}() | |||
} | |||
} | |||
func TestSocketPVVote(t *testing.T) { | |||
for _, tc := range socketTestCases(t) { | |||
func() { | |||
var ( | |||
chainID = cmn.RandStr(12) | |||
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer) | |||
ts = time.Now() | |||
vType = types.PrecommitType | |||
want = &types.Vote{Timestamp: ts, Type: vType} | |||
have = &types.Vote{Timestamp: ts, Type: vType} | |||
) | |||
defer sc.Stop() | |||
defer rs.Stop() | |||
require.NoError(t, rs.privVal.SignVote(chainID, want)) | |||
require.NoError(t, sc.SignVote(chainID, have)) | |||
assert.Equal(t, want.Signature, have.Signature) | |||
}() | |||
} | |||
} | |||
func TestSocketPVVoteResetDeadline(t *testing.T) { | |||
for _, tc := range socketTestCases(t) { | |||
func() { | |||
var ( | |||
chainID = cmn.RandStr(12) | |||
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer) | |||
ts = time.Now() | |||
vType = types.PrecommitType | |||
want = &types.Vote{Timestamp: ts, Type: vType} | |||
have = &types.Vote{Timestamp: ts, Type: vType} | |||
) | |||
defer sc.Stop() | |||
defer rs.Stop() | |||
time.Sleep(testConnDeadline2o3) | |||
require.NoError(t, rs.privVal.SignVote(chainID, want)) | |||
require.NoError(t, sc.SignVote(chainID, have)) | |||
assert.Equal(t, want.Signature, have.Signature) | |||
// This would exceed the deadline if it was not extended by the previous message | |||
time.Sleep(testConnDeadline2o3) | |||
require.NoError(t, rs.privVal.SignVote(chainID, want)) | |||
require.NoError(t, sc.SignVote(chainID, have)) | |||
assert.Equal(t, want.Signature, have.Signature) | |||
}() | |||
} | |||
} | |||
func TestSocketPVVoteKeepalive(t *testing.T) { | |||
for _, tc := range socketTestCases(t) { | |||
func() { | |||
var ( | |||
chainID = cmn.RandStr(12) | |||
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer) | |||
ts = time.Now() | |||
vType = types.PrecommitType | |||
want = &types.Vote{Timestamp: ts, Type: vType} | |||
have = &types.Vote{Timestamp: ts, Type: vType} | |||
) | |||
defer sc.Stop() | |||
defer rs.Stop() | |||
time.Sleep(testConnDeadline * 2) | |||
require.NoError(t, rs.privVal.SignVote(chainID, want)) | |||
require.NoError(t, sc.SignVote(chainID, have)) | |||
assert.Equal(t, want.Signature, have.Signature) | |||
}() | |||
} | |||
} | |||
// TestSocketPVDeadlineTCPOnly is not relevant to Unix domain sockets, since the | |||
// OS knows instantaneously the state of both sides of the connection. | |||
func TestSocketPVDeadlineTCPOnly(t *testing.T) { | |||
var ( | |||
addr = testFreeTCPAddr(t) | |||
listenc = make(chan struct{}) | |||
thisConnTimeout = 100 * time.Millisecond | |||
sc = newSocketVal(log.TestingLogger(), addr, thisConnTimeout) | |||
) | |||
go func(sc *SocketVal) { | |||
defer close(listenc) | |||
assert.Equal(t, sc.Start().(cmn.Error).Data(), ErrConnTimeout) | |||
assert.False(t, sc.IsRunning()) | |||
}(sc) | |||
for { | |||
conn, err := cmn.Connect(addr) | |||
if err != nil { | |||
continue | |||
} | |||
_, err = p2pconn.MakeSecretConnection( | |||
conn, | |||
ed25519.GenPrivKey(), | |||
) | |||
if err == nil { | |||
break | |||
} | |||
} | |||
<-listenc | |||
} | |||
func TestRemoteSignVoteErrors(t *testing.T) { | |||
for _, tc := range socketTestCases(t) { | |||
func() { | |||
var ( | |||
chainID = cmn.RandStr(12) | |||
sc, rs = testSetupSocketPair(t, chainID, types.NewErroringMockPV(), tc.addr, tc.dialer) | |||
ts = time.Now() | |||
vType = types.PrecommitType | |||
vote = &types.Vote{Timestamp: ts, Type: vType} | |||
) | |||
defer sc.Stop() | |||
defer rs.Stop() | |||
err := sc.SignVote("", vote) | |||
require.Equal(t, err.(*RemoteSignerError).Description, types.ErroringMockPVErr.Error()) | |||
err = rs.privVal.SignVote(chainID, vote) | |||
require.Error(t, err) | |||
err = sc.SignVote(chainID, vote) | |||
require.Error(t, err) | |||
}() | |||
} | |||
} | |||
func TestRemoteSignProposalErrors(t *testing.T) { | |||
for _, tc := range socketTestCases(t) { | |||
func() { | |||
var ( | |||
chainID = cmn.RandStr(12) | |||
sc, rs = testSetupSocketPair(t, chainID, types.NewErroringMockPV(), tc.addr, tc.dialer) | |||
ts = time.Now() | |||
proposal = &types.Proposal{Timestamp: ts} | |||
) | |||
defer sc.Stop() | |||
defer rs.Stop() | |||
err := sc.SignProposal("", proposal) | |||
require.Equal(t, err.(*RemoteSignerError).Description, types.ErroringMockPVErr.Error()) | |||
err = rs.privVal.SignProposal(chainID, proposal) | |||
require.Error(t, err) | |||
err = sc.SignProposal(chainID, proposal) | |||
require.Error(t, err) | |||
}() | |||
} | |||
} | |||
func TestErrUnexpectedResponse(t *testing.T) { | |||
for _, tc := range socketTestCases(t) { | |||
func() { | |||
var ( | |||
logger = log.TestingLogger() | |||
chainID = cmn.RandStr(12) | |||
readyc = make(chan struct{}) | |||
errc = make(chan error, 1) | |||
rs = NewRemoteSigner( | |||
logger, | |||
chainID, | |||
types.NewMockPV(), | |||
tc.dialer, | |||
) | |||
sc = newSocketVal(logger, tc.addr, testConnDeadline) | |||
) | |||
testStartSocketPV(t, readyc, sc) | |||
defer sc.Stop() | |||
RemoteSignerConnDeadline(time.Millisecond)(rs) | |||
RemoteSignerConnRetries(100)(rs) | |||
// we do not want to Start() the remote signer here and instead use the connection to | |||
// reply with intentionally wrong replies below: | |||
rsConn, err := rs.connect() | |||
defer rsConn.Close() | |||
require.NoError(t, err) | |||
require.NotNil(t, rsConn) | |||
// send over public key to get the remote signer running: | |||
go testReadWriteResponse(t, &PubKeyResponse{}, rsConn) | |||
<-readyc | |||
// Proposal: | |||
go func(errc chan error) { | |||
errc <- sc.SignProposal(chainID, &types.Proposal{}) | |||
}(errc) | |||
// read request and write wrong response: | |||
go testReadWriteResponse(t, &SignedVoteResponse{}, rsConn) | |||
err = <-errc | |||
require.Error(t, err) | |||
require.Equal(t, err, ErrUnexpectedResponse) | |||
// Vote: | |||
go func(errc chan error) { | |||
errc <- sc.SignVote(chainID, &types.Vote{}) | |||
}(errc) | |||
// read request and write wrong response: | |||
go testReadWriteResponse(t, &SignedProposalResponse{}, rsConn) | |||
err = <-errc | |||
require.Error(t, err) | |||
require.Equal(t, err, ErrUnexpectedResponse) | |||
}() | |||
} | |||
} | |||
func TestRetryConnToRemoteSigner(t *testing.T) { | |||
for _, tc := range socketTestCases(t) { | |||
func() { | |||
var ( | |||
logger = log.TestingLogger() | |||
chainID = cmn.RandStr(12) | |||
readyc = make(chan struct{}) | |||
rs = NewRemoteSigner( | |||
logger, | |||
chainID, | |||
types.NewMockPV(), | |||
tc.dialer, | |||
) | |||
thisConnTimeout = testConnDeadline | |||
sc = newSocketVal(logger, tc.addr, thisConnTimeout) | |||
) | |||
// Ping every: | |||
SocketValHeartbeat(testHeartbeatTimeout)(sc) | |||
RemoteSignerConnDeadline(testConnDeadline)(rs) | |||
RemoteSignerConnRetries(10)(rs) | |||
testStartSocketPV(t, readyc, sc) | |||
defer sc.Stop() | |||
require.NoError(t, rs.Start()) | |||
assert.True(t, rs.IsRunning()) | |||
<-readyc | |||
time.Sleep(testHeartbeatTimeout * 2) | |||
rs.Stop() | |||
rs2 := NewRemoteSigner( | |||
logger, | |||
chainID, | |||
types.NewMockPV(), | |||
tc.dialer, | |||
) | |||
// let some pings pass | |||
time.Sleep(testHeartbeatTimeout3o2) | |||
require.NoError(t, rs2.Start()) | |||
assert.True(t, rs2.IsRunning()) | |||
defer rs2.Stop() | |||
// give the client some time to re-establish the conn to the remote signer | |||
// should see sth like this in the logs: | |||
// | |||
// E[10016-01-10|17:12:46.128] Ping err="remote signer timed out" | |||
// I[10016-01-10|17:16:42.447] Re-created connection to remote signer impl=SocketVal | |||
time.Sleep(testConnDeadline * 2) | |||
}() | |||
} | |||
} | |||
func newSocketVal(logger log.Logger, addr string, connDeadline time.Duration) *SocketVal { | |||
proto, address := cmn.ProtocolAndAddress(addr) | |||
ln, err := net.Listen(proto, address) | |||
logger.Info("Listening at", "proto", proto, "address", address) | |||
if err != nil { | |||
panic(err) | |||
} | |||
var svln net.Listener | |||
if proto == "unix" { | |||
unixLn := NewUnixListener(ln) | |||
UnixListenerAcceptDeadline(testAcceptDeadline)(unixLn) | |||
UnixListenerConnDeadline(connDeadline)(unixLn) | |||
svln = unixLn | |||
} else { | |||
tcpLn := NewTCPListener(ln, ed25519.GenPrivKey()) | |||
TCPListenerAcceptDeadline(testAcceptDeadline)(tcpLn) | |||
TCPListenerConnDeadline(connDeadline)(tcpLn) | |||
svln = tcpLn | |||
} | |||
return NewSocketVal(logger, svln) | |||
} | |||
func testSetupSocketPair( | |||
t *testing.T, | |||
chainID string, | |||
privValidator types.PrivValidator, | |||
addr string, | |||
dialer Dialer, | |||
) (*SocketVal, *RemoteSigner) { | |||
var ( | |||
logger = log.TestingLogger() | |||
privVal = privValidator | |||
readyc = make(chan struct{}) | |||
rs = NewRemoteSigner( | |||
logger, | |||
chainID, | |||
privVal, | |||
dialer, | |||
) | |||
thisConnTimeout = testConnDeadline | |||
sc = newSocketVal(logger, addr, thisConnTimeout) | |||
) | |||
SocketValHeartbeat(testHeartbeatTimeout)(sc) | |||
RemoteSignerConnDeadline(testConnDeadline)(rs) | |||
RemoteSignerConnRetries(1e6)(rs) | |||
testStartSocketPV(t, readyc, sc) | |||
require.NoError(t, rs.Start()) | |||
assert.True(t, rs.IsRunning()) | |||
<-readyc | |||
return sc, rs | |||
} | |||
func testReadWriteResponse(t *testing.T, resp RemoteSignerMsg, rsConn net.Conn) { | |||
_, err := readMsg(rsConn) | |||
require.NoError(t, err) | |||
err = writeMsg(rsConn, resp) | |||
require.NoError(t, err) | |||
} | |||
func testStartSocketPV(t *testing.T, readyc chan struct{}, sc *SocketVal) { | |||
go func(sc *SocketVal) { | |||
require.NoError(t, sc.Start()) | |||
assert.True(t, sc.IsRunning()) | |||
readyc <- struct{}{} | |||
}(sc) | |||
} | |||
// testFreeTCPAddr claims a free port so we don't block on listener being ready. | |||
func testFreeTCPAddr(t *testing.T) string { | |||
ln, err := net.Listen("tcp", "127.0.0.1:0") | |||
require.NoError(t, err) | |||
defer ln.Close() | |||
return fmt.Sprintf("127.0.0.1:%d", ln.Addr().(*net.TCPAddr).Port) | |||
} |
@ -0,0 +1,21 @@ | |||
/* | |||
Package privval provides different implementations of the types.PrivValidator. | |||
FilePV | |||
FilePV is the simplest implementation and developer default. It uses one file for the private key and another to store state. | |||
SocketVal | |||
SocketVal establishes a connection to an external process, like a Key Management Server (KMS), using a socket. | |||
SocketVal listens for the external KMS process to dial in. | |||
SocketVal takes a listener, which determines the type of connection | |||
(ie. encrypted over tcp, or unencrypted over unix). | |||
RemoteSigner | |||
RemoteSigner is a simple wrapper around a net.Conn. It's used by both IPCVal and TCPVal. | |||
*/ | |||
package privval |
@ -1,120 +0,0 @@ | |||
package privval | |||
import ( | |||
"net" | |||
"time" | |||
cmn "github.com/tendermint/tendermint/libs/common" | |||
"github.com/tendermint/tendermint/libs/log" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
// IPCValOption sets an optional parameter on the SocketPV. | |||
type IPCValOption func(*IPCVal) | |||
// IPCValConnTimeout sets the read and write timeout for connections | |||
// from external signing processes. | |||
func IPCValConnTimeout(timeout time.Duration) IPCValOption { | |||
return func(sc *IPCVal) { sc.connTimeout = timeout } | |||
} | |||
// IPCValHeartbeat sets the period on which to check the liveness of the | |||
// connected Signer connections. | |||
func IPCValHeartbeat(period time.Duration) IPCValOption { | |||
return func(sc *IPCVal) { sc.connHeartbeat = period } | |||
} | |||
// IPCVal implements PrivValidator, it uses a unix socket to request signatures | |||
// from an external process. | |||
type IPCVal struct { | |||
cmn.BaseService | |||
*RemoteSignerClient | |||
addr string | |||
connTimeout time.Duration | |||
connHeartbeat time.Duration | |||
conn net.Conn | |||
cancelPing chan struct{} | |||
pingTicker *time.Ticker | |||
} | |||
// Check that IPCVal implements PrivValidator. | |||
var _ types.PrivValidator = (*IPCVal)(nil) | |||
// NewIPCVal returns an instance of IPCVal. | |||
func NewIPCVal( | |||
logger log.Logger, | |||
socketAddr string, | |||
) *IPCVal { | |||
sc := &IPCVal{ | |||
addr: socketAddr, | |||
connTimeout: connTimeout, | |||
connHeartbeat: connHeartbeat, | |||
} | |||
sc.BaseService = *cmn.NewBaseService(logger, "IPCVal", sc) | |||
return sc | |||
} | |||
// OnStart implements cmn.Service. | |||
func (sc *IPCVal) OnStart() error { | |||
err := sc.connect() | |||
if err != nil { | |||
sc.Logger.Error("OnStart", "err", err) | |||
return err | |||
} | |||
sc.RemoteSignerClient = NewRemoteSignerClient(sc.conn) | |||
// Start a routine to keep the connection alive | |||
sc.cancelPing = make(chan struct{}, 1) | |||
sc.pingTicker = time.NewTicker(sc.connHeartbeat) | |||
go func() { | |||
for { | |||
select { | |||
case <-sc.pingTicker.C: | |||
err := sc.Ping() | |||
if err != nil { | |||
sc.Logger.Error("Ping", "err", err) | |||
} | |||
case <-sc.cancelPing: | |||
sc.pingTicker.Stop() | |||
return | |||
} | |||
} | |||
}() | |||
return nil | |||
} | |||
// OnStop implements cmn.Service. | |||
func (sc *IPCVal) OnStop() { | |||
if sc.cancelPing != nil { | |||
close(sc.cancelPing) | |||
} | |||
if sc.conn != nil { | |||
if err := sc.conn.Close(); err != nil { | |||
sc.Logger.Error("OnStop", "err", err) | |||
} | |||
} | |||
} | |||
func (sc *IPCVal) connect() error { | |||
la, err := net.ResolveUnixAddr("unix", sc.addr) | |||
if err != nil { | |||
return err | |||
} | |||
conn, err := net.DialUnix("unix", nil, la) | |||
if err != nil { | |||
return err | |||
} | |||
sc.conn = newTimeoutConn(conn, sc.connTimeout) | |||
return nil | |||
} |
@ -1,132 +0,0 @@ | |||
package privval | |||
import ( | |||
"io" | |||
"net" | |||
"time" | |||
cmn "github.com/tendermint/tendermint/libs/common" | |||
"github.com/tendermint/tendermint/libs/log" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
// IPCRemoteSignerOption sets an optional parameter on the IPCRemoteSigner. | |||
type IPCRemoteSignerOption func(*IPCRemoteSigner) | |||
// IPCRemoteSignerConnDeadline sets the read and write deadline for connections | |||
// from external signing processes. | |||
func IPCRemoteSignerConnDeadline(deadline time.Duration) IPCRemoteSignerOption { | |||
return func(ss *IPCRemoteSigner) { ss.connDeadline = deadline } | |||
} | |||
// IPCRemoteSignerConnRetries sets the amount of attempted retries to connect. | |||
func IPCRemoteSignerConnRetries(retries int) IPCRemoteSignerOption { | |||
return func(ss *IPCRemoteSigner) { ss.connRetries = retries } | |||
} | |||
// IPCRemoteSigner is a RPC implementation of PrivValidator that listens on a unix socket. | |||
type IPCRemoteSigner struct { | |||
cmn.BaseService | |||
addr string | |||
chainID string | |||
connDeadline time.Duration | |||
connRetries int | |||
privVal types.PrivValidator | |||
listener *net.UnixListener | |||
} | |||
// NewIPCRemoteSigner returns an instance of IPCRemoteSigner. | |||
func NewIPCRemoteSigner( | |||
logger log.Logger, | |||
chainID, socketAddr string, | |||
privVal types.PrivValidator, | |||
) *IPCRemoteSigner { | |||
rs := &IPCRemoteSigner{ | |||
addr: socketAddr, | |||
chainID: chainID, | |||
connDeadline: time.Second * defaultConnDeadlineSeconds, | |||
connRetries: defaultDialRetries, | |||
privVal: privVal, | |||
} | |||
rs.BaseService = *cmn.NewBaseService(logger, "IPCRemoteSigner", rs) | |||
return rs | |||
} | |||
// OnStart implements cmn.Service. | |||
func (rs *IPCRemoteSigner) OnStart() error { | |||
err := rs.listen() | |||
if err != nil { | |||
err = cmn.ErrorWrap(err, "listen") | |||
rs.Logger.Error("OnStart", "err", err) | |||
return err | |||
} | |||
go func() { | |||
for { | |||
conn, err := rs.listener.AcceptUnix() | |||
if err != nil { | |||
rs.Logger.Error("AcceptUnix", "err", err) | |||
return | |||
} | |||
go rs.handleConnection(conn) | |||
} | |||
}() | |||
return nil | |||
} | |||
// OnStop implements cmn.Service. | |||
func (rs *IPCRemoteSigner) OnStop() { | |||
if rs.listener != nil { | |||
if err := rs.listener.Close(); err != nil { | |||
rs.Logger.Error("OnStop", "err", cmn.ErrorWrap(err, "closing listener failed")) | |||
} | |||
} | |||
} | |||
func (rs *IPCRemoteSigner) listen() error { | |||
la, err := net.ResolveUnixAddr("unix", rs.addr) | |||
if err != nil { | |||
return err | |||
} | |||
rs.listener, err = net.ListenUnix("unix", la) | |||
return err | |||
} | |||
func (rs *IPCRemoteSigner) 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 | |||
} | |||
} | |||
} |
@ -1,147 +0,0 @@ | |||
package privval | |||
import ( | |||
"io/ioutil" | |||
"os" | |||
"testing" | |||
"time" | |||
"github.com/stretchr/testify/assert" | |||
"github.com/stretchr/testify/require" | |||
cmn "github.com/tendermint/tendermint/libs/common" | |||
"github.com/tendermint/tendermint/libs/log" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
func TestIPCPVVote(t *testing.T) { | |||
var ( | |||
chainID = cmn.RandStr(12) | |||
sc, rs = testSetupIPCSocketPair(t, chainID, types.NewMockPV()) | |||
ts = time.Now() | |||
vType = types.PrecommitType | |||
want = &types.Vote{Timestamp: ts, Type: vType} | |||
have = &types.Vote{Timestamp: ts, Type: vType} | |||
) | |||
defer sc.Stop() | |||
defer rs.Stop() | |||
require.NoError(t, rs.privVal.SignVote(chainID, want)) | |||
require.NoError(t, sc.SignVote(chainID, have)) | |||
assert.Equal(t, want.Signature, have.Signature) | |||
} | |||
func TestIPCPVVoteResetDeadline(t *testing.T) { | |||
var ( | |||
chainID = cmn.RandStr(12) | |||
sc, rs = testSetupIPCSocketPair(t, chainID, types.NewMockPV()) | |||
ts = time.Now() | |||
vType = types.PrecommitType | |||
want = &types.Vote{Timestamp: ts, Type: vType} | |||
have = &types.Vote{Timestamp: ts, Type: vType} | |||
) | |||
defer sc.Stop() | |||
defer rs.Stop() | |||
time.Sleep(3 * time.Millisecond) | |||
require.NoError(t, rs.privVal.SignVote(chainID, want)) | |||
require.NoError(t, sc.SignVote(chainID, have)) | |||
assert.Equal(t, want.Signature, have.Signature) | |||
// This would exceed the deadline if it was not extended by the previous message | |||
time.Sleep(3 * time.Millisecond) | |||
require.NoError(t, rs.privVal.SignVote(chainID, want)) | |||
require.NoError(t, sc.SignVote(chainID, have)) | |||
assert.Equal(t, want.Signature, have.Signature) | |||
} | |||
func TestIPCPVVoteKeepalive(t *testing.T) { | |||
var ( | |||
chainID = cmn.RandStr(12) | |||
sc, rs = testSetupIPCSocketPair(t, chainID, types.NewMockPV()) | |||
ts = time.Now() | |||
vType = types.PrecommitType | |||
want = &types.Vote{Timestamp: ts, Type: vType} | |||
have = &types.Vote{Timestamp: ts, Type: vType} | |||
) | |||
defer sc.Stop() | |||
defer rs.Stop() | |||
time.Sleep(10 * time.Millisecond) | |||
require.NoError(t, rs.privVal.SignVote(chainID, want)) | |||
require.NoError(t, sc.SignVote(chainID, have)) | |||
assert.Equal(t, want.Signature, have.Signature) | |||
} | |||
func testSetupIPCSocketPair( | |||
t *testing.T, | |||
chainID string, | |||
privValidator types.PrivValidator, | |||
) (*IPCVal, *IPCRemoteSigner) { | |||
addr, err := testUnixAddr() | |||
require.NoError(t, err) | |||
var ( | |||
logger = log.TestingLogger() | |||
privVal = privValidator | |||
readyc = make(chan struct{}) | |||
rs = NewIPCRemoteSigner( | |||
logger, | |||
chainID, | |||
addr, | |||
privVal, | |||
) | |||
sc = NewIPCVal( | |||
logger, | |||
addr, | |||
) | |||
) | |||
IPCValConnTimeout(5 * time.Millisecond)(sc) | |||
IPCValHeartbeat(time.Millisecond)(sc) | |||
IPCRemoteSignerConnDeadline(time.Millisecond * 5)(rs) | |||
testStartIPCRemoteSigner(t, readyc, rs) | |||
<-readyc | |||
require.NoError(t, sc.Start()) | |||
assert.True(t, sc.IsRunning()) | |||
return sc, rs | |||
} | |||
func testStartIPCRemoteSigner(t *testing.T, readyc chan struct{}, rs *IPCRemoteSigner) { | |||
go func(rs *IPCRemoteSigner) { | |||
require.NoError(t, rs.Start()) | |||
assert.True(t, rs.IsRunning()) | |||
readyc <- struct{}{} | |||
}(rs) | |||
} | |||
func testUnixAddr() (string, error) { | |||
f, err := ioutil.TempFile("/tmp", "nettest") | |||
if err != nil { | |||
return "", err | |||
} | |||
addr := f.Name() | |||
err = f.Close() | |||
if err != nil { | |||
return "", err | |||
} | |||
err = os.Remove(addr) | |||
if err != nil { | |||
return "", err | |||
} | |||
return addr, nil | |||
} |
@ -0,0 +1,80 @@ | |||
package privval | |||
import ( | |||
"io/ioutil" | |||
"os" | |||
"github.com/tendermint/tendermint/crypto" | |||
cmn "github.com/tendermint/tendermint/libs/common" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
// OldFilePV is the old version of the FilePV, pre v0.28.0. | |||
type OldFilePV struct { | |||
Address types.Address `json:"address"` | |||
PubKey crypto.PubKey `json:"pub_key"` | |||
LastHeight int64 `json:"last_height"` | |||
LastRound int `json:"last_round"` | |||
LastStep int8 `json:"last_step"` | |||
LastSignature []byte `json:"last_signature,omitempty"` | |||
LastSignBytes cmn.HexBytes `json:"last_signbytes,omitempty"` | |||
PrivKey crypto.PrivKey `json:"priv_key"` | |||
filePath string | |||
} | |||
// LoadOldFilePV loads an OldFilePV from the filePath. | |||
func LoadOldFilePV(filePath string) (*OldFilePV, error) { | |||
pvJSONBytes, err := ioutil.ReadFile(filePath) | |||
if err != nil { | |||
return nil, err | |||
} | |||
pv := &OldFilePV{} | |||
err = cdc.UnmarshalJSON(pvJSONBytes, &pv) | |||
if err != nil { | |||
return nil, err | |||
} | |||
// overwrite pubkey and address for convenience | |||
pv.PubKey = pv.PrivKey.PubKey() | |||
pv.Address = pv.PubKey.Address() | |||
pv.filePath = filePath | |||
return pv, nil | |||
} | |||
// Upgrade convets the OldFilePV to the new FilePV, separating the immutable and mutable components, | |||
// and persisting them to the keyFilePath and stateFilePath, respectively. | |||
// It renames the original file by adding ".bak". | |||
func (oldFilePV *OldFilePV) Upgrade(keyFilePath, stateFilePath string) *FilePV { | |||
privKey := oldFilePV.PrivKey | |||
pvKey := FilePVKey{ | |||
PrivKey: privKey, | |||
PubKey: privKey.PubKey(), | |||
Address: privKey.PubKey().Address(), | |||
filePath: keyFilePath, | |||
} | |||
pvState := FilePVLastSignState{ | |||
Height: oldFilePV.LastHeight, | |||
Round: oldFilePV.LastRound, | |||
Step: oldFilePV.LastStep, | |||
Signature: oldFilePV.LastSignature, | |||
SignBytes: oldFilePV.LastSignBytes, | |||
filePath: stateFilePath, | |||
} | |||
// Save the new PV files | |||
pv := &FilePV{ | |||
Key: pvKey, | |||
LastSignState: pvState, | |||
} | |||
pv.Save() | |||
// Rename the old PV file | |||
err := os.Rename(oldFilePV.filePath, oldFilePV.filePath+".bak") | |||
if err != nil { | |||
panic(err) | |||
} | |||
return pv | |||
} |
@ -0,0 +1,77 @@ | |||
package privval_test | |||
import ( | |||
"io/ioutil" | |||
"os" | |||
"testing" | |||
"github.com/stretchr/testify/assert" | |||
"github.com/stretchr/testify/require" | |||
"github.com/tendermint/tendermint/privval" | |||
) | |||
const oldPrivvalContent = `{ | |||
"address": "1D8089FAFDFAE4A637F3D616E17B92905FA2D91D", | |||
"pub_key": { | |||
"type": "tendermint/PubKeyEd25519", | |||
"value": "r3Yg2AhDZ745CNTpavsGU+mRZ8WpRXqoJuyqjN8mJq0=" | |||
}, | |||
"last_height": "5", | |||
"last_round": "0", | |||
"last_step": 3, | |||
"last_signature": "CTr7b9ZQlrJJf+12rPl5t/YSCUc/KqV7jQogCfFJA24e7hof69X6OMT7eFLVQHyodPjD/QTA298XHV5ejxInDQ==", | |||
"last_signbytes": "750802110500000000000000220B08B398F3E00510F48DA6402A480A20FC258973076512999C3E6839A22E9FBDB1B77CF993E8A9955412A41A59D4CAD312240A20C971B286ACB8AAA6FCA0365EB0A660B189EDC08B46B5AF2995DEFA51A28D215B10013211746573742D636861696E2D533245415533", | |||
"priv_key": { | |||
"type": "tendermint/PrivKeyEd25519", | |||
"value": "7MwvTGEWWjsYwjn2IpRb+GYsWi9nnFsw8jPLLY1UtP6vdiDYCENnvjkI1Olq+wZT6ZFnxalFeqgm7KqM3yYmrQ==" | |||
} | |||
}` | |||
func TestLoadAndUpgrade(t *testing.T) { | |||
oldFilePath := initTmpOldFile(t) | |||
defer os.Remove(oldFilePath) | |||
newStateFile, err := ioutil.TempFile("", "priv_validator_state*.json") | |||
defer os.Remove(newStateFile.Name()) | |||
require.NoError(t, err) | |||
newKeyFile, err := ioutil.TempFile("", "priv_validator_key*.json") | |||
defer os.Remove(newKeyFile.Name()) | |||
require.NoError(t, err) | |||
oldPV, err := privval.LoadOldFilePV(oldFilePath) | |||
assert.NoError(t, err) | |||
newPV := oldPV.Upgrade(newKeyFile.Name(), newStateFile.Name()) | |||
assertEqualPV(t, oldPV, newPV) | |||
assert.NoError(t, err) | |||
upgradedPV := privval.LoadFilePV(newKeyFile.Name(), newStateFile.Name()) | |||
assertEqualPV(t, oldPV, upgradedPV) | |||
oldPV, err = privval.LoadOldFilePV(oldFilePath + ".bak") | |||
require.NoError(t, err) | |||
assertEqualPV(t, oldPV, upgradedPV) | |||
} | |||
func assertEqualPV(t *testing.T, oldPV *privval.OldFilePV, newPV *privval.FilePV) { | |||
assert.Equal(t, oldPV.Address, newPV.Key.Address) | |||
assert.Equal(t, oldPV.Address, newPV.GetAddress()) | |||
assert.Equal(t, oldPV.PubKey, newPV.Key.PubKey) | |||
assert.Equal(t, oldPV.PubKey, newPV.GetPubKey()) | |||
assert.Equal(t, oldPV.PrivKey, newPV.Key.PrivKey) | |||
assert.Equal(t, oldPV.LastHeight, newPV.LastSignState.Height) | |||
assert.Equal(t, oldPV.LastRound, newPV.LastSignState.Round) | |||
assert.Equal(t, oldPV.LastSignature, newPV.LastSignState.Signature) | |||
assert.Equal(t, oldPV.LastSignBytes, newPV.LastSignState.SignBytes) | |||
assert.Equal(t, oldPV.LastStep, newPV.LastSignState.Step) | |||
} | |||
func initTmpOldFile(t *testing.T) string { | |||
tmpfile, err := ioutil.TempFile("", "priv_validator_*.json") | |||
require.NoError(t, err) | |||
t.Logf("created test file %s", tmpfile.Name()) | |||
_, err = tmpfile.WriteString(oldPrivvalContent) | |||
require.NoError(t, err) | |||
return tmpfile.Name() | |||
} |
@ -0,0 +1,68 @@ | |||
package privval | |||
import ( | |||
"net" | |||
"testing" | |||
"time" | |||
"github.com/stretchr/testify/assert" | |||
"github.com/stretchr/testify/require" | |||
"github.com/tendermint/tendermint/crypto/ed25519" | |||
cmn "github.com/tendermint/tendermint/libs/common" | |||
"github.com/tendermint/tendermint/libs/log" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
// TestRemoteSignerRetryTCPOnly will test connection retry attempts over TCP. We | |||
// don't need this for Unix sockets because the OS instantly knows the state of | |||
// both ends of the socket connection. This basically causes the | |||
// RemoteSigner.dialer() call inside RemoteSigner.connect() to return | |||
// successfully immediately, putting an instant stop to any retry attempts. | |||
func TestRemoteSignerRetryTCPOnly(t *testing.T) { | |||
var ( | |||
attemptc = make(chan int) | |||
retries = 2 | |||
) | |||
ln, err := net.Listen("tcp", "127.0.0.1:0") | |||
require.NoError(t, err) | |||
go func(ln net.Listener, attemptc chan<- int) { | |||
attempts := 0 | |||
for { | |||
conn, err := ln.Accept() | |||
require.NoError(t, err) | |||
err = conn.Close() | |||
require.NoError(t, err) | |||
attempts++ | |||
if attempts == retries { | |||
attemptc <- attempts | |||
break | |||
} | |||
} | |||
}(ln, attemptc) | |||
rs := NewRemoteSigner( | |||
log.TestingLogger(), | |||
cmn.RandStr(12), | |||
types.NewMockPV(), | |||
DialTCPFn(ln.Addr().String(), testConnDeadline, ed25519.GenPrivKey()), | |||
) | |||
defer rs.Stop() | |||
RemoteSignerConnDeadline(time.Millisecond)(rs) | |||
RemoteSignerConnRetries(retries)(rs) | |||
assert.Equal(t, rs.Start(), ErrDialRetryMax) | |||
select { | |||
case attempts := <-attemptc: | |||
assert.Equal(t, retries, attempts) | |||
case <-time.After(100 * time.Millisecond): | |||
t.Error("expected remote to observe connection attempts") | |||
} | |||
} |
@ -0,0 +1,184 @@ | |||
package privval | |||
import ( | |||
"net" | |||
"time" | |||
"github.com/tendermint/tendermint/crypto/ed25519" | |||
p2pconn "github.com/tendermint/tendermint/p2p/conn" | |||
) | |||
const ( | |||
defaultAcceptDeadlineSeconds = 3 | |||
defaultConnDeadlineSeconds = 3 | |||
) | |||
// timeoutError can be used to check if an error returned from the netp package | |||
// was due to a timeout. | |||
type timeoutError interface { | |||
Timeout() bool | |||
} | |||
//------------------------------------------------------------------ | |||
// TCP Listener | |||
// TCPListenerOption sets an optional parameter on the tcpListener. | |||
type TCPListenerOption func(*tcpListener) | |||
// TCPListenerAcceptDeadline sets the deadline for the listener. | |||
// A zero time value disables the deadline. | |||
func TCPListenerAcceptDeadline(deadline time.Duration) TCPListenerOption { | |||
return func(tl *tcpListener) { tl.acceptDeadline = deadline } | |||
} | |||
// TCPListenerConnDeadline sets the read and write deadline for connections | |||
// from external signing processes. | |||
func TCPListenerConnDeadline(deadline time.Duration) TCPListenerOption { | |||
return func(tl *tcpListener) { tl.connDeadline = deadline } | |||
} | |||
// tcpListener implements net.Listener. | |||
var _ net.Listener = (*tcpListener)(nil) | |||
// tcpListener wraps a *net.TCPListener to standardise protocol timeouts | |||
// and potentially other tuning parameters. It also returns encrypted connections. | |||
type tcpListener struct { | |||
*net.TCPListener | |||
secretConnKey ed25519.PrivKeyEd25519 | |||
acceptDeadline time.Duration | |||
connDeadline time.Duration | |||
} | |||
// NewTCPListener returns a listener that accepts authenticated encrypted connections | |||
// using the given secretConnKey and the default timeout values. | |||
func NewTCPListener(ln net.Listener, secretConnKey ed25519.PrivKeyEd25519) *tcpListener { | |||
return &tcpListener{ | |||
TCPListener: ln.(*net.TCPListener), | |||
secretConnKey: secretConnKey, | |||
acceptDeadline: time.Second * defaultAcceptDeadlineSeconds, | |||
connDeadline: time.Second * defaultConnDeadlineSeconds, | |||
} | |||
} | |||
// Accept implements net.Listener. | |||
func (ln *tcpListener) Accept() (net.Conn, error) { | |||
err := ln.SetDeadline(time.Now().Add(ln.acceptDeadline)) | |||
if err != nil { | |||
return nil, err | |||
} | |||
tc, err := ln.AcceptTCP() | |||
if err != nil { | |||
return nil, err | |||
} | |||
// Wrap the conn in our timeout and encryption wrappers | |||
timeoutConn := newTimeoutConn(tc, ln.connDeadline) | |||
secretConn, err := p2pconn.MakeSecretConnection(timeoutConn, ln.secretConnKey) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return secretConn, nil | |||
} | |||
//------------------------------------------------------------------ | |||
// Unix Listener | |||
// unixListener implements net.Listener. | |||
var _ net.Listener = (*unixListener)(nil) | |||
type UnixListenerOption func(*unixListener) | |||
// UnixListenerAcceptDeadline sets the deadline for the listener. | |||
// A zero time value disables the deadline. | |||
func UnixListenerAcceptDeadline(deadline time.Duration) UnixListenerOption { | |||
return func(ul *unixListener) { ul.acceptDeadline = deadline } | |||
} | |||
// UnixListenerConnDeadline sets the read and write deadline for connections | |||
// from external signing processes. | |||
func UnixListenerConnDeadline(deadline time.Duration) UnixListenerOption { | |||
return func(ul *unixListener) { ul.connDeadline = deadline } | |||
} | |||
// unixListener wraps a *net.UnixListener to standardise protocol timeouts | |||
// and potentially other tuning parameters. It returns unencrypted connections. | |||
type unixListener struct { | |||
*net.UnixListener | |||
acceptDeadline time.Duration | |||
connDeadline time.Duration | |||
} | |||
// NewUnixListener returns a listener that accepts unencrypted connections | |||
// using the default timeout values. | |||
func NewUnixListener(ln net.Listener) *unixListener { | |||
return &unixListener{ | |||
UnixListener: ln.(*net.UnixListener), | |||
acceptDeadline: time.Second * defaultAcceptDeadlineSeconds, | |||
connDeadline: time.Second * defaultConnDeadlineSeconds, | |||
} | |||
} | |||
// Accept implements net.Listener. | |||
func (ln *unixListener) Accept() (net.Conn, error) { | |||
err := ln.SetDeadline(time.Now().Add(ln.acceptDeadline)) | |||
if err != nil { | |||
return nil, err | |||
} | |||
tc, err := ln.AcceptUnix() | |||
if err != nil { | |||
return nil, err | |||
} | |||
// Wrap the conn in our timeout wrapper | |||
conn := newTimeoutConn(tc, ln.connDeadline) | |||
// TODO: wrap in something that authenticates | |||
// with a MAC - https://github.com/tendermint/tendermint/issues/3099 | |||
return conn, nil | |||
} | |||
//------------------------------------------------------------------ | |||
// Connection | |||
// timeoutConn implements net.Conn. | |||
var _ net.Conn = (*timeoutConn)(nil) | |||
// timeoutConn wraps a net.Conn to standardise protocol timeouts / deadline resets. | |||
type timeoutConn struct { | |||
net.Conn | |||
connDeadline time.Duration | |||
} | |||
// newTimeoutConn returns an instance of timeoutConn. | |||
func newTimeoutConn( | |||
conn net.Conn, | |||
connDeadline time.Duration) *timeoutConn { | |||
return &timeoutConn{ | |||
conn, | |||
connDeadline, | |||
} | |||
} | |||
// Read implements net.Conn. | |||
func (c timeoutConn) Read(b []byte) (n int, err error) { | |||
// Reset deadline | |||
c.Conn.SetReadDeadline(time.Now().Add(c.connDeadline)) | |||
return c.Conn.Read(b) | |||
} | |||
// Write implements net.Conn. | |||
func (c timeoutConn) Write(b []byte) (n int, err error) { | |||
// Reset deadline | |||
c.Conn.SetWriteDeadline(time.Now().Add(c.connDeadline)) | |||
return c.Conn.Write(b) | |||
} |
@ -0,0 +1,133 @@ | |||
package privval | |||
import ( | |||
"io/ioutil" | |||
"net" | |||
"os" | |||
"testing" | |||
"time" | |||
"github.com/tendermint/tendermint/crypto/ed25519" | |||
) | |||
//------------------------------------------- | |||
// helper funcs | |||
func newPrivKey() ed25519.PrivKeyEd25519 { | |||
return ed25519.GenPrivKey() | |||
} | |||
//------------------------------------------- | |||
// tests | |||
type listenerTestCase struct { | |||
description string // For test reporting purposes. | |||
listener net.Listener | |||
dialer Dialer | |||
} | |||
// testUnixAddr will attempt to obtain a platform-independent temporary file | |||
// name for a Unix socket | |||
func testUnixAddr() (string, error) { | |||
f, err := ioutil.TempFile("", "tendermint-privval-test-*") | |||
if err != nil { | |||
return "", err | |||
} | |||
addr := f.Name() | |||
f.Close() | |||
os.Remove(addr) | |||
return addr, nil | |||
} | |||
func tcpListenerTestCase(t *testing.T, acceptDeadline, connectDeadline time.Duration) listenerTestCase { | |||
ln, err := net.Listen("tcp", "127.0.0.1:0") | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
tcpLn := NewTCPListener(ln, newPrivKey()) | |||
TCPListenerAcceptDeadline(acceptDeadline)(tcpLn) | |||
TCPListenerConnDeadline(connectDeadline)(tcpLn) | |||
return listenerTestCase{ | |||
description: "TCP", | |||
listener: tcpLn, | |||
dialer: DialTCPFn(ln.Addr().String(), testConnDeadline, newPrivKey()), | |||
} | |||
} | |||
func unixListenerTestCase(t *testing.T, acceptDeadline, connectDeadline time.Duration) listenerTestCase { | |||
addr, err := testUnixAddr() | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
ln, err := net.Listen("unix", addr) | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
unixLn := NewUnixListener(ln) | |||
UnixListenerAcceptDeadline(acceptDeadline)(unixLn) | |||
UnixListenerConnDeadline(connectDeadline)(unixLn) | |||
return listenerTestCase{ | |||
description: "Unix", | |||
listener: unixLn, | |||
dialer: DialUnixFn(addr), | |||
} | |||
} | |||
func listenerTestCases(t *testing.T, acceptDeadline, connectDeadline time.Duration) []listenerTestCase { | |||
return []listenerTestCase{ | |||
tcpListenerTestCase(t, acceptDeadline, connectDeadline), | |||
unixListenerTestCase(t, acceptDeadline, connectDeadline), | |||
} | |||
} | |||
func TestListenerAcceptDeadlines(t *testing.T) { | |||
for _, tc := range listenerTestCases(t, time.Millisecond, time.Second) { | |||
_, err := tc.listener.Accept() | |||
opErr, ok := err.(*net.OpError) | |||
if !ok { | |||
t.Fatalf("for %s listener, have %v, want *net.OpError", tc.description, err) | |||
} | |||
if have, want := opErr.Op, "accept"; have != want { | |||
t.Errorf("for %s listener, have %v, want %v", tc.description, have, want) | |||
} | |||
} | |||
} | |||
func TestListenerConnectDeadlines(t *testing.T) { | |||
for _, tc := range listenerTestCases(t, time.Second, time.Millisecond) { | |||
readyc := make(chan struct{}) | |||
donec := make(chan struct{}) | |||
go func(ln net.Listener) { | |||
defer close(donec) | |||
c, err := ln.Accept() | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
<-readyc | |||
time.Sleep(2 * time.Millisecond) | |||
msg := make([]byte, 200) | |||
_, err = c.Read(msg) | |||
opErr, ok := err.(*net.OpError) | |||
if !ok { | |||
t.Fatalf("for %s listener, have %v, want *net.OpError", tc.description, err) | |||
} | |||
if have, want := opErr.Op, "read"; have != want { | |||
t.Errorf("for %s listener, have %v, want %v", tc.description, have, want) | |||
} | |||
}(tc.listener) | |||
_, err := tc.dialer() | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
close(readyc) | |||
<-donec | |||
} | |||
} |
@ -1,214 +0,0 @@ | |||
package privval | |||
import ( | |||
"errors" | |||
"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" | |||
) | |||
const ( | |||
defaultAcceptDeadlineSeconds = 3 | |||
defaultConnDeadlineSeconds = 3 | |||
defaultConnHeartBeatSeconds = 2 | |||
defaultDialRetries = 10 | |||
) | |||
// Socket errors. | |||
var ( | |||
ErrDialRetryMax = errors.New("dialed maximum retries") | |||
ErrConnTimeout = errors.New("remote signer timed out") | |||
ErrUnexpectedResponse = errors.New("received unexpected response") | |||
) | |||
var ( | |||
acceptDeadline = time.Second * defaultAcceptDeadlineSeconds | |||
connTimeout = time.Second * defaultConnDeadlineSeconds | |||
connHeartbeat = time.Second * defaultConnHeartBeatSeconds | |||
) | |||
// TCPValOption sets an optional parameter on the SocketPV. | |||
type TCPValOption func(*TCPVal) | |||
// TCPValAcceptDeadline sets the deadline for the TCPVal listener. | |||
// A zero time value disables the deadline. | |||
func TCPValAcceptDeadline(deadline time.Duration) TCPValOption { | |||
return func(sc *TCPVal) { sc.acceptDeadline = deadline } | |||
} | |||
// TCPValConnTimeout sets the read and write timeout for connections | |||
// from external signing processes. | |||
func TCPValConnTimeout(timeout time.Duration) TCPValOption { | |||
return func(sc *TCPVal) { sc.connTimeout = timeout } | |||
} | |||
// TCPValHeartbeat sets the period on which to check the liveness of the | |||
// connected Signer connections. | |||
func TCPValHeartbeat(period time.Duration) TCPValOption { | |||
return func(sc *TCPVal) { sc.connHeartbeat = period } | |||
} | |||
// TCPVal implements PrivValidator, it uses a socket to request signatures | |||
// from an external process. | |||
type TCPVal struct { | |||
cmn.BaseService | |||
*RemoteSignerClient | |||
addr string | |||
acceptDeadline time.Duration | |||
connTimeout time.Duration | |||
connHeartbeat time.Duration | |||
privKey ed25519.PrivKeyEd25519 | |||
conn net.Conn | |||
listener net.Listener | |||
cancelPing chan struct{} | |||
pingTicker *time.Ticker | |||
} | |||
// Check that TCPVal implements PrivValidator. | |||
var _ types.PrivValidator = (*TCPVal)(nil) | |||
// NewTCPVal returns an instance of TCPVal. | |||
func NewTCPVal( | |||
logger log.Logger, | |||
socketAddr string, | |||
privKey ed25519.PrivKeyEd25519, | |||
) *TCPVal { | |||
sc := &TCPVal{ | |||
addr: socketAddr, | |||
acceptDeadline: acceptDeadline, | |||
connTimeout: connTimeout, | |||
connHeartbeat: connHeartbeat, | |||
privKey: privKey, | |||
} | |||
sc.BaseService = *cmn.NewBaseService(logger, "TCPVal", sc) | |||
return sc | |||
} | |||
// OnStart implements cmn.Service. | |||
func (sc *TCPVal) OnStart() error { | |||
if err := sc.listen(); err != nil { | |||
sc.Logger.Error("OnStart", "err", err) | |||
return err | |||
} | |||
conn, err := sc.waitConnection() | |||
if err != nil { | |||
sc.Logger.Error("OnStart", "err", err) | |||
return err | |||
} | |||
sc.conn = conn | |||
sc.RemoteSignerClient = NewRemoteSignerClient(sc.conn) | |||
// Start a routine to keep the connection alive | |||
sc.cancelPing = make(chan struct{}, 1) | |||
sc.pingTicker = time.NewTicker(sc.connHeartbeat) | |||
go func() { | |||
for { | |||
select { | |||
case <-sc.pingTicker.C: | |||
err := sc.Ping() | |||
if err != nil { | |||
sc.Logger.Error( | |||
"Ping", | |||
"err", err, | |||
) | |||
} | |||
case <-sc.cancelPing: | |||
sc.pingTicker.Stop() | |||
return | |||
} | |||
} | |||
}() | |||
return nil | |||
} | |||
// OnStop implements cmn.Service. | |||
func (sc *TCPVal) OnStop() { | |||
if sc.cancelPing != nil { | |||
close(sc.cancelPing) | |||
} | |||
if sc.conn != nil { | |||
if err := sc.conn.Close(); err != nil { | |||
sc.Logger.Error("OnStop", "err", err) | |||
} | |||
} | |||
if sc.listener != nil { | |||
if err := sc.listener.Close(); err != nil { | |||
sc.Logger.Error("OnStop", "err", err) | |||
} | |||
} | |||
} | |||
func (sc *TCPVal) acceptConnection() (net.Conn, error) { | |||
conn, err := sc.listener.Accept() | |||
if err != nil { | |||
if !sc.IsRunning() { | |||
return nil, nil // Ignore error from listener closing. | |||
} | |||
return nil, err | |||
} | |||
conn, err = p2pconn.MakeSecretConnection(conn, sc.privKey) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return conn, nil | |||
} | |||
func (sc *TCPVal) listen() error { | |||
ln, err := net.Listen(cmn.ProtocolAndAddress(sc.addr)) | |||
if err != nil { | |||
return err | |||
} | |||
sc.listener = newTCPTimeoutListener( | |||
ln, | |||
sc.acceptDeadline, | |||
sc.connTimeout, | |||
sc.connHeartbeat, | |||
) | |||
return nil | |||
} | |||
// waitConnection uses the configured wait timeout to error if no external | |||
// process connects in the time period. | |||
func (sc *TCPVal) waitConnection() (net.Conn, error) { | |||
var ( | |||
connc = make(chan net.Conn, 1) | |||
errc = make(chan error, 1) | |||
) | |||
go func(connc chan<- net.Conn, errc chan<- error) { | |||
conn, err := sc.acceptConnection() | |||
if err != nil { | |||
errc <- err | |||
return | |||
} | |||
connc <- conn | |||
}(connc, errc) | |||
select { | |||
case conn := <-connc: | |||
return conn, nil | |||
case err := <-errc: | |||
return nil, err | |||
} | |||
} |
@ -1,90 +0,0 @@ | |||
package privval | |||
import ( | |||
"net" | |||
"time" | |||
) | |||
// timeoutError can be used to check if an error returned from the netp package | |||
// was due to a timeout. | |||
type timeoutError interface { | |||
Timeout() bool | |||
} | |||
// tcpTimeoutListener implements net.Listener. | |||
var _ net.Listener = (*tcpTimeoutListener)(nil) | |||
// tcpTimeoutListener wraps a *net.TCPListener to standardise protocol timeouts | |||
// and potentially other tuning parameters. | |||
type tcpTimeoutListener struct { | |||
*net.TCPListener | |||
acceptDeadline time.Duration | |||
connDeadline time.Duration | |||
period time.Duration | |||
} | |||
// timeoutConn wraps a net.Conn to standardise protocol timeouts / deadline resets. | |||
type timeoutConn struct { | |||
net.Conn | |||
connDeadline time.Duration | |||
} | |||
// newTCPTimeoutListener returns an instance of tcpTimeoutListener. | |||
func newTCPTimeoutListener( | |||
ln net.Listener, | |||
acceptDeadline, connDeadline time.Duration, | |||
period time.Duration, | |||
) tcpTimeoutListener { | |||
return tcpTimeoutListener{ | |||
TCPListener: ln.(*net.TCPListener), | |||
acceptDeadline: acceptDeadline, | |||
connDeadline: connDeadline, | |||
period: period, | |||
} | |||
} | |||
// newTimeoutConn returns an instance of newTCPTimeoutConn. | |||
func newTimeoutConn( | |||
conn net.Conn, | |||
connDeadline time.Duration) *timeoutConn { | |||
return &timeoutConn{ | |||
conn, | |||
connDeadline, | |||
} | |||
} | |||
// Accept implements net.Listener. | |||
func (ln tcpTimeoutListener) Accept() (net.Conn, error) { | |||
err := ln.SetDeadline(time.Now().Add(ln.acceptDeadline)) | |||
if err != nil { | |||
return nil, err | |||
} | |||
tc, err := ln.AcceptTCP() | |||
if err != nil { | |||
return nil, err | |||
} | |||
// Wrap the conn in our timeout wrapper | |||
conn := newTimeoutConn(tc, ln.connDeadline) | |||
return conn, nil | |||
} | |||
// Read implements net.Listener. | |||
func (c timeoutConn) Read(b []byte) (n int, err error) { | |||
// Reset deadline | |||
c.Conn.SetReadDeadline(time.Now().Add(c.connDeadline)) | |||
return c.Conn.Read(b) | |||
} | |||
// Write implements net.Listener. | |||
func (c timeoutConn) Write(b []byte) (n int, err error) { | |||
// Reset deadline | |||
c.Conn.SetWriteDeadline(time.Now().Add(c.connDeadline)) | |||
return c.Conn.Write(b) | |||
} |
@ -1,65 +0,0 @@ | |||
package privval | |||
import ( | |||
"net" | |||
"testing" | |||
"time" | |||
) | |||
func TestTCPTimeoutListenerAcceptDeadline(t *testing.T) { | |||
ln, err := net.Listen("tcp", "127.0.0.1:0") | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
ln = newTCPTimeoutListener(ln, time.Millisecond, time.Second, time.Second) | |||
_, err = ln.Accept() | |||
opErr, ok := err.(*net.OpError) | |||
if !ok { | |||
t.Fatalf("have %v, want *net.OpError", err) | |||
} | |||
if have, want := opErr.Op, "accept"; have != want { | |||
t.Errorf("have %v, want %v", have, want) | |||
} | |||
} | |||
func TestTCPTimeoutListenerConnDeadline(t *testing.T) { | |||
ln, err := net.Listen("tcp", "127.0.0.1:0") | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
ln = newTCPTimeoutListener(ln, time.Second, time.Millisecond, time.Second) | |||
donec := make(chan struct{}) | |||
go func(ln net.Listener) { | |||
defer close(donec) | |||
c, err := ln.Accept() | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
time.Sleep(2 * time.Millisecond) | |||
msg := make([]byte, 200) | |||
_, err = c.Read(msg) | |||
opErr, ok := err.(*net.OpError) | |||
if !ok { | |||
t.Fatalf("have %v, want *net.OpError", err) | |||
} | |||
if have, want := opErr.Op, "read"; have != want { | |||
t.Errorf("have %v, want %v", have, want) | |||
} | |||
}(ln) | |||
_, err = net.Dial("tcp", ln.Addr().String()) | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
<-donec | |||
} |
@ -1,407 +0,0 @@ | |||
package privval | |||
import ( | |||
"fmt" | |||
"net" | |||
"testing" | |||
"time" | |||
"github.com/stretchr/testify/assert" | |||
"github.com/stretchr/testify/require" | |||
"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" | |||
) | |||
func TestSocketPVAddress(t *testing.T) { | |||
var ( | |||
chainID = cmn.RandStr(12) | |||
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV()) | |||
) | |||
defer sc.Stop() | |||
defer rs.Stop() | |||
serverAddr := rs.privVal.GetAddress() | |||
clientAddr := sc.GetAddress() | |||
assert.Equal(t, serverAddr, clientAddr) | |||
// TODO(xla): Remove when PrivValidator2 replaced PrivValidator. | |||
assert.Equal(t, serverAddr, sc.GetAddress()) | |||
} | |||
func TestSocketPVPubKey(t *testing.T) { | |||
var ( | |||
chainID = cmn.RandStr(12) | |||
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV()) | |||
) | |||
defer sc.Stop() | |||
defer rs.Stop() | |||
clientKey, err := sc.getPubKey() | |||
require.NoError(t, err) | |||
privKey := rs.privVal.GetPubKey() | |||
assert.Equal(t, privKey, clientKey) | |||
// TODO(xla): Remove when PrivValidator2 replaced PrivValidator. | |||
assert.Equal(t, privKey, sc.GetPubKey()) | |||
} | |||
func TestSocketPVProposal(t *testing.T) { | |||
var ( | |||
chainID = cmn.RandStr(12) | |||
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV()) | |||
ts = time.Now() | |||
privProposal = &types.Proposal{Timestamp: ts} | |||
clientProposal = &types.Proposal{Timestamp: ts} | |||
) | |||
defer sc.Stop() | |||
defer rs.Stop() | |||
require.NoError(t, rs.privVal.SignProposal(chainID, privProposal)) | |||
require.NoError(t, sc.SignProposal(chainID, clientProposal)) | |||
assert.Equal(t, privProposal.Signature, clientProposal.Signature) | |||
} | |||
func TestSocketPVVote(t *testing.T) { | |||
var ( | |||
chainID = cmn.RandStr(12) | |||
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV()) | |||
ts = time.Now() | |||
vType = types.PrecommitType | |||
want = &types.Vote{Timestamp: ts, Type: vType} | |||
have = &types.Vote{Timestamp: ts, Type: vType} | |||
) | |||
defer sc.Stop() | |||
defer rs.Stop() | |||
require.NoError(t, rs.privVal.SignVote(chainID, want)) | |||
require.NoError(t, sc.SignVote(chainID, have)) | |||
assert.Equal(t, want.Signature, have.Signature) | |||
} | |||
func TestSocketPVVoteResetDeadline(t *testing.T) { | |||
var ( | |||
chainID = cmn.RandStr(12) | |||
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV()) | |||
ts = time.Now() | |||
vType = types.PrecommitType | |||
want = &types.Vote{Timestamp: ts, Type: vType} | |||
have = &types.Vote{Timestamp: ts, Type: vType} | |||
) | |||
defer sc.Stop() | |||
defer rs.Stop() | |||
time.Sleep(3 * time.Millisecond) | |||
require.NoError(t, rs.privVal.SignVote(chainID, want)) | |||
require.NoError(t, sc.SignVote(chainID, have)) | |||
assert.Equal(t, want.Signature, have.Signature) | |||
// This would exceed the deadline if it was not extended by the previous message | |||
time.Sleep(3 * time.Millisecond) | |||
require.NoError(t, rs.privVal.SignVote(chainID, want)) | |||
require.NoError(t, sc.SignVote(chainID, have)) | |||
assert.Equal(t, want.Signature, have.Signature) | |||
} | |||
func TestSocketPVVoteKeepalive(t *testing.T) { | |||
var ( | |||
chainID = cmn.RandStr(12) | |||
sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV()) | |||
ts = time.Now() | |||
vType = types.PrecommitType | |||
want = &types.Vote{Timestamp: ts, Type: vType} | |||
have = &types.Vote{Timestamp: ts, Type: vType} | |||
) | |||
defer sc.Stop() | |||
defer rs.Stop() | |||
time.Sleep(10 * time.Millisecond) | |||
require.NoError(t, rs.privVal.SignVote(chainID, want)) | |||
require.NoError(t, sc.SignVote(chainID, have)) | |||
assert.Equal(t, want.Signature, have.Signature) | |||
} | |||
func TestSocketPVDeadline(t *testing.T) { | |||
var ( | |||
addr = testFreeAddr(t) | |||
listenc = make(chan struct{}) | |||
sc = NewTCPVal( | |||
log.TestingLogger(), | |||
addr, | |||
ed25519.GenPrivKey(), | |||
) | |||
) | |||
TCPValConnTimeout(100 * time.Millisecond)(sc) | |||
go func(sc *TCPVal) { | |||
defer close(listenc) | |||
require.NoError(t, sc.Start()) | |||
assert.True(t, sc.IsRunning()) | |||
}(sc) | |||
for { | |||
conn, err := cmn.Connect(addr) | |||
if err != nil { | |||
continue | |||
} | |||
_, err = p2pconn.MakeSecretConnection( | |||
conn, | |||
ed25519.GenPrivKey(), | |||
) | |||
if err == nil { | |||
break | |||
} | |||
} | |||
<-listenc | |||
_, err := sc.getPubKey() | |||
assert.Equal(t, err.(cmn.Error).Data(), ErrConnTimeout) | |||
} | |||
func TestRemoteSignerRetry(t *testing.T) { | |||
var ( | |||
attemptc = make(chan int) | |||
retries = 2 | |||
) | |||
ln, err := net.Listen("tcp", "127.0.0.1:0") | |||
require.NoError(t, err) | |||
go func(ln net.Listener, attemptc chan<- int) { | |||
attempts := 0 | |||
for { | |||
conn, err := ln.Accept() | |||
require.NoError(t, err) | |||
err = conn.Close() | |||
require.NoError(t, err) | |||
attempts++ | |||
if attempts == retries { | |||
attemptc <- attempts | |||
break | |||
} | |||
} | |||
}(ln, attemptc) | |||
rs := NewRemoteSigner( | |||
log.TestingLogger(), | |||
cmn.RandStr(12), | |||
ln.Addr().String(), | |||
types.NewMockPV(), | |||
ed25519.GenPrivKey(), | |||
) | |||
defer rs.Stop() | |||
RemoteSignerConnDeadline(time.Millisecond)(rs) | |||
RemoteSignerConnRetries(retries)(rs) | |||
assert.Equal(t, rs.Start(), ErrDialRetryMax) | |||
select { | |||
case attempts := <-attemptc: | |||
assert.Equal(t, retries, attempts) | |||
case <-time.After(100 * time.Millisecond): | |||
t.Error("expected remote to observe connection attempts") | |||
} | |||
} | |||
func TestRemoteSignVoteErrors(t *testing.T) { | |||
var ( | |||
chainID = cmn.RandStr(12) | |||
sc, rs = testSetupSocketPair(t, chainID, types.NewErroringMockPV()) | |||
ts = time.Now() | |||
vType = types.PrecommitType | |||
vote = &types.Vote{Timestamp: ts, Type: vType} | |||
) | |||
defer sc.Stop() | |||
defer rs.Stop() | |||
err := writeMsg(sc.conn, &SignVoteRequest{Vote: vote}) | |||
require.NoError(t, err) | |||
res, err := readMsg(sc.conn) | |||
require.NoError(t, err) | |||
resp := *res.(*SignedVoteResponse) | |||
require.NotNil(t, resp.Error) | |||
require.Equal(t, resp.Error.Description, types.ErroringMockPVErr.Error()) | |||
err = rs.privVal.SignVote(chainID, vote) | |||
require.Error(t, err) | |||
err = sc.SignVote(chainID, vote) | |||
require.Error(t, err) | |||
} | |||
func TestRemoteSignProposalErrors(t *testing.T) { | |||
var ( | |||
chainID = cmn.RandStr(12) | |||
sc, rs = testSetupSocketPair(t, chainID, types.NewErroringMockPV()) | |||
ts = time.Now() | |||
proposal = &types.Proposal{Timestamp: ts} | |||
) | |||
defer sc.Stop() | |||
defer rs.Stop() | |||
err := writeMsg(sc.conn, &SignProposalRequest{Proposal: proposal}) | |||
require.NoError(t, err) | |||
res, err := readMsg(sc.conn) | |||
require.NoError(t, err) | |||
resp := *res.(*SignedProposalResponse) | |||
require.NotNil(t, resp.Error) | |||
require.Equal(t, resp.Error.Description, types.ErroringMockPVErr.Error()) | |||
err = rs.privVal.SignProposal(chainID, proposal) | |||
require.Error(t, err) | |||
err = sc.SignProposal(chainID, proposal) | |||
require.Error(t, err) | |||
} | |||
func TestErrUnexpectedResponse(t *testing.T) { | |||
var ( | |||
addr = testFreeAddr(t) | |||
logger = log.TestingLogger() | |||
chainID = cmn.RandStr(12) | |||
readyc = make(chan struct{}) | |||
errc = make(chan error, 1) | |||
rs = NewRemoteSigner( | |||
logger, | |||
chainID, | |||
addr, | |||
types.NewMockPV(), | |||
ed25519.GenPrivKey(), | |||
) | |||
sc = NewTCPVal( | |||
logger, | |||
addr, | |||
ed25519.GenPrivKey(), | |||
) | |||
) | |||
testStartSocketPV(t, readyc, sc) | |||
defer sc.Stop() | |||
RemoteSignerConnDeadline(time.Millisecond)(rs) | |||
RemoteSignerConnRetries(1e6)(rs) | |||
// we do not want to Start() the remote signer here and instead use the connection to | |||
// reply with intentionally wrong replies below: | |||
rsConn, err := rs.connect() | |||
defer rsConn.Close() | |||
require.NoError(t, err) | |||
require.NotNil(t, rsConn) | |||
<-readyc | |||
// Proposal: | |||
go func(errc chan error) { | |||
errc <- sc.SignProposal(chainID, &types.Proposal{}) | |||
}(errc) | |||
// read request and write wrong response: | |||
go testReadWriteResponse(t, &SignedVoteResponse{}, rsConn) | |||
err = <-errc | |||
require.Error(t, err) | |||
require.Equal(t, err, ErrUnexpectedResponse) | |||
// Vote: | |||
go func(errc chan error) { | |||
errc <- sc.SignVote(chainID, &types.Vote{}) | |||
}(errc) | |||
// read request and write wrong response: | |||
go testReadWriteResponse(t, &SignedProposalResponse{}, rsConn) | |||
err = <-errc | |||
require.Error(t, err) | |||
require.Equal(t, err, ErrUnexpectedResponse) | |||
} | |||
func testSetupSocketPair( | |||
t *testing.T, | |||
chainID string, | |||
privValidator types.PrivValidator, | |||
) (*TCPVal, *RemoteSigner) { | |||
var ( | |||
addr = testFreeAddr(t) | |||
logger = log.TestingLogger() | |||
privVal = privValidator | |||
readyc = make(chan struct{}) | |||
rs = NewRemoteSigner( | |||
logger, | |||
chainID, | |||
addr, | |||
privVal, | |||
ed25519.GenPrivKey(), | |||
) | |||
sc = NewTCPVal( | |||
logger, | |||
addr, | |||
ed25519.GenPrivKey(), | |||
) | |||
) | |||
TCPValConnTimeout(5 * time.Millisecond)(sc) | |||
TCPValHeartbeat(2 * time.Millisecond)(sc) | |||
RemoteSignerConnDeadline(5 * time.Millisecond)(rs) | |||
RemoteSignerConnRetries(1e6)(rs) | |||
testStartSocketPV(t, readyc, sc) | |||
require.NoError(t, rs.Start()) | |||
assert.True(t, rs.IsRunning()) | |||
<-readyc | |||
return sc, rs | |||
} | |||
func testReadWriteResponse(t *testing.T, resp RemoteSignerMsg, rsConn net.Conn) { | |||
_, err := readMsg(rsConn) | |||
require.NoError(t, err) | |||
err = writeMsg(rsConn, resp) | |||
require.NoError(t, err) | |||
} | |||
func testStartSocketPV(t *testing.T, readyc chan struct{}, sc *TCPVal) { | |||
go func(sc *TCPVal) { | |||
require.NoError(t, sc.Start()) | |||
assert.True(t, sc.IsRunning()) | |||
readyc <- struct{}{} | |||
}(sc) | |||
} | |||
// testFreeAddr claims a free port so we don't block on listener being ready. | |||
func testFreeAddr(t *testing.T) string { | |||
ln, err := net.Listen("tcp", "127.0.0.1:0") | |||
require.NoError(t, err) | |||
defer ln.Close() | |||
return fmt.Sprintf("127.0.0.1:%d", ln.Addr().(*net.TCPAddr).Port) | |||
} |
@ -0,0 +1,41 @@ | |||
package main | |||
import ( | |||
"fmt" | |||
"os" | |||
"github.com/tendermint/tendermint/libs/log" | |||
"github.com/tendermint/tendermint/privval" | |||
) | |||
var ( | |||
logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) | |||
) | |||
func main() { | |||
args := os.Args[1:] | |||
if len(args) != 3 { | |||
fmt.Println("Expected three args: <old path> <new key path> <new state path>") | |||
fmt.Println("Eg. ~/.tendermint/config/priv_validator.json ~/.tendermint/config/priv_validator_key.json ~/.tendermint/data/priv_validator_state.json") | |||
os.Exit(1) | |||
} | |||
err := loadAndUpgrade(args[0], args[1], args[2]) | |||
if err != nil { | |||
fmt.Println(err) | |||
os.Exit(1) | |||
} | |||
} | |||
func loadAndUpgrade(oldPVPath, newPVKeyPath, newPVStatePath string) error { | |||
oldPV, err := privval.LoadOldFilePV(oldPVPath) | |||
if err != nil { | |||
return fmt.Errorf("Error reading OldPrivValidator from %v: %v\n", oldPVPath, err) | |||
} | |||
logger.Info("Upgrading PrivValidator file", | |||
"old", oldPVPath, | |||
"newKey", newPVKeyPath, | |||
"newState", newPVStatePath, | |||
) | |||
oldPV.Upgrade(newPVKeyPath, newPVStatePath) | |||
return nil | |||
} |
@ -0,0 +1,121 @@ | |||
package main | |||
import ( | |||
"fmt" | |||
"io/ioutil" | |||
"os" | |||
"testing" | |||
"github.com/stretchr/testify/assert" | |||
"github.com/stretchr/testify/require" | |||
"github.com/tendermint/tendermint/privval" | |||
) | |||
const oldPrivvalContent = `{ | |||
"address": "1D8089FAFDFAE4A637F3D616E17B92905FA2D91D", | |||
"pub_key": { | |||
"type": "tendermint/PubKeyEd25519", | |||
"value": "r3Yg2AhDZ745CNTpavsGU+mRZ8WpRXqoJuyqjN8mJq0=" | |||
}, | |||
"last_height": "5", | |||
"last_round": "0", | |||
"last_step": 3, | |||
"last_signature": "CTr7b9ZQlrJJf+12rPl5t/YSCUc/KqV7jQogCfFJA24e7hof69X6OMT7eFLVQHyodPjD/QTA298XHV5ejxInDQ==", | |||
"last_signbytes": "750802110500000000000000220B08B398F3E00510F48DA6402A480A20FC258973076512999C3E6839A22E9FBDB1B77CF993E8A9955412A41A59D4CAD312240A20C971B286ACB8AAA6FCA0365EB0A660B189EDC08B46B5AF2995DEFA51A28D215B10013211746573742D636861696E2D533245415533", | |||
"priv_key": { | |||
"type": "tendermint/PrivKeyEd25519", | |||
"value": "7MwvTGEWWjsYwjn2IpRb+GYsWi9nnFsw8jPLLY1UtP6vdiDYCENnvjkI1Olq+wZT6ZFnxalFeqgm7KqM3yYmrQ==" | |||
} | |||
}` | |||
func TestLoadAndUpgrade(t *testing.T) { | |||
oldFilePath := initTmpOldFile(t) | |||
defer os.Remove(oldFilePath) | |||
newStateFile, err := ioutil.TempFile("", "priv_validator_state*.json") | |||
defer os.Remove(newStateFile.Name()) | |||
require.NoError(t, err) | |||
newKeyFile, err := ioutil.TempFile("", "priv_validator_key*.json") | |||
defer os.Remove(newKeyFile.Name()) | |||
require.NoError(t, err) | |||
emptyOldFile, err := ioutil.TempFile("", "priv_validator_empty*.json") | |||
require.NoError(t, err) | |||
defer os.Remove(emptyOldFile.Name()) | |||
type args struct { | |||
oldPVPath string | |||
newPVKeyPath string | |||
newPVStatePath string | |||
} | |||
tests := []struct { | |||
name string | |||
args args | |||
wantErr bool | |||
wantPanic bool | |||
}{ | |||
{"successful upgrade", | |||
args{oldPVPath: oldFilePath, newPVKeyPath: newKeyFile.Name(), newPVStatePath: newStateFile.Name()}, | |||
false, false, | |||
}, | |||
{"unsuccessful upgrade: empty old privval file", | |||
args{oldPVPath: emptyOldFile.Name(), newPVKeyPath: newKeyFile.Name(), newPVStatePath: newStateFile.Name()}, | |||
true, false, | |||
}, | |||
{"unsuccessful upgrade: invalid new paths (1/3)", | |||
args{oldPVPath: oldFilePath, newPVKeyPath: "", newPVStatePath: newStateFile.Name()}, | |||
false, true, | |||
}, | |||
{"unsuccessful upgrade: invalid new paths (2/3)", | |||
args{oldPVPath: oldFilePath, newPVKeyPath: newKeyFile.Name(), newPVStatePath: ""}, | |||
false, true, | |||
}, | |||
{"unsuccessful upgrade: invalid new paths (3/3)", | |||
args{oldPVPath: oldFilePath, newPVKeyPath: "", newPVStatePath: ""}, | |||
false, true, | |||
}, | |||
} | |||
for _, tt := range tests { | |||
t.Run(tt.name, func(t *testing.T) { | |||
// need to re-write the file everytime because upgrading renames it | |||
err := ioutil.WriteFile(oldFilePath, []byte(oldPrivvalContent), 0600) | |||
require.NoError(t, err) | |||
if tt.wantPanic { | |||
require.Panics(t, func() { loadAndUpgrade(tt.args.oldPVPath, tt.args.newPVKeyPath, tt.args.newPVStatePath) }) | |||
} else { | |||
err = loadAndUpgrade(tt.args.oldPVPath, tt.args.newPVKeyPath, tt.args.newPVStatePath) | |||
if tt.wantErr { | |||
assert.Error(t, err) | |||
fmt.Println("ERR", err) | |||
} else { | |||
assert.NoError(t, err) | |||
upgradedPV := privval.LoadFilePV(tt.args.newPVKeyPath, tt.args.newPVStatePath) | |||
oldPV, err := privval.LoadOldFilePV(tt.args.oldPVPath + ".bak") | |||
require.NoError(t, err) | |||
assert.Equal(t, oldPV.Address, upgradedPV.Key.Address) | |||
assert.Equal(t, oldPV.Address, upgradedPV.GetAddress()) | |||
assert.Equal(t, oldPV.PubKey, upgradedPV.Key.PubKey) | |||
assert.Equal(t, oldPV.PubKey, upgradedPV.GetPubKey()) | |||
assert.Equal(t, oldPV.PrivKey, upgradedPV.Key.PrivKey) | |||
assert.Equal(t, oldPV.LastHeight, upgradedPV.LastSignState.Height) | |||
assert.Equal(t, oldPV.LastRound, upgradedPV.LastSignState.Round) | |||
assert.Equal(t, oldPV.LastSignature, upgradedPV.LastSignState.Signature) | |||
assert.Equal(t, oldPV.LastSignBytes, upgradedPV.LastSignState.SignBytes) | |||
assert.Equal(t, oldPV.LastStep, upgradedPV.LastSignState.Step) | |||
} | |||
} | |||
}) | |||
} | |||
} | |||
func initTmpOldFile(t *testing.T) string { | |||
tmpfile, err := ioutil.TempFile("", "priv_validator_*.json") | |||
require.NoError(t, err) | |||
t.Logf("created test file %s", tmpfile.Name()) | |||
_, err = tmpfile.WriteString(oldPrivvalContent) | |||
require.NoError(t, err) | |||
return tmpfile.Name() | |||
} |
@ -1,182 +0,0 @@ | |||
package main | |||
import ( | |||
"encoding/json" | |||
"fmt" | |||
"io/ioutil" | |||
"os" | |||
"path/filepath" | |||
"time" | |||
"github.com/tendermint/go-amino" | |||
"github.com/tendermint/tendermint/crypto/ed25519" | |||
cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" | |||
cmn "github.com/tendermint/tendermint/libs/common" | |||
"github.com/tendermint/tendermint/p2p" | |||
"github.com/tendermint/tendermint/privval" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
type GenesisValidator struct { | |||
PubKey Data `json:"pub_key"` | |||
Power int64 `json:"power"` | |||
Name string `json:"name"` | |||
} | |||
type Genesis struct { | |||
GenesisTime time.Time `json:"genesis_time"` | |||
ChainID string `json:"chain_id"` | |||
ConsensusParams *types.ConsensusParams `json:"consensus_params,omitempty"` | |||
Validators []GenesisValidator `json:"validators"` | |||
AppHash cmn.HexBytes `json:"app_hash"` | |||
AppState json.RawMessage `json:"app_state,omitempty"` | |||
AppOptions json.RawMessage `json:"app_options,omitempty"` // DEPRECATED | |||
} | |||
type NodeKey struct { | |||
PrivKey Data `json:"priv_key"` | |||
} | |||
type PrivVal struct { | |||
Address cmn.HexBytes `json:"address"` | |||
LastHeight int64 `json:"last_height"` | |||
LastRound int `json:"last_round"` | |||
LastStep int8 `json:"last_step"` | |||
PubKey Data `json:"pub_key"` | |||
PrivKey Data `json:"priv_key"` | |||
} | |||
type Data struct { | |||
Type string `json:"type"` | |||
Data cmn.HexBytes `json:"data"` | |||
} | |||
func convertNodeKey(cdc *amino.Codec, jsonBytes []byte) ([]byte, error) { | |||
var nodeKey NodeKey | |||
err := json.Unmarshal(jsonBytes, &nodeKey) | |||
if err != nil { | |||
return nil, err | |||
} | |||
var privKey ed25519.PrivKeyEd25519 | |||
copy(privKey[:], nodeKey.PrivKey.Data) | |||
nodeKeyNew := p2p.NodeKey{privKey} | |||
bz, err := cdc.MarshalJSON(nodeKeyNew) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return bz, nil | |||
} | |||
func convertPrivVal(cdc *amino.Codec, jsonBytes []byte) ([]byte, error) { | |||
var privVal PrivVal | |||
err := json.Unmarshal(jsonBytes, &privVal) | |||
if err != nil { | |||
return nil, err | |||
} | |||
var privKey ed25519.PrivKeyEd25519 | |||
copy(privKey[:], privVal.PrivKey.Data) | |||
var pubKey ed25519.PubKeyEd25519 | |||
copy(pubKey[:], privVal.PubKey.Data) | |||
privValNew := privval.FilePV{ | |||
Address: pubKey.Address(), | |||
PubKey: pubKey, | |||
LastHeight: privVal.LastHeight, | |||
LastRound: privVal.LastRound, | |||
LastStep: privVal.LastStep, | |||
PrivKey: privKey, | |||
} | |||
bz, err := cdc.MarshalJSON(privValNew) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return bz, nil | |||
} | |||
func convertGenesis(cdc *amino.Codec, jsonBytes []byte) ([]byte, error) { | |||
var genesis Genesis | |||
err := json.Unmarshal(jsonBytes, &genesis) | |||
if err != nil { | |||
return nil, err | |||
} | |||
genesisNew := types.GenesisDoc{ | |||
GenesisTime: genesis.GenesisTime, | |||
ChainID: genesis.ChainID, | |||
ConsensusParams: genesis.ConsensusParams, | |||
// Validators | |||
AppHash: genesis.AppHash, | |||
AppState: genesis.AppState, | |||
} | |||
if genesis.AppOptions != nil { | |||
genesisNew.AppState = genesis.AppOptions | |||
} | |||
for _, v := range genesis.Validators { | |||
var pubKey ed25519.PubKeyEd25519 | |||
copy(pubKey[:], v.PubKey.Data) | |||
genesisNew.Validators = append( | |||
genesisNew.Validators, | |||
types.GenesisValidator{ | |||
PubKey: pubKey, | |||
Power: v.Power, | |||
Name: v.Name, | |||
}, | |||
) | |||
} | |||
bz, err := cdc.MarshalJSON(genesisNew) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return bz, nil | |||
} | |||
func main() { | |||
cdc := amino.NewCodec() | |||
cryptoAmino.RegisterAmino(cdc) | |||
args := os.Args[1:] | |||
if len(args) != 1 { | |||
fmt.Println("Please specify a file to convert") | |||
os.Exit(1) | |||
} | |||
filePath := args[0] | |||
fileName := filepath.Base(filePath) | |||
fileBytes, err := ioutil.ReadFile(filePath) | |||
if err != nil { | |||
panic(err) | |||
} | |||
var bz []byte | |||
switch fileName { | |||
case "node_key.json": | |||
bz, err = convertNodeKey(cdc, fileBytes) | |||
case "priv_validator.json": | |||
bz, err = convertPrivVal(cdc, fileBytes) | |||
case "genesis.json": | |||
bz, err = convertGenesis(cdc, fileBytes) | |||
default: | |||
fmt.Println("Expected file name to be in (node_key.json, priv_validator.json, genesis.json)") | |||
os.Exit(1) | |||
} | |||
if err != nil { | |||
panic(err) | |||
} | |||
fmt.Println(string(bz)) | |||
} |