@ -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) | |||||
} | |||||
} |