From 944ebccfe9063550923276f2b3fd0c9d3a8bd953 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 18 Sep 2017 22:05:33 -0400 Subject: [PATCH] more PrivValidator interface --- cmd/tendermint/commands/gen_validator.go | 2 +- cmd/tendermint/commands/init.go | 3 +- .../commands/reset_priv_validator.go | 6 +- cmd/tendermint/commands/run_node.go | 2 +- cmd/tendermint/commands/show_validator.go | 2 +- cmd/tendermint/commands/testnet.go | 5 +- consensus/common_test.go | 3 +- node/node.go | 4 +- types/priv_validator.go | 239 +++++++++--------- types/validator.go | 5 +- types/validator_set.go | 4 +- 11 files changed, 135 insertions(+), 140 deletions(-) diff --git a/cmd/tendermint/commands/gen_validator.go b/cmd/tendermint/commands/gen_validator.go index e4dfb0bd1..984176d2a 100644 --- a/cmd/tendermint/commands/gen_validator.go +++ b/cmd/tendermint/commands/gen_validator.go @@ -18,7 +18,7 @@ var GenValidatorCmd = &cobra.Command{ } func genValidator(cmd *cobra.Command, args []string) { - privValidator := types.GenPrivValidator() + privValidator := types.GenPrivValidatorFS("") privValidatorJSONBytes, _ := json.MarshalIndent(privValidator, "", "\t") fmt.Printf(`%v `, string(privValidatorJSONBytes)) diff --git a/cmd/tendermint/commands/init.go b/cmd/tendermint/commands/init.go index 259645044..d8e8bde40 100644 --- a/cmd/tendermint/commands/init.go +++ b/cmd/tendermint/commands/init.go @@ -19,8 +19,7 @@ var InitFilesCmd = &cobra.Command{ func initFiles(cmd *cobra.Command, args []string) { privValFile := config.PrivValidatorFile() if _, err := os.Stat(privValFile); os.IsNotExist(err) { - privValidator := types.GenPrivValidator() - privValidator.SetFile(privValFile) + privValidator := types.GenPrivValidatorFS(privValFile) privValidator.Save() genFile := config.GenesisFile() diff --git a/cmd/tendermint/commands/reset_priv_validator.go b/cmd/tendermint/commands/reset_priv_validator.go index bedd38926..6255f9613 100644 --- a/cmd/tendermint/commands/reset_priv_validator.go +++ b/cmd/tendermint/commands/reset_priv_validator.go @@ -46,14 +46,12 @@ func resetPrivValidator(cmd *cobra.Command, args []string) { func resetPrivValidatorLocal(privValFile string, logger log.Logger) { // Get PrivValidator - var privValidator types.PrivValidator if _, err := os.Stat(privValFile); err == nil { - privValidator = types.LoadPrivValidator(privValFile) + privValidator := types.LoadPrivValidatorFS(privValFile) privValidator.Reset() logger.Info("Reset PrivValidator", "file", privValFile) } else { - privValidator = types.GenPrivValidator() - privValidator.SetFile(privValFile) + privValidator := types.GenPrivValidatorFS(privValFile) privValidator.Save() logger.Info("Generated PrivValidator", "file", privValFile) } diff --git a/cmd/tendermint/commands/run_node.go b/cmd/tendermint/commands/run_node.go index 883ec6ec4..d4acd98df 100644 --- a/cmd/tendermint/commands/run_node.go +++ b/cmd/tendermint/commands/run_node.go @@ -44,7 +44,7 @@ func AddNodeFlags(cmd *cobra.Command) { type FuncSignerAndApp func(*cfg.Config) (types.PrivValidator, proxy.ClientCreator) func DefaultSignerAndApp(config *cfg.Config) (types.PrivValidator, proxy.ClientCreator) { - privValidator := types.LoadOrGenPrivValidator(config.PrivValidatorFile()) + privValidator := types.LoadOrGenPrivValidatorFS(config.PrivValidatorFile()) clientCreator := proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()) return privValidator, clientCreator } diff --git a/cmd/tendermint/commands/show_validator.go b/cmd/tendermint/commands/show_validator.go index 86a665f0c..458f11c2d 100644 --- a/cmd/tendermint/commands/show_validator.go +++ b/cmd/tendermint/commands/show_validator.go @@ -17,7 +17,7 @@ var ShowValidatorCmd = &cobra.Command{ } func showValidator(cmd *cobra.Command, args []string) { - privValidator := types.LoadOrGenPrivValidator(config.PrivValidatorFile()) + privValidator := types.LoadOrGenPrivValidatorFS(config.PrivValidatorFile()) pubKeyJSONBytes, _ := data.ToJSON(privValidator.PubKey) fmt.Println(string(pubKeyJSONBytes)) } diff --git a/cmd/tendermint/commands/testnet.go b/cmd/tendermint/commands/testnet.go index 9412379a5..7fc4f94fe 100644 --- a/cmd/tendermint/commands/testnet.go +++ b/cmd/tendermint/commands/testnet.go @@ -45,7 +45,7 @@ func testnetFiles(cmd *cobra.Command, args []string) { } // Read priv_validator.json to populate vals privValFile := path.Join(dataDir, mach, "priv_validator.json") - privVal := types.LoadPrivValidator(privValFile) + privVal := types.LoadPrivValidatorFS(privValFile) genVals[i] = types.GenesisValidator{ PubKey: privVal.PubKey(), Power: 1, @@ -87,7 +87,6 @@ func ensurePrivValidator(file string) { if cmn.FileExists(file) { return } - privValidator := types.GenPrivValidator() - privValidator.SetFile(file) + privValidator := types.GenPrivValidatorFS(file) privValidator.Save() } diff --git a/consensus/common_test.go b/consensus/common_test.go index cde56a3db..09b113553 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -368,9 +368,8 @@ func randConsensusNetWithPeers(nValidators, nPeers int, testName string, tickerF if i < nValidators { privVal = privVals[i] } else { - privVal = types.GenPrivValidator() _, tempFilePath := Tempfile("priv_validator_") - privVal.SetFile(tempFilePath) + privVal = types.GenPrivValidator(tempFilePath) } css[i] = newConsensusStateWithConfig(thisConfig, state, privVal, appFunc()) diff --git a/node/node.go b/node/node.go index a0a8c529a..4f22ee923 100644 --- a/node/node.go +++ b/node/node.go @@ -60,7 +60,7 @@ type Node struct { func NewNodeDefault(config *cfg.Config, logger log.Logger) *Node { // Get PrivValidator - privValidator := types.LoadOrGenPrivValidator(config.PrivValidatorFile()) + privValidator := types.LoadOrGenPrivValidatorFS(config.PrivValidatorFile()) return NewNode(config, privValidator, proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()), logger) } @@ -125,7 +125,7 @@ func NewNode(config *cfg.Config, privValidator types.PrivValidator, clientCreato } // Log whether this node is a validator or an observer - if state.Validators.HasAddress(privValidator.PubKey().Address()) { + if state.Validators.HasAddress(privValidator.Address()) { consensusLogger.Info("This node is a validator") } else { consensusLogger.Info("This node is not a validator") diff --git a/types/priv_validator.go b/types/priv_validator.go index e8fb3e240..a90f620b6 100644 --- a/types/priv_validator.go +++ b/types/priv_validator.go @@ -34,37 +34,7 @@ func voteToStep(vote *Vote) int8 { } } -// Signer is an interface that defines how to sign votes. -// It is the caller's duty to verify the msg before calling Sign, -// eg. to avoid double signing. -// Currently, the only callers are SignVote and SignProposal. -type Signer interface { - PubKey() crypto.PubKey - Sign(msg []byte) (crypto.Signature, error) -} - -// DefaultSigner implements Signer. -// It uses a standard crypto.PrivKey. -type DefaultSigner struct { - PrivKey crypto.PrivKey `json:"priv_key"` -} - -// NewDefaultSigner returns an instance of DefaultSigner. -func NewDefaultSigner(priv crypto.PrivKey) *DefaultSigner { - return &DefaultSigner{PrivKey: priv} -} - -// Sign implements Signer. It signs the byte slice with a private key. -func (ds *DefaultSigner) Sign(msg []byte) (crypto.Signature, error) { - return ds.PrivKey.Sign(msg), nil -} - -// PubKey implements Signer. It should return the public key that corresponds -// to the private key used for signing. -func (ds *DefaultSigner) PubKey() crypto.PubKey { - return ds.PrivKey.PubKey() -} - +// PrivValidator defines the functionality of a local Tendermint validator. type PrivValidator interface { Address() data.Bytes // redundant since .PubKey().Address() PubKey() crypto.PubKey @@ -72,60 +42,45 @@ type PrivValidator interface { SignVote(chainID string, vote *Vote) error SignProposal(chainID string, proposal *Proposal) error SignHeartbeat(chainID string, heartbeat *Heartbeat) error - - Reset() - - SetFile(file string) - Save() } -// DefaultPrivValidator implements the functionality for signing blocks. -type DefaultPrivValidator struct { - Info PrivValidatorInfo `json:"info"` - Signer *DefaultSigner `json:"signer"` +// PrivValidatorFS implements PrivValidator using data persisted to disk +// to prevent double signing. The Signer itself can be mutated to use +// something besides the default, for instance a hardware signer. +type PrivValidatorFS struct { + ID ValidatorID `json:"id"` + Signer Signer `json:"signer"` + + // mutable state to be persisted to disk + // after each signature to prevent double signing + mtx sync.Mutex + Info LastSignedInfo `json:"info"` // For persistence. // Overloaded for testing. filePath string - mtx sync.Mutex -} - -func (pv *DefaultPrivValidator) Address() data.Bytes { - return pv.Info.Address } -func (pv *DefaultPrivValidator) PubKey() crypto.PubKey { - return pv.Info.PubKey -} - -type PrivValidatorInfo struct { - Address data.Bytes `json:"address"` - PubKey crypto.PubKey `json:"pub_key"` - LastHeight int `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 -} - -func LoadOrGenPrivValidator(filePath string) *DefaultPrivValidator { - var privValidator *DefaultPrivValidator +// LoadOrGenPrivValidatorFS loads a PrivValidatorFS from the given filePath +// or else generates a new one and saves it to the filePath. +func LoadOrGenPrivValidatorFS(filePath string) *PrivValidatorFS { + var PrivValidatorFS *PrivValidatorFS if _, err := os.Stat(filePath); err == nil { - privValidator = LoadPrivValidator(filePath) + PrivValidatorFS = LoadPrivValidatorFS(filePath) } else { - privValidator = GenPrivValidator() - privValidator.SetFile(filePath) - privValidator.Save() + PrivValidatorFS = GenPrivValidatorFS(filePath) + PrivValidatorFS.Save() } - return privValidator + return PrivValidatorFS } -func LoadPrivValidator(filePath string) *DefaultPrivValidator { +// LoadPrivValidatorFS loads a PrivValidatorFS from the filePath. +func LoadPrivValidatorFS(filePath string) *PrivValidatorFS { privValJSONBytes, err := ioutil.ReadFile(filePath) if err != nil { Exit(err.Error()) } - privVal := DefaultPrivValidator{} + privVal := PrivValidatorFS{} err = json.Unmarshal(privValJSONBytes, &privVal) if err != nil { Exit(Fmt("Error reading PrivValidator from %v: %v\n", filePath, err)) @@ -135,56 +90,58 @@ func LoadPrivValidator(filePath string) *DefaultPrivValidator { return &privVal } -// Generates a new validator with private key. -func GenPrivValidator() *DefaultPrivValidator { +// GenPrivValidatorFS generates a new validator with randomly generated private key +// and sets the filePath, but does not call Save(). +func GenPrivValidatorFS(filePath string) *PrivValidatorFS { privKey := crypto.GenPrivKeyEd25519().Wrap() - pubKey := privKey.PubKey() - return &DefaultPrivValidator{ - Info: PrivValidatorInfo{ - Address: pubKey.Address(), - PubKey: pubKey, + return &PrivValidatorFS{ + ID: ValidatorID{privKey.PubKey().Address(), privKey.PubKey()}, + Info: LastSignedInfo{ LastStep: stepNone, }, Signer: NewDefaultSigner(privKey), - filePath: "", + filePath: filePath, } } -// LoadPrivValidatorWithSigner instantiates a private validator with a custom -// signer object. Tendermint tracks state in the PrivValidator that might be -// saved to disk. Please supply a filepath where Tendermint can save the -// private validator. -func LoadPrivValidatorWithSigner(signer *DefaultSigner, filePath string) *DefaultPrivValidator { - return &DefaultPrivValidator{ - Info: PrivValidatorInfo{ - Address: signer.PubKey().Address(), - PubKey: signer.PubKey(), - LastStep: stepNone, - }, - Signer: signer, - filePath: filePath, +// LoadPrivValidatorWithSigner loads a PrivValidatorFS with a custom +// signer object. The PrivValidatorFS handles double signing prevention by persisting +// data to the filePath, while the Signer handles the signing. +// If the filePath does not exist, the PrivValidatorFS must be created manually and saved. +func LoadPrivValidatorFSWithSigner(filePath string, signerFunc func(ValidatorID) Signer) *PrivValidatorFS { + privValJSONBytes, err := ioutil.ReadFile(filePath) + if err != nil { + Exit(err.Error()) } + privVal := PrivValidatorFS{} + err = json.Unmarshal(privValJSONBytes, &privVal) + if err != nil { + Exit(Fmt("Error reading PrivValidator from %v: %v\n", filePath, err)) + } + + privVal.filePath = filePath + privVal.Signer = signerFunc(privVal.ID) + return &privVal } -// Overwrite address and pubkey for convenience -/*func (privVal *DefaultPrivValidator) setPubKeyAndAddress() { - privVal.PubKey = privVal.Signer.PubKey() - privVal.Address = privVal.PubKey.Address() -}*/ +// Address returns the address of the validator. +func (pv *PrivValidatorFS) Address() data.Bytes { + return pv.ID.Address +} -func (privVal *DefaultPrivValidator) SetFile(filePath string) { - privVal.mtx.Lock() - defer privVal.mtx.Unlock() - privVal.filePath = filePath +// PubKey returns the public key of the validator. +func (pv *PrivValidatorFS) PubKey() crypto.PubKey { + return pv.ID.PubKey } -func (privVal *DefaultPrivValidator) Save() { +// Save persists the PrivValidatorFS to disk. +func (privVal *PrivValidatorFS) Save() { privVal.mtx.Lock() defer privVal.mtx.Unlock() privVal.save() } -func (privVal *DefaultPrivValidator) save() { +func (privVal *PrivValidatorFS) save() { if privVal.filePath == "" { PanicSanity("Cannot save PrivValidator: filePath not set") } @@ -200,8 +157,9 @@ func (privVal *DefaultPrivValidator) save() { } } +// Reset resets all fields in the PrivValidatorFS.Info. // NOTE: Unsafe! -func (privVal *DefaultPrivValidator) Reset() { +func (privVal *PrivValidatorFS) Reset() { privVal.Info.LastHeight = 0 privVal.Info.LastRound = 0 privVal.Info.LastStep = 0 @@ -210,11 +168,8 @@ func (privVal *DefaultPrivValidator) Reset() { privVal.Save() } -func (privVal *DefaultPrivValidator) GetAddress() []byte { - return privVal.Address() -} - -func (privVal *DefaultPrivValidator) SignVote(chainID string, vote *Vote) error { +// SignVote signs a canonical representation of the vote, along with the chainID. +func (privVal *PrivValidatorFS) SignVote(chainID string, vote *Vote) error { privVal.mtx.Lock() defer privVal.mtx.Unlock() signature, err := privVal.signBytesHRS(vote.Height, vote.Round, voteToStep(vote), SignBytes(chainID, vote)) @@ -225,7 +180,8 @@ func (privVal *DefaultPrivValidator) SignVote(chainID string, vote *Vote) error return nil } -func (privVal *DefaultPrivValidator) SignProposal(chainID string, proposal *Proposal) error { +// SignProposal signs a canonical representation of the proposal, along with the chainID. +func (privVal *PrivValidatorFS) SignProposal(chainID string, proposal *Proposal) error { privVal.mtx.Lock() defer privVal.mtx.Unlock() signature, err := privVal.signBytesHRS(proposal.Height, proposal.Round, stepPropose, SignBytes(chainID, proposal)) @@ -236,8 +192,17 @@ func (privVal *DefaultPrivValidator) SignProposal(chainID string, proposal *Prop return nil } +// SignHeartbeat signs a canonical representation of the heartbeat, along with the chainID. +func (privVal *PrivValidatorFS) SignHeartbeat(chainID string, heartbeat *Heartbeat) error { + privVal.mtx.Lock() + defer privVal.mtx.Unlock() + var err error + heartbeat.Signature, err = privVal.Signer.Sign(SignBytes(chainID, heartbeat)) + return err +} + // check if there's a regression. Else sign and write the hrs+signature to disk -func (privVal *DefaultPrivValidator) signBytesHRS(height, round int, step int8, signBytes []byte) (crypto.Signature, error) { +func (privVal *PrivValidatorFS) signBytesHRS(height, round int, step int8, signBytes []byte) (crypto.Signature, error) { sig := crypto.Signature{} info := privVal.Info // If height regression, err @@ -287,32 +252,68 @@ func (privVal *DefaultPrivValidator) signBytesHRS(height, round int, step int8, privVal.save() return sig, nil +} +// String returns a string representation of the PrivValidatorFS. +func (privVal *PrivValidatorFS) String() string { + info := privVal.Info + return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", privVal.Address(), info.LastHeight, info.LastRound, info.LastStep) } -func (privVal *DefaultPrivValidator) SignHeartbeat(chainID string, heartbeat *Heartbeat) error { - privVal.mtx.Lock() - defer privVal.mtx.Unlock() - var err error - heartbeat.Signature, err = privVal.Signer.Sign(SignBytes(chainID, heartbeat)) - return err +//------------------------------------- + +// ValidatorID contains the identity of the validator. +type ValidatorID struct { + Address data.Bytes `json:"address"` + PubKey crypto.PubKey `json:"pub_key"` } -func (privVal *DefaultPrivValidator) String() string { - info := privVal.Info - return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", info.Address, info.LastHeight, info.LastRound, info.LastStep) +// LastSignedInfo contains information about the latest +// data signed by a validator to help prevent double signing. +type LastSignedInfo struct { + LastHeight int `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 +} + +// Signer is an interface that defines how to sign messages. +// It is the caller's duty to verify the msg before calling Sign, +// eg. to avoid double signing. +// Currently, the only callers are SignVote, SignProposal, and SignHeartbeat. +type Signer interface { + Sign(msg []byte) (crypto.Signature, error) +} + +// DefaultSigner implements Signer. +// It uses a standard, unencrypted crypto.PrivKey. +type DefaultSigner struct { + PrivKey crypto.PrivKey `json:"priv_key"` +} + +// NewDefaultSigner returns an instance of DefaultSigner. +func NewDefaultSigner(priv crypto.PrivKey) *DefaultSigner { + return &DefaultSigner{ + PrivKey: priv, + } +} + +// Sign implements Signer. It signs the byte slice with a private key. +func (ds *DefaultSigner) Sign(msg []byte) (crypto.Signature, error) { + return ds.PrivKey.Sign(msg), nil } //------------------------------------- -type PrivValidatorsByAddress []*DefaultPrivValidator +type PrivValidatorsByAddress []*PrivValidatorFS func (pvs PrivValidatorsByAddress) Len() int { return len(pvs) } func (pvs PrivValidatorsByAddress) Less(i, j int) bool { - return bytes.Compare(pvs[i].Info.Address, pvs[j].Info.Address) == -1 + return bytes.Compare(pvs[i].Address(), pvs[j].Address()) == -1 } func (pvs PrivValidatorsByAddress) Swap(i, j int) { diff --git a/types/validator.go b/types/validator.go index 29cbfea27..e52831e27 100644 --- a/types/validator.go +++ b/types/validator.go @@ -106,10 +106,9 @@ func (vc validatorCodec) Compare(o1 interface{}, o2 interface{}) int { //-------------------------------------------------------------------------------- // For testing... -func RandValidator(randPower bool, minPower int64) (*Validator, *DefaultPrivValidator) { - privVal := GenPrivValidator() +func RandValidator(randPower bool, minPower int64) (*Validator, *PrivValidatorFS) { _, tempFilePath := cmn.Tempfile("priv_validator_") - privVal.SetFile(tempFilePath) + privVal := GenPrivValidatorFS(tempFilePath) votePower := minPower if randPower { votePower += int64(cmn.RandUint32()) diff --git a/types/validator_set.go b/types/validator_set.go index 5531f4af3..1e9e3e314 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -369,9 +369,9 @@ func (ac accumComparable) Less(o interface{}) bool { // For testing // NOTE: PrivValidator are in order. -func RandValidatorSet(numValidators int, votingPower int64) (*ValidatorSet, []*DefaultPrivValidator) { +func RandValidatorSet(numValidators int, votingPower int64) (*ValidatorSet, []*PrivValidatorFS) { vals := make([]*Validator, numValidators) - privValidators := make([]*DefaultPrivValidator, numValidators) + privValidators := make([]*PrivValidatorFS, numValidators) for i := 0; i < numValidators; i++ { val, privValidator := RandValidator(false, votingPower) vals[i] = val