@ -0,0 +1,180 @@ | |||
package types | |||
import ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
"io/ioutil" | |||
"os" | |||
crypto "github.com/tendermint/go-crypto" | |||
"github.com/tendermint/tendermint/types" | |||
cmn "github.com/tendermint/tmlibs/common" | |||
) | |||
// PrivValidator aliases types.PrivValidator | |||
type PrivValidator = types.PrivValidator | |||
//----------------------------------------------------- | |||
// PrivKey implements Signer | |||
type PrivKey crypto.PrivKey | |||
// Sign - Implements Signer | |||
func (pk PrivKey) Sign(msg []byte) (crypto.Signature, error) { | |||
return crypto.PrivKey(pk).Sign(msg), nil | |||
} | |||
// MarshalJSON | |||
func (pk PrivKey) MarshalJSON() ([]byte, error) { | |||
return crypto.PrivKey(pk).MarshalJSON() | |||
} | |||
// UnmarshalJSON | |||
func (pk *PrivKey) UnmarshalJSON(b []byte) error { | |||
cpk := new(crypto.PrivKey) | |||
if err := cpk.UnmarshalJSON(b); err != nil { | |||
return err | |||
} | |||
*pk = (PrivKey)(*cpk) | |||
return nil | |||
} | |||
//----------------------------------------------------- | |||
var _ types.PrivValidator = (*PrivValidatorJSON)(nil) | |||
// PrivValidatorJSON wraps PrivValidatorUnencrypted | |||
// and persists it to disk after every SignVote and SignProposal. | |||
type PrivValidatorJSON struct { | |||
*PrivValidatorUnencrypted | |||
filePath string | |||
} | |||
// SignVote implements PrivValidator. It persists to disk. | |||
func (pvj *PrivValidatorJSON) SignVote(chainID string, vote *types.Vote) error { | |||
err := pvj.PrivValidatorUnencrypted.SignVote(chainID, vote) | |||
if err != nil { | |||
return err | |||
} | |||
pvj.Save() | |||
return nil | |||
} | |||
// SignProposal implements PrivValidator. It persists to disk. | |||
func (pvj *PrivValidatorJSON) SignProposal(chainID string, proposal *types.Proposal) error { | |||
err := pvj.PrivValidatorUnencrypted.SignProposal(chainID, proposal) | |||
if err != nil { | |||
return err | |||
} | |||
pvj.Save() | |||
return nil | |||
} | |||
//------------------------------------------------------- | |||
// String returns a string representation of the PrivValidatorJSON. | |||
func (pvj *PrivValidatorJSON) String() string { | |||
return fmt.Sprintf("PrivValidator{%v %v}", pvj.Address(), pvj.PrivValidatorUnencrypted.String()) | |||
} | |||
func (pvj *PrivValidatorJSON) Save() { | |||
pvj.save() | |||
} | |||
func (pvj *PrivValidatorJSON) save() { | |||
if pvj.filePath == "" { | |||
cmn.PanicSanity("Cannot save PrivValidator: filePath not set") | |||
} | |||
jsonBytes, err := json.Marshal(pvj) | |||
if err != nil { | |||
// ; BOOM!!! | |||
cmn.PanicCrisis(err) | |||
} | |||
err = cmn.WriteFileAtomic(pvj.filePath, jsonBytes, 0600) | |||
if err != nil { | |||
// ; BOOM!!! | |||
cmn.PanicCrisis(err) | |||
} | |||
} | |||
// Reset resets the PrivValidatorUnencrypted. Panics if the Signer is the wrong type. | |||
// NOTE: Unsafe! | |||
func (pvj *PrivValidatorJSON) Reset() { | |||
pvj.PrivValidatorUnencrypted.LastSignedInfo.Reset() | |||
pvj.Save() | |||
} | |||
//---------------------------------------------------------------- | |||
// GenPrivValidatorJSON generates a new validator with randomly generated private key | |||
// and the given filePath. It does not persist to file. | |||
func GenPrivValidatorJSON(filePath string) *PrivValidatorJSON { | |||
privKey := crypto.GenPrivKeyEd25519().Wrap() | |||
return &PrivValidatorJSON{ | |||
PrivValidatorUnencrypted: NewPrivValidatorUnencrypted(privKey), | |||
filePath: filePath, | |||
} | |||
} | |||
// LoadPrivValidatorJSON loads a PrivValidatorJSON from the filePath. | |||
func LoadPrivValidatorJSON(filePath string) *PrivValidatorJSON { | |||
pvJSONBytes, err := ioutil.ReadFile(filePath) | |||
if err != nil { | |||
cmn.Exit(err.Error()) | |||
} | |||
pvj := PrivValidatorJSON{} | |||
err = json.Unmarshal(pvJSONBytes, &pvj) | |||
if err != nil { | |||
cmn.Exit(cmn.Fmt("Error reading PrivValidatorJSON from %v: %v\n", filePath, err)) | |||
} | |||
// enable persistence | |||
pvj.filePath = filePath | |||
return &pvj | |||
} | |||
// LoadOrGenPrivValidatorJSON loads a PrivValidatorJSON from the given filePath | |||
// or else generates a new one and saves it to the filePath. | |||
func LoadOrGenPrivValidatorJSON(filePath string) *PrivValidatorJSON { | |||
var pvj *PrivValidatorJSON | |||
if _, err := os.Stat(filePath); err == nil { | |||
pvj = LoadPrivValidatorJSON(filePath) | |||
} else { | |||
pvj = GenPrivValidatorJSON(filePath) | |||
pvj.Save() | |||
} | |||
return pvj | |||
} | |||
//-------------------------------------------------------------- | |||
// NewTestPrivValidator returns a PrivValidatorJSON with a tempfile | |||
// for the file path. | |||
func NewTestPrivValidator(signer types.TestSigner) *PrivValidatorJSON { | |||
_, tempFilePath := cmn.Tempfile("priv_validator_") | |||
pv := &PrivValidatorJSON{ | |||
PrivValidatorUnencrypted: NewPrivValidatorUnencrypted(signer.(*types.DefaultTestSigner).PrivKey), | |||
filePath: tempFilePath, | |||
} | |||
return pv | |||
} | |||
//------------------------------------------------------ | |||
type PrivValidatorsByAddress []*PrivValidatorJSON | |||
func (pvs PrivValidatorsByAddress) Len() int { | |||
return len(pvs) | |||
} | |||
func (pvs PrivValidatorsByAddress) Less(i, j int) bool { | |||
return bytes.Compare(pvs[i].Address(), pvs[j].Address()) == -1 | |||
} | |||
func (pvs PrivValidatorsByAddress) Swap(i, j int) { | |||
it := pvs[i] | |||
pvs[i] = pvs[j] | |||
pvs[j] = it | |||
} |
@ -0,0 +1,259 @@ | |||
package types | |||
import ( | |||
"encoding/hex" | |||
"encoding/json" | |||
"fmt" | |||
"os" | |||
"testing" | |||
"time" | |||
"github.com/stretchr/testify/assert" | |||
"github.com/stretchr/testify/require" | |||
crypto "github.com/tendermint/go-crypto" | |||
data "github.com/tendermint/go-wire/data" | |||
"github.com/tendermint/tendermint/types" | |||
cmn "github.com/tendermint/tmlibs/common" | |||
) | |||
func TestGenLoadValidator(t *testing.T) { | |||
assert := assert.New(t) | |||
_, tempFilePath := cmn.Tempfile("priv_validator_") | |||
privVal := GenPrivValidatorJSON(tempFilePath) | |||
height := int64(100) | |||
privVal.LastSignedInfo.Height = height | |||
privVal.Save() | |||
addr := privVal.Address() | |||
privVal = LoadPrivValidatorJSON(tempFilePath) | |||
assert.Equal(addr, privVal.Address(), "expected privval addr to be the same") | |||
assert.Equal(height, privVal.LastSignedInfo.Height, "expected privval.LastHeight to have been saved") | |||
} | |||
func TestLoadOrGenValidator(t *testing.T) { | |||
assert := assert.New(t) | |||
_, tempFilePath := cmn.Tempfile("priv_validator_") | |||
if err := os.Remove(tempFilePath); err != nil { | |||
t.Error(err) | |||
} | |||
privVal := LoadOrGenPrivValidatorJSON(tempFilePath) | |||
addr := privVal.Address() | |||
privVal = LoadOrGenPrivValidatorJSON(tempFilePath) | |||
assert.Equal(addr, privVal.Address(), "expected privval addr to be the same") | |||
} | |||
func TestUnmarshalValidator(t *testing.T) { | |||
assert, require := assert.New(t), require.New(t) | |||
// create some fixed values | |||
addrStr := "D028C9981F7A87F3093672BF0D5B0E2A1B3ED456" | |||
pubStr := "3B3069C422E19688B45CBFAE7BB009FC0FA1B1EA86593519318B7214853803C8" | |||
privStr := "27F82582AEFAE7AB151CFB01C48BB6C1A0DA78F9BDDA979A9F70A84D074EB07D3B3069C422E19688B45CBFAE7BB009FC0FA1B1EA86593519318B7214853803C8" | |||
addrBytes, _ := hex.DecodeString(addrStr) | |||
pubBytes, _ := hex.DecodeString(pubStr) | |||
privBytes, _ := hex.DecodeString(privStr) | |||
// prepend type byte | |||
pubKey, err := crypto.PubKeyFromBytes(append([]byte{1}, pubBytes...)) | |||
require.Nil(err, "%+v", err) | |||
privKey, err := crypto.PrivKeyFromBytes(append([]byte{1}, privBytes...)) | |||
require.Nil(err, "%+v", err) | |||
serialized := fmt.Sprintf(`{ | |||
"id": { | |||
"address": "%s", | |||
"pub_key": { | |||
"type": "ed25519", | |||
"data": "%s" | |||
} | |||
}, | |||
"priv_key": { | |||
"type": "ed25519", | |||
"data": "%s" | |||
}, | |||
"last_signed_info": { | |||
"height": 0, | |||
"round": 0, | |||
"step": 0, | |||
"signature": null | |||
} | |||
}`, addrStr, pubStr, privStr) | |||
val := PrivValidatorJSON{} | |||
err = json.Unmarshal([]byte(serialized), &val) | |||
require.Nil(err, "%+v", err) | |||
// make sure the values match | |||
assert.EqualValues(addrBytes, val.Address()) | |||
assert.EqualValues(pubKey, val.PubKey()) | |||
assert.EqualValues(privKey, val.PrivKey) | |||
// export it and make sure it is the same | |||
out, err := json.Marshal(val) | |||
require.Nil(err, "%+v", err) | |||
assert.JSONEq(serialized, string(out)) | |||
} | |||
func TestSignVote(t *testing.T) { | |||
assert := assert.New(t) | |||
_, tempFilePath := cmn.Tempfile("priv_validator_") | |||
privVal := GenPrivValidatorJSON(tempFilePath) | |||
block1 := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}} | |||
block2 := types.BlockID{[]byte{3, 2, 1}, types.PartSetHeader{}} | |||
height, round := int64(10), 1 | |||
voteType := types.VoteTypePrevote | |||
// sign a vote for first time | |||
vote := newVote(privVal.Address(), 0, height, round, voteType, block1) | |||
err := privVal.SignVote("mychainid", vote) | |||
assert.NoError(err, "expected no error signing vote") | |||
// try to sign the same vote again; should be fine | |||
err = privVal.SignVote("mychainid", vote) | |||
assert.NoError(err, "expected no error on signing same vote") | |||
// now try some bad votes | |||
cases := []*types.Vote{ | |||
newVote(privVal.Address(), 0, height, round-1, voteType, block1), // round regression | |||
newVote(privVal.Address(), 0, height-1, round, voteType, block1), // height regression | |||
newVote(privVal.Address(), 0, height-2, round+4, voteType, block1), // height regression and different round | |||
newVote(privVal.Address(), 0, height, round, voteType, block2), // different block | |||
} | |||
for _, c := range cases { | |||
err = privVal.SignVote("mychainid", c) | |||
assert.Error(err, "expected error on signing conflicting vote") | |||
} | |||
// try signing a vote with a different time stamp | |||
sig := vote.Signature | |||
vote.Timestamp = vote.Timestamp.Add(time.Duration(1000)) | |||
err = privVal.SignVote("mychainid", vote) | |||
assert.NoError(err) | |||
assert.Equal(sig, vote.Signature) | |||
} | |||
func TestSignProposal(t *testing.T) { | |||
assert := assert.New(t) | |||
_, tempFilePath := cmn.Tempfile("priv_validator_") | |||
privVal := GenPrivValidatorJSON(tempFilePath) | |||
block1 := types.PartSetHeader{5, []byte{1, 2, 3}} | |||
block2 := types.PartSetHeader{10, []byte{3, 2, 1}} | |||
height, round := int64(10), 1 | |||
// sign a proposal for first time | |||
proposal := newProposal(height, round, block1) | |||
err := privVal.SignProposal("mychainid", proposal) | |||
assert.NoError(err, "expected no error signing proposal") | |||
// try to sign the same proposal again; should be fine | |||
err = privVal.SignProposal("mychainid", proposal) | |||
assert.NoError(err, "expected no error on signing same proposal") | |||
// now try some bad Proposals | |||
cases := []*types.Proposal{ | |||
newProposal(height, round-1, block1), // round regression | |||
newProposal(height-1, round, block1), // height regression | |||
newProposal(height-2, round+4, block1), // height regression and different round | |||
newProposal(height, round, block2), // different block | |||
} | |||
for _, c := range cases { | |||
err = privVal.SignProposal("mychainid", c) | |||
assert.Error(err, "expected error on signing conflicting proposal") | |||
} | |||
// try signing a proposal with a different time stamp | |||
sig := proposal.Signature | |||
proposal.Timestamp = proposal.Timestamp.Add(time.Duration(1000)) | |||
err = privVal.SignProposal("mychainid", proposal) | |||
assert.NoError(err) | |||
assert.Equal(sig, proposal.Signature) | |||
} | |||
func TestDifferByTimestamp(t *testing.T) { | |||
_, tempFilePath := cmn.Tempfile("priv_validator_") | |||
privVal := GenPrivValidatorJSON(tempFilePath) | |||
block1 := types.PartSetHeader{5, []byte{1, 2, 3}} | |||
height, round := int64(10), 1 | |||
chainID := "mychainid" | |||
// test proposal | |||
{ | |||
proposal := newProposal(height, round, block1) | |||
err := privVal.SignProposal(chainID, proposal) | |||
assert.NoError(t, err, "expected no error signing proposal") | |||
signBytes := types.SignBytes(chainID, proposal) | |||
sig := proposal.Signature | |||
timeStamp := clipToMS(proposal.Timestamp) | |||
// manipulate the timestamp. should get changed back | |||
proposal.Timestamp = proposal.Timestamp.Add(time.Millisecond) | |||
proposal.Signature = crypto.Signature{} | |||
err = privVal.SignProposal("mychainid", proposal) | |||
assert.NoError(t, err, "expected no error on signing same proposal") | |||
assert.Equal(t, timeStamp, proposal.Timestamp) | |||
assert.Equal(t, signBytes, types.SignBytes(chainID, proposal)) | |||
assert.Equal(t, sig, proposal.Signature) | |||
} | |||
// test vote | |||
{ | |||
voteType := types.VoteTypePrevote | |||
blockID := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}} | |||
vote := newVote(privVal.Address(), 0, height, round, voteType, blockID) | |||
err := privVal.SignVote("mychainid", vote) | |||
assert.NoError(t, err, "expected no error signing vote") | |||
signBytes := types.SignBytes(chainID, vote) | |||
sig := vote.Signature | |||
timeStamp := clipToMS(vote.Timestamp) | |||
// manipulate the timestamp. should get changed back | |||
vote.Timestamp = vote.Timestamp.Add(time.Millisecond) | |||
vote.Signature = crypto.Signature{} | |||
err = privVal.SignVote("mychainid", vote) | |||
assert.NoError(t, err, "expected no error on signing same vote") | |||
assert.Equal(t, timeStamp, vote.Timestamp) | |||
assert.Equal(t, signBytes, types.SignBytes(chainID, vote)) | |||
assert.Equal(t, sig, vote.Signature) | |||
} | |||
} | |||
func newVote(addr data.Bytes, idx int, height int64, round int, typ byte, blockID types.BlockID) *types.Vote { | |||
return &types.Vote{ | |||
ValidatorAddress: addr, | |||
ValidatorIndex: idx, | |||
Height: height, | |||
Round: round, | |||
Type: typ, | |||
Timestamp: time.Now().UTC(), | |||
BlockID: blockID, | |||
} | |||
} | |||
func newProposal(height int64, round int, partsHeader types.PartSetHeader) *types.Proposal { | |||
return &types.Proposal{ | |||
Height: height, | |||
Round: round, | |||
BlockPartsHeader: partsHeader, | |||
Timestamp: time.Now().UTC(), | |||
} | |||
} | |||
func clipToMS(t time.Time) time.Time { | |||
nano := t.UnixNano() | |||
million := int64(1000000) | |||
nano = (nano / million) * million | |||
return time.Unix(0, nano).UTC() | |||
} |
@ -0,0 +1,238 @@ | |||
package types | |||
import ( | |||
"bytes" | |||
"encoding/json" | |||
"errors" | |||
"fmt" | |||
"time" | |||
crypto "github.com/tendermint/go-crypto" | |||
data "github.com/tendermint/go-wire/data" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
// TODO: type ? | |||
const ( | |||
stepNone int8 = 0 // Used to distinguish the initial state | |||
stepPropose int8 = 1 | |||
stepPrevote int8 = 2 | |||
stepPrecommit int8 = 3 | |||
) | |||
func voteToStep(vote *types.Vote) int8 { | |||
switch vote.Type { | |||
case types.VoteTypePrevote: | |||
return stepPrevote | |||
case types.VoteTypePrecommit: | |||
return stepPrecommit | |||
default: | |||
panic("Unknown vote type") | |||
} | |||
} | |||
//------------------------------------- | |||
// LastSignedInfo contains information about the latest | |||
// data signed by a validator to help prevent double signing. | |||
type LastSignedInfo struct { | |||
Height int64 `json:"height"` | |||
Round int `json:"round"` | |||
Step int8 `json:"step"` | |||
Signature crypto.Signature `json:"signature,omitempty"` // so we dont lose signatures | |||
SignBytes data.Bytes `json:"signbytes,omitempty"` // so we dont lose signatures | |||
} | |||
func NewLastSignedInfo() *LastSignedInfo { | |||
return &LastSignedInfo{ | |||
Step: stepNone, | |||
} | |||
} | |||
func (info *LastSignedInfo) String() string { | |||
return fmt.Sprintf("LH:%v, LR:%v, LS:%v", info.Height, info.Round, info.Step) | |||
} | |||
// Verify returns an error if there is a height/round/step regression | |||
// or if the HRS matches but there are no LastSignBytes. | |||
// It returns true if HRS matches exactly and the LastSignature exists. | |||
// It panics if the HRS matches, the LastSignBytes are not empty, but the LastSignature is empty. | |||
func (info LastSignedInfo) Verify(height int64, round int, step int8) (bool, error) { | |||
if info.Height > height { | |||
return false, errors.New("Height regression") | |||
} | |||
if info.Height == height { | |||
if info.Round > round { | |||
return false, errors.New("Round regression") | |||
} | |||
if info.Round == round { | |||
if info.Step > step { | |||
return false, errors.New("Step regression") | |||
} else if info.Step == step { | |||
if info.SignBytes != nil { | |||
if info.Signature.Empty() { | |||
panic("info: LastSignature is nil but LastSignBytes is not!") | |||
} | |||
return true, nil | |||
} | |||
return false, errors.New("No LastSignature found") | |||
} | |||
} | |||
} | |||
return false, nil | |||
} | |||
// Set height/round/step and signature on the info | |||
func (info *LastSignedInfo) Set(height int64, round int, step int8, | |||
signBytes []byte, sig crypto.Signature) { | |||
info.Height = height | |||
info.Round = round | |||
info.Step = step | |||
info.Signature = sig | |||
info.SignBytes = signBytes | |||
} | |||
// Reset resets all the values. | |||
// XXX: Unsafe. | |||
func (info *LastSignedInfo) Reset() { | |||
info.Height = 0 | |||
info.Round = 0 | |||
info.Step = 0 | |||
info.Signature = crypto.Signature{} | |||
info.SignBytes = nil | |||
} | |||
// SignVote checks the height/round/step (HRS) are greater than the latest state of the LastSignedInfo. | |||
// If so, it signs the vote, updates the LastSignedInfo, and sets the signature on the vote. | |||
// If the HRS are equal and the only thing changed is the timestamp, it sets the vote.Timestamp to the previous | |||
// value and the Signature to the LastSignedInfo.Signature. | |||
// Else it returns an error. | |||
func (lsi *LastSignedInfo) SignVote(signer types.Signer, chainID string, vote *types.Vote) error { | |||
height, round, step := vote.Height, vote.Round, voteToStep(vote) | |||
signBytes := types.SignBytes(chainID, vote) | |||
sameHRS, err := lsi.Verify(height, round, step) | |||
if err != nil { | |||
return err | |||
} | |||
// We might crash before writing to the wal, | |||
// causing us to try to re-sign for the same HRS. | |||
// If signbytes are the same, use the last signature. | |||
// If they only differ by timestamp, use last timestamp and signature | |||
// Otherwise, return error | |||
if sameHRS { | |||
if bytes.Equal(signBytes, lsi.SignBytes) { | |||
vote.Signature = lsi.Signature | |||
} else if timestamp, ok := checkVotesOnlyDifferByTimestamp(lsi.SignBytes, signBytes); ok { | |||
vote.Timestamp = timestamp | |||
vote.Signature = lsi.Signature | |||
} else { | |||
err = fmt.Errorf("Conflicting data") | |||
} | |||
return err | |||
} | |||
sig, err := signer.Sign(signBytes) | |||
if err != nil { | |||
return err | |||
} | |||
lsi.Set(height, round, step, signBytes, sig) | |||
vote.Signature = sig | |||
return nil | |||
} | |||
// SignProposal checks if the height/round/step (HRS) are greater than the latest state of the LastSignedInfo. | |||
// If so, it signs the proposal, updates the LastSignedInfo, and sets the signature on the proposal. | |||
// If the HRS are equal and the only thing changed is the timestamp, it sets the timestamp to the previous | |||
// value and the Signature to the LastSignedInfo.Signature. | |||
// Else it returns an error. | |||
func (lsi *LastSignedInfo) SignProposal(signer types.Signer, chainID string, proposal *types.Proposal) error { | |||
height, round, step := proposal.Height, proposal.Round, stepPropose | |||
signBytes := types.SignBytes(chainID, proposal) | |||
sameHRS, err := lsi.Verify(height, round, step) | |||
if err != nil { | |||
return err | |||
} | |||
// We might crash before writing to the wal, | |||
// causing us to try to re-sign for the same HRS. | |||
// If signbytes are the same, use the last signature. | |||
// If they only differ by timestamp, use last timestamp and signature | |||
// Otherwise, return error | |||
if sameHRS { | |||
if bytes.Equal(signBytes, lsi.SignBytes) { | |||
proposal.Signature = lsi.Signature | |||
} else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(lsi.SignBytes, signBytes); ok { | |||
proposal.Timestamp = timestamp | |||
proposal.Signature = lsi.Signature | |||
} else { | |||
err = fmt.Errorf("Conflicting data") | |||
} | |||
return err | |||
} | |||
sig, err := signer.Sign(signBytes) | |||
if err != nil { | |||
return err | |||
} | |||
lsi.Set(height, round, step, signBytes, sig) | |||
proposal.Signature = sig | |||
return nil | |||
} | |||
//------------------------------------- | |||
// returns the timestamp from the lastSignBytes. | |||
// returns true if the only difference in the votes is their timestamp. | |||
func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) { | |||
var lastVote, newVote types.CanonicalJSONOnceVote | |||
if err := json.Unmarshal(lastSignBytes, &lastVote); err != nil { | |||
panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into vote: %v", err)) | |||
} | |||
if err := json.Unmarshal(newSignBytes, &newVote); err != nil { | |||
panic(fmt.Sprintf("signBytes cannot be unmarshalled into vote: %v", err)) | |||
} | |||
lastTime, err := time.Parse(types.TimeFormat, lastVote.Vote.Timestamp) | |||
if err != nil { | |||
panic(err) | |||
} | |||
// set the times to the same value and check equality | |||
now := types.CanonicalTime(time.Now()) | |||
lastVote.Vote.Timestamp = now | |||
newVote.Vote.Timestamp = now | |||
lastVoteBytes, _ := json.Marshal(lastVote) | |||
newVoteBytes, _ := json.Marshal(newVote) | |||
return lastTime, bytes.Equal(newVoteBytes, lastVoteBytes) | |||
} | |||
// returns the timestamp from the lastSignBytes. | |||
// returns true if the only difference in the proposals is their timestamp | |||
func checkProposalsOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) { | |||
var lastProposal, newProposal types.CanonicalJSONOnceProposal | |||
if err := json.Unmarshal(lastSignBytes, &lastProposal); err != nil { | |||
panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into proposal: %v", err)) | |||
} | |||
if err := json.Unmarshal(newSignBytes, &newProposal); err != nil { | |||
panic(fmt.Sprintf("signBytes cannot be unmarshalled into proposal: %v", err)) | |||
} | |||
lastTime, err := time.Parse(types.TimeFormat, lastProposal.Proposal.Timestamp) | |||
if err != nil { | |||
panic(err) | |||
} | |||
// set the times to the same value and check equality | |||
now := types.CanonicalTime(time.Now()) | |||
lastProposal.Proposal.Timestamp = now | |||
newProposal.Proposal.Timestamp = now | |||
lastProposalBytes, _ := json.Marshal(lastProposal) | |||
newProposalBytes, _ := json.Marshal(newProposal) | |||
return lastTime, bytes.Equal(newProposalBytes, lastProposalBytes) | |||
} |
@ -0,0 +1,273 @@ | |||
package types | |||
import ( | |||
"bytes" | |||
"fmt" | |||
"net" | |||
"time" | |||
crypto "github.com/tendermint/go-crypto" | |||
wire "github.com/tendermint/go-wire" | |||
"github.com/tendermint/go-wire/data" | |||
cmn "github.com/tendermint/tmlibs/common" | |||
"github.com/tendermint/tmlibs/log" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
//----------------------------------------------------------------- | |||
var _ types.PrivValidator = (*PrivValidatorSocketClient)(nil) | |||
// PrivValidatorSocketClient implements PrivValidator. | |||
// It uses a socket to request signatures. | |||
type PrivValidatorSocketClient struct { | |||
cmn.BaseService | |||
conn net.Conn | |||
ID types.ValidatorID | |||
SocketAddress string | |||
} | |||
const ( | |||
dialRetryIntervalSeconds = 1 | |||
) | |||
// NewPrivValidatorSocket returns an instance of PrivValidatorSocket. | |||
func NewPrivValidatorSocketClient(logger log.Logger, socketAddr string) *PrivValidatorSocketClient { | |||
pvsc := &PrivValidatorSocketClient{ | |||
SocketAddress: socketAddr, | |||
} | |||
pvsc.BaseService = *cmn.NewBaseService(logger, "privValidatorSocketClient", pvsc) | |||
return pvsc | |||
} | |||
func (pvsc *PrivValidatorSocketClient) OnStart() error { | |||
if err := pvsc.BaseService.OnStart(); err != nil { | |||
return err | |||
} | |||
var err error | |||
var conn net.Conn | |||
RETRY_LOOP: | |||
for { | |||
conn, err = cmn.Connect(pvsc.SocketAddress) | |||
if err != nil { | |||
pvsc.Logger.Error(fmt.Sprintf("PrivValidatorSocket failed to connect to %v. Retrying...", pvsc.SocketAddress)) | |||
time.Sleep(time.Second * dialRetryIntervalSeconds) | |||
continue RETRY_LOOP | |||
} | |||
pvsc.conn = conn | |||
return nil | |||
} | |||
} | |||
func (pvsc *PrivValidatorSocketClient) OnStop() { | |||
pvsc.BaseService.OnStop() | |||
if pvsc.conn != nil { | |||
pvsc.conn.Close() | |||
} | |||
} | |||
func (pvsc *PrivValidatorSocketClient) Address() data.Bytes { | |||
pubKey := pvsc.PubKey() | |||
return pubKey.Address() | |||
} | |||
func (pvsc *PrivValidatorSocketClient) PubKey() crypto.PubKey { | |||
res, err := readWrite(pvsc.conn, PubKeyMsg{}) | |||
if err != nil { | |||
panic(err) | |||
} | |||
return res.(PubKeyMsg).PubKey | |||
} | |||
func (pvsc *PrivValidatorSocketClient) SignVote(chainID string, vote *types.Vote) error { | |||
res, err := readWrite(pvsc.conn, SignVoteMsg{Vote: vote}) | |||
if err != nil { | |||
return err | |||
} | |||
*vote = *res.(SignVoteMsg).Vote | |||
return nil | |||
} | |||
func (pvsc *PrivValidatorSocketClient) SignProposal(chainID string, proposal *types.Proposal) error { | |||
res, err := readWrite(pvsc.conn, SignProposalMsg{Proposal: proposal}) | |||
if err != nil { | |||
return err | |||
} | |||
*proposal = *res.(SignProposalMsg).Proposal | |||
return nil | |||
} | |||
func (pvsc *PrivValidatorSocketClient) SignHeartbeat(chainID string, heartbeat *types.Heartbeat) error { | |||
res, err := readWrite(pvsc.conn, SignHeartbeatMsg{Heartbeat: heartbeat}) | |||
if err != nil { | |||
return err | |||
} | |||
*heartbeat = *res.(SignHeartbeatMsg).Heartbeat | |||
return nil | |||
} | |||
//--------------------------------------------------------- | |||
// PrivValidatorSocketServer implements PrivValidator. | |||
// It responds to requests over a socket | |||
type PrivValidatorSocketServer struct { | |||
cmn.BaseService | |||
conn net.Conn | |||
proto, addr string | |||
listener net.Listener | |||
privVal PrivValidator | |||
chainID string | |||
} | |||
func NewPrivValidatorSocketServer(logger log.Logger, socketAddr, chainID string, privVal PrivValidator) *PrivValidatorSocketServer { | |||
proto, addr := cmn.ProtocolAndAddress(socketAddr) | |||
pvss := &PrivValidatorSocketServer{ | |||
proto: proto, | |||
addr: addr, | |||
privVal: privVal, | |||
chainID: chainID, | |||
} | |||
pvss.BaseService = *cmn.NewBaseService(logger, "privValidatorSocketServer", pvss) | |||
return pvss | |||
} | |||
func (pvss *PrivValidatorSocketServer) OnStart() error { | |||
if err := pvss.BaseService.OnStart(); err != nil { | |||
return err | |||
} | |||
ln, err := net.Listen(pvss.proto, pvss.addr) | |||
if err != nil { | |||
return err | |||
} | |||
pvss.listener = ln | |||
go pvss.acceptConnectionsRoutine() | |||
return nil | |||
} | |||
func (pvss *PrivValidatorSocketServer) OnStop() { | |||
pvss.BaseService.OnStop() | |||
if err := pvss.listener.Close(); err != nil { | |||
pvss.Logger.Error("Error closing listener", "err", err) | |||
} | |||
if err := pvss.conn.Close(); err != nil { | |||
pvss.Logger.Error("Error closing connection", "conn", pvss.conn, "err", err) | |||
} | |||
} | |||
func (pvss *PrivValidatorSocketServer) acceptConnectionsRoutine() { | |||
for { | |||
// Accept a connection | |||
pvss.Logger.Info("Waiting for new connection...") | |||
var err error | |||
pvss.conn, err = pvss.listener.Accept() | |||
if err != nil { | |||
if !pvss.IsRunning() { | |||
return // Ignore error from listener closing. | |||
} | |||
pvss.Logger.Error("Failed to accept connection: " + err.Error()) | |||
continue | |||
} | |||
pvss.Logger.Info("Accepted a new connection") | |||
// read/write | |||
for { | |||
if !pvss.IsRunning() { | |||
return // Ignore error from listener closing. | |||
} | |||
var n int | |||
var err error | |||
b := wire.ReadByteSlice(pvss.conn, 0, &n, &err) //XXX: no max | |||
req_, err := decodeMsg(b) | |||
if err != nil { | |||
panic(err) | |||
} | |||
var res PrivValidatorSocketMsg | |||
switch req := req_.(type) { | |||
case PubKeyMsg: | |||
res = PubKeyMsg{pvss.privVal.PubKey()} | |||
case SignVoteMsg: | |||
pvss.privVal.SignVote(pvss.chainID, req.Vote) | |||
res = SignVoteMsg{req.Vote} | |||
case SignProposalMsg: | |||
pvss.privVal.SignProposal(pvss.chainID, req.Proposal) | |||
res = SignProposalMsg{req.Proposal} | |||
case SignHeartbeatMsg: | |||
pvss.privVal.SignHeartbeat(pvss.chainID, req.Heartbeat) | |||
res = SignHeartbeatMsg{req.Heartbeat} | |||
default: | |||
panic(fmt.Sprintf("unknown msg: %v", req_)) | |||
} | |||
b = wire.BinaryBytes(res) | |||
_, err = pvss.conn.Write(b) | |||
if err != nil { | |||
panic(err) | |||
} | |||
} | |||
} | |||
} | |||
//--------------------------------------------------------- | |||
const ( | |||
msgTypePubKey = byte(0x01) | |||
msgTypeSignVote = byte(0x10) | |||
msgTypeSignProposal = byte(0x11) | |||
msgTypeSignHeartbeat = byte(0x12) | |||
) | |||
type PrivValidatorSocketMsg interface{} | |||
var _ = wire.RegisterInterface( | |||
struct{ PrivValidatorSocketMsg }{}, | |||
wire.ConcreteType{&PubKeyMsg{}, msgTypePubKey}, | |||
wire.ConcreteType{&SignVoteMsg{}, msgTypeSignVote}, | |||
wire.ConcreteType{&SignProposalMsg{}, msgTypeSignProposal}, | |||
wire.ConcreteType{&SignHeartbeatMsg{}, msgTypeSignHeartbeat}, | |||
) | |||
func readWrite(conn net.Conn, req PrivValidatorSocketMsg) (res PrivValidatorSocketMsg, err error) { | |||
b := wire.BinaryBytes(req) | |||
_, err = conn.Write(b) | |||
if err != nil { | |||
return nil, err | |||
} | |||
var n int | |||
b = wire.ReadByteSlice(conn, 0, &n, &err) //XXX: no max | |||
return decodeMsg(b) | |||
} | |||
func decodeMsg(bz []byte) (msg PrivValidatorSocketMsg, err error) { | |||
n := new(int) | |||
r := bytes.NewReader(bz) | |||
msgI := wire.ReadBinary(struct{ PrivValidatorSocketMsg }{}, r, 0, n, &err) | |||
msg = msgI.(struct{ PrivValidatorSocketMsg }).PrivValidatorSocketMsg | |||
return msg, err | |||
} | |||
type PubKeyMsg struct { | |||
PubKey crypto.PubKey | |||
} | |||
type SignVoteMsg struct { | |||
Vote *types.Vote | |||
} | |||
type SignProposalMsg struct { | |||
Proposal *types.Proposal | |||
} | |||
type SignHeartbeatMsg struct { | |||
Heartbeat *types.Heartbeat | |||
} |
@ -0,0 +1,61 @@ | |||
package types | |||
import ( | |||
"fmt" | |||
crypto "github.com/tendermint/go-crypto" | |||
data "github.com/tendermint/go-wire/data" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
//----------------------------------------------------------------- | |||
var _ types.PrivValidator = (*PrivValidatorUnencrypted)(nil) | |||
// PrivValidatorUnencrypted implements PrivValidator. | |||
// It uses an in-memory crypto.PrivKey that is | |||
// persisted to disk unencrypted. | |||
type PrivValidatorUnencrypted struct { | |||
ID types.ValidatorID `json:"id"` | |||
PrivKey PrivKey `json:"priv_key"` | |||
LastSignedInfo *LastSignedInfo `json:"last_signed_info"` | |||
} | |||
// NewPrivValidatorUnencrypted returns an instance of PrivValidatorUnencrypted. | |||
func NewPrivValidatorUnencrypted(priv crypto.PrivKey) *PrivValidatorUnencrypted { | |||
return &PrivValidatorUnencrypted{ | |||
ID: types.ValidatorID{ | |||
Address: priv.PubKey().Address(), | |||
PubKey: priv.PubKey(), | |||
}, | |||
PrivKey: PrivKey(priv), | |||
LastSignedInfo: NewLastSignedInfo(), | |||
} | |||
} | |||
// String returns a string representation of the PrivValidatorUnencrypted | |||
func (upv *PrivValidatorUnencrypted) String() string { | |||
return fmt.Sprintf("PrivValidator{%v %v}", upv.Address(), upv.LastSignedInfo.String()) | |||
} | |||
func (upv *PrivValidatorUnencrypted) Address() data.Bytes { | |||
return upv.PrivKey.PubKey().Address() | |||
} | |||
func (upv *PrivValidatorUnencrypted) PubKey() crypto.PubKey { | |||
return upv.PrivKey.PubKey() | |||
} | |||
func (upv *PrivValidatorUnencrypted) SignVote(chainID string, vote *types.Vote) error { | |||
return upv.LastSignedInfo.SignVote(upv.PrivKey, chainID, vote) | |||
} | |||
func (upv *PrivValidatorUnencrypted) SignProposal(chainID string, proposal *types.Proposal) error { | |||
return upv.LastSignedInfo.SignProposal(upv.PrivKey, chainID, proposal) | |||
} | |||
func (upv *PrivValidatorUnencrypted) SignHeartbeat(chainID string, heartbeat *types.Heartbeat) error { | |||
var err error | |||
heartbeat.Signature, err = upv.PrivKey.Sign(types.SignBytes(chainID, heartbeat)) | |||
return err | |||
} |
@ -0,0 +1,59 @@ | |||
package types | |||
import ( | |||
"encoding/json" | |||
"io/ioutil" | |||
crypto "github.com/tendermint/go-crypto" | |||
data "github.com/tendermint/go-wire/data" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
type PrivValidatorV1 struct { | |||
Address data.Bytes `json:"address"` | |||
PubKey crypto.PubKey `json:"pub_key"` | |||
LastHeight int64 `json:"last_height"` | |||
LastRound int `json:"last_round"` | |||
LastStep int8 `json:"last_step"` | |||
LastSignature crypto.Signature `json:"last_signature,omitempty"` // so we dont lose signatures | |||
LastSignBytes data.Bytes `json:"last_signbytes,omitempty"` // so we dont lose signatures | |||
PrivKey crypto.PrivKey `json:"priv_key"` | |||
} | |||
func UpgradePrivValidator(filePath string) (*PrivValidatorJSON, error) { | |||
b, err := ioutil.ReadFile(filePath) | |||
if err != nil { | |||
return nil, err | |||
} | |||
pv := new(PrivValidatorV1) | |||
err = json.Unmarshal(b, pv) | |||
if err != nil { | |||
return nil, err | |||
} | |||
pvNew := &PrivValidatorJSON{ | |||
PrivValidatorUnencrypted: &PrivValidatorUnencrypted{ | |||
ID: types.ValidatorID{ | |||
Address: pv.Address, | |||
PubKey: pv.PubKey, | |||
}, | |||
PrivKey: PrivKey(pv.PrivKey), | |||
LastSignedInfo: &LastSignedInfo{ | |||
Height: pv.LastHeight, | |||
Round: pv.LastRound, | |||
Step: pv.LastStep, | |||
SignBytes: pv.LastSignBytes, | |||
Signature: pv.LastSignature, | |||
}, | |||
}, | |||
} | |||
b, err = json.MarshalIndent(pvNew, "", " ") | |||
if err != nil { | |||
return nil, err | |||
} | |||
err = ioutil.WriteFile(filePath, b, 0600) | |||
return pvNew, err | |||
} |
@ -0,0 +1,21 @@ | |||
package main | |||
import ( | |||
"fmt" | |||
"os" | |||
priv_val "github.com/tendermint/tendermint/types/priv_validator" | |||
) | |||
func main() { | |||
if len(os.Args) < 2 { | |||
fmt.Println("USAGE: priv_val_converter <path to priv_validator.json>") | |||
os.Exit(1) | |||
} | |||
file := os.Args[1] | |||
_, err := priv_val.UpgradePrivValidator(file) | |||
if err != nil { | |||
fmt.Println(err) | |||
os.Exit(1) | |||
} | |||
} |