mirror of
https://github.com/tendermint/tendermint.git
synced 2026-06-10 00:03:04 +00:00
types/priv_validator
This commit is contained in:
180
types/priv_validator/json.go
Normal file
180
types/priv_validator/json.go
Normal file
@@ -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
|
||||
}
|
||||
259
types/priv_validator/priv_validator_test.go
Normal file
259
types/priv_validator/priv_validator_test.go
Normal file
@@ -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()
|
||||
}
|
||||
238
types/priv_validator/sign_info.go
Normal file
238
types/priv_validator/sign_info.go
Normal file
@@ -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)
|
||||
}
|
||||
273
types/priv_validator/socket.go
Normal file
273
types/priv_validator/socket.go
Normal file
@@ -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
|
||||
}
|
||||
61
types/priv_validator/unencrypted.go
Normal file
61
types/priv_validator/unencrypted.go
Normal file
@@ -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
|
||||
}
|
||||
59
types/priv_validator/upgrade.go
Normal file
59
types/priv_validator/upgrade.go
Normal file
@@ -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
|
||||
}
|
||||
21
types/priv_validator/upgrade_pv/main.go
Normal file
21
types/priv_validator/upgrade_pv/main.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user