From e8700152beb17a3f8df1aeb89390f2883982d50b Mon Sep 17 00:00:00 2001 From: yutianwu Date: Sat, 17 Nov 2018 18:48:33 +0800 Subject: [PATCH 01/10] split immutable and mutable parts of priv_validator.json --- CHANGELOG_PENDING.md | 1 + cmd/priv_val_server/main.go | 12 +- cmd/tendermint/commands/gen_validator.go | 2 +- cmd/tendermint/commands/init.go | 15 +- .../commands/reset_priv_validator.go | 23 +- cmd/tendermint/commands/show_validator.go | 2 +- cmd/tendermint/commands/testnet.go | 6 +- config/config.go | 59 +++-- config/toml.go | 24 +- config/toml_test.go | 2 +- consensus/common_test.go | 16 +- consensus/replay_test.go | 4 +- consensus/wal_generator.go | 5 +- node/node.go | 2 +- privval/priv_validator.go | 207 +++++++++++------- privval/priv_validator_test.go | 100 ++++++--- rpc/test/helpers.go | 5 +- scripts/wire2amino.go | 53 ++++- 18 files changed, 352 insertions(+), 186 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index b9a5454cd..26021918b 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -34,6 +34,7 @@ program](https://hackerone.com/tendermint). reset to 0 ### FEATURES: +- [privval] \#1181 Split immutable and mutable parts of priv_validator.json ### IMPROVEMENTS: diff --git a/cmd/priv_val_server/main.go b/cmd/priv_val_server/main.go index 03aa57f4e..546025588 100644 --- a/cmd/priv_val_server/main.go +++ b/cmd/priv_val_server/main.go @@ -13,9 +13,10 @@ import ( func main() { var ( - addr = flag.String("addr", ":26659", "Address of client to connect to") - chainID = flag.String("chain-id", "mychain", "chain id") - privValPath = flag.String("priv", "", "priv val file path") + addr = flag.String("addr", ":26659", "Address of client to connect to") + chainID = flag.String("chain-id", "mychain", "chain id") + privValKeyPath = flag.String("priv-key", "", "priv val key file path") + privValStatePath = flag.String("priv-state", "", "priv val state file path") logger = log.NewTMLogger( log.NewSyncWriter(os.Stdout), @@ -27,10 +28,11 @@ func main() { "Starting private validator", "addr", *addr, "chainID", *chainID, - "privPath", *privValPath, + "privKeyPath", *privValKeyPath, + "privStatePath", *privValStatePath, ) - pv := privval.LoadFilePV(*privValPath) + pv := privval.LoadFilePV(*privValKeyPath, *privValStatePath) rs := privval.NewRemoteSigner( logger, diff --git a/cmd/tendermint/commands/gen_validator.go b/cmd/tendermint/commands/gen_validator.go index 20d43d4dd..572bc974f 100644 --- a/cmd/tendermint/commands/gen_validator.go +++ b/cmd/tendermint/commands/gen_validator.go @@ -17,7 +17,7 @@ var GenValidatorCmd = &cobra.Command{ } func genValidator(cmd *cobra.Command, args []string) { - pv := privval.GenFilePV("") + pv := privval.GenFilePV("", "") jsbz, err := cdc.MarshalJSON(pv) if err != nil { panic(err) diff --git a/cmd/tendermint/commands/init.go b/cmd/tendermint/commands/init.go index 85ee44916..0ef1e2d24 100644 --- a/cmd/tendermint/commands/init.go +++ b/cmd/tendermint/commands/init.go @@ -26,15 +26,18 @@ func initFiles(cmd *cobra.Command, args []string) error { func initFilesWithConfig(config *cfg.Config) error { // private validator - privValFile := config.PrivValidatorFile() + privValkeyFile := config.PrivValidatorKeyFile() + privValStateFile := config.PrivValidatorStateFile() var pv *privval.FilePV - if cmn.FileExists(privValFile) { - pv = privval.LoadFilePV(privValFile) - logger.Info("Found private validator", "path", privValFile) + if cmn.FileExists(privValkeyFile) { + pv = privval.LoadFilePV(privValkeyFile, privValStateFile) + logger.Info("Found private validator", "keyFile", privValkeyFile, + "stateFile", privValStateFile) } else { - pv = privval.GenFilePV(privValFile) + pv = privval.GenFilePV(privValkeyFile, privValStateFile) pv.Save() - logger.Info("Generated private validator", "path", privValFile) + logger.Info("Generated private validator", "keyFile", privValkeyFile, + "stateFile", privValStateFile) } nodeKeyFile := config.NodeKeyFile() diff --git a/cmd/tendermint/commands/reset_priv_validator.go b/cmd/tendermint/commands/reset_priv_validator.go index 53d347129..1669e6737 100644 --- a/cmd/tendermint/commands/reset_priv_validator.go +++ b/cmd/tendermint/commands/reset_priv_validator.go @@ -27,19 +27,20 @@ var ResetPrivValidatorCmd = &cobra.Command{ // XXX: this is totally unsafe. // it's only suitable for testnets. func resetAll(cmd *cobra.Command, args []string) { - ResetAll(config.DBDir(), config.P2P.AddrBookFile(), config.PrivValidatorFile(), logger) + ResetAll(config.DBDir(), config.P2P.AddrBookFile(), config.PrivValidatorKeyFile(), + config.PrivValidatorStateFile(), logger) } // XXX: this is totally unsafe. // it's only suitable for testnets. func resetPrivValidator(cmd *cobra.Command, args []string) { - resetFilePV(config.PrivValidatorFile(), logger) + resetFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile(), logger) } // ResetAll removes the privValidator and address book files plus all data. // Exported so other CLI tools can use it. -func ResetAll(dbDir, addrBookFile, privValFile string, logger log.Logger) { - resetFilePV(privValFile, logger) +func ResetAll(dbDir, addrBookFile, privValKeyFile, privValStateFile string, logger log.Logger) { + resetFilePV(privValKeyFile, privValStateFile, logger) removeAddrBook(addrBookFile, logger) if err := os.RemoveAll(dbDir); err == nil { logger.Info("Removed all blockchain history", "dir", dbDir) @@ -48,15 +49,17 @@ func ResetAll(dbDir, addrBookFile, privValFile string, logger log.Logger) { } } -func resetFilePV(privValFile string, logger log.Logger) { - if _, err := os.Stat(privValFile); err == nil { - pv := privval.LoadFilePV(privValFile) +func resetFilePV(privValKeyFile, privValStateFile string, logger log.Logger) { + if _, err := os.Stat(privValKeyFile); err == nil { + pv := privval.LoadFilePV(privValKeyFile, privValStateFile) pv.Reset() - logger.Info("Reset private validator file to genesis state", "file", privValFile) + logger.Info("Reset private validator file to genesis state", "keyFile", privValKeyFile, + "stateFile", privValStateFile) } else { - pv := privval.GenFilePV(privValFile) + pv := privval.GenFilePV(privValKeyFile, privValStateFile) pv.Save() - logger.Info("Generated private validator file", "file", privValFile) + logger.Info("Generated private validator file", "file", "keyFile", privValKeyFile, + "stateFile", privValStateFile) } } diff --git a/cmd/tendermint/commands/show_validator.go b/cmd/tendermint/commands/show_validator.go index 54765164b..78bc06034 100644 --- a/cmd/tendermint/commands/show_validator.go +++ b/cmd/tendermint/commands/show_validator.go @@ -16,7 +16,7 @@ var ShowValidatorCmd = &cobra.Command{ } func showValidator(cmd *cobra.Command, args []string) { - privValidator := privval.LoadOrGenFilePV(config.PrivValidatorFile()) + privValidator := privval.LoadOrGenFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()) pubKeyJSONBytes, _ := cdc.MarshalJSON(privValidator.GetPubKey()) fmt.Println(string(pubKeyJSONBytes)) } diff --git a/cmd/tendermint/commands/testnet.go b/cmd/tendermint/commands/testnet.go index 0f7dd79a4..6398f4ebd 100644 --- a/cmd/tendermint/commands/testnet.go +++ b/cmd/tendermint/commands/testnet.go @@ -88,8 +88,10 @@ func testnetFiles(cmd *cobra.Command, args []string) error { initFilesWithConfig(config) - pvFile := filepath.Join(nodeDir, config.BaseConfig.PrivValidator) - pv := privval.LoadFilePV(pvFile) + pvKeyFile := filepath.Join(nodeDir, config.BaseConfig.PrivValidatorKey) + pvStateFile := filepath.Join(nodeDir, config.BaseConfig.PrivValidatorState) + + pv := privval.LoadFilePV(pvKeyFile, pvStateFile) genVals[i] = types.GenesisValidator{ Address: pv.GetPubKey().Address(), PubKey: pv.GetPubKey(), diff --git a/config/config.go b/config/config.go index 23b033994..85d55b556 100644 --- a/config/config.go +++ b/config/config.go @@ -35,15 +35,19 @@ var ( defaultConfigFileName = "config.toml" defaultGenesisJSONName = "genesis.json" - defaultPrivValName = "priv_validator.json" + defaultPrivValKeyName = "priv_validator_key.json" + defaultPrivValStateName = "priv_validator_state.json" + defaultNodeKeyName = "node_key.json" defaultAddrBookName = "addrbook.json" - defaultConfigFilePath = filepath.Join(defaultConfigDir, defaultConfigFileName) - defaultGenesisJSONPath = filepath.Join(defaultConfigDir, defaultGenesisJSONName) - defaultPrivValPath = filepath.Join(defaultConfigDir, defaultPrivValName) - defaultNodeKeyPath = filepath.Join(defaultConfigDir, defaultNodeKeyName) - defaultAddrBookPath = filepath.Join(defaultConfigDir, defaultAddrBookName) + defaultConfigFilePath = filepath.Join(defaultConfigDir, defaultConfigFileName) + defaultGenesisJSONPath = filepath.Join(defaultConfigDir, defaultGenesisJSONName) + defaultPrivValKeyPath = filepath.Join(defaultConfigDir, defaultPrivValKeyName) + defaultPrivValStatePath = filepath.Join(defaultDataDir, defaultPrivValStateName) + + defaultNodeKeyPath = filepath.Join(defaultConfigDir, defaultNodeKeyName) + defaultAddrBookPath = filepath.Join(defaultConfigDir, defaultAddrBookName) ) // Config defines the top level configuration for a Tendermint node @@ -160,7 +164,10 @@ type BaseConfig struct { Genesis string `mapstructure:"genesis_file"` // Path to the JSON file containing the private key to use as a validator in the consensus protocol - PrivValidator string `mapstructure:"priv_validator_file"` + PrivValidatorKey string `mapstructure:"priv_validator_key_file"` + + // Path to the JSON file containing the last sign state of a validator + PrivValidatorState string `mapstructure:"priv_validator_state_file"` // TCP or UNIX socket address for Tendermint to listen on for // connections from an external PrivValidator process @@ -183,19 +190,20 @@ type BaseConfig struct { // DefaultBaseConfig returns a default base configuration for a Tendermint node func DefaultBaseConfig() BaseConfig { return BaseConfig{ - Genesis: defaultGenesisJSONPath, - PrivValidator: defaultPrivValPath, - NodeKey: defaultNodeKeyPath, - Moniker: defaultMoniker, - ProxyApp: "tcp://127.0.0.1:26658", - ABCI: "socket", - LogLevel: DefaultPackageLogLevels(), - LogFormat: LogFormatPlain, - ProfListenAddress: "", - FastSync: true, - FilterPeers: false, - DBBackend: "leveldb", - DBPath: "data", + Genesis: defaultGenesisJSONPath, + PrivValidatorKey: defaultPrivValKeyPath, + PrivValidatorState: defaultPrivValStatePath, + NodeKey: defaultNodeKeyPath, + Moniker: defaultMoniker, + ProxyApp: "tcp://127.0.0.1:26658", + ABCI: "socket", + LogLevel: DefaultPackageLogLevels(), + LogFormat: LogFormatPlain, + ProfListenAddress: "", + FastSync: true, + FilterPeers: false, + DBBackend: "leveldb", + DBPath: "data", } } @@ -218,9 +226,14 @@ func (cfg BaseConfig) GenesisFile() string { return rootify(cfg.Genesis, cfg.RootDir) } -// PrivValidatorFile returns the full path to the priv_validator.json file -func (cfg BaseConfig) PrivValidatorFile() string { - return rootify(cfg.PrivValidator, cfg.RootDir) +// PrivValidatorKeyFile returns the full path to the priv_validator_key.json file +func (cfg BaseConfig) PrivValidatorKeyFile() string { + return rootify(cfg.PrivValidatorKey, cfg.RootDir) +} + +// PrivValidatorFile returns the full path to the priv_validator_state.json file +func (cfg BaseConfig) PrivValidatorStateFile() string { + return rootify(cfg.PrivValidatorState, cfg.RootDir) } // NodeKeyFile returns the full path to the node_key.json file diff --git a/config/toml.go b/config/toml.go index 21e017b45..ad48e211c 100644 --- a/config/toml.go +++ b/config/toml.go @@ -95,7 +95,10 @@ log_format = "{{ .BaseConfig.LogFormat }}" genesis_file = "{{ js .BaseConfig.Genesis }}" # Path to the JSON file containing the private key to use as a validator in the consensus protocol -priv_validator_file = "{{ js .BaseConfig.PrivValidator }}" +priv_validator_key_file = "{{ js .BaseConfig.PrivValidatorKey }}" + +# Path to the JSON file containing the last sign state of a validator +priv_validator_state_file = "{{ js .BaseConfig.PrivValidatorState }}" # TCP or UNIX socket address for Tendermint to listen on for # connections from an external PrivValidator process @@ -342,7 +345,8 @@ func ResetTestRoot(testName string) *Config { baseConfig := DefaultBaseConfig() configFilePath := filepath.Join(rootDir, defaultConfigFilePath) genesisFilePath := filepath.Join(rootDir, baseConfig.Genesis) - privFilePath := filepath.Join(rootDir, baseConfig.PrivValidator) + privKeyFilePath := filepath.Join(rootDir, baseConfig.PrivValidatorKey) + privStateFilePath := filepath.Join(rootDir, baseConfig.PrivValidatorState) // Write default config file if missing. if !cmn.FileExists(configFilePath) { @@ -352,7 +356,8 @@ func ResetTestRoot(testName string) *Config { cmn.MustWriteFile(genesisFilePath, []byte(testGenesis), 0644) } // we always overwrite the priv val - cmn.MustWriteFile(privFilePath, []byte(testPrivValidator), 0644) + cmn.MustWriteFile(privKeyFilePath, []byte(testPrivValidatorKey), 0644) + cmn.MustWriteFile(privStateFilePath, []byte(testPrivValidatorState), 0644) config := TestConfig().SetRoot(rootDir) return config @@ -374,7 +379,7 @@ var testGenesis = `{ "app_hash": "" }` -var testPrivValidator = `{ +var testPrivValidatorKey = `{ "address": "A3258DCBF45DCA0DF052981870F2D1441A36D145", "pub_key": { "type": "tendermint/PubKeyEd25519", @@ -383,8 +388,11 @@ var testPrivValidator = `{ "priv_key": { "type": "tendermint/PrivKeyEd25519", "value": "EVkqJO/jIXp3rkASXfh9YnyToYXRXhBr6g9cQVxPFnQBP/5povV4HTjvsy530kybxKHwEi85iU8YL0qQhSYVoQ==" - }, - "last_height": "0", - "last_round": "0", - "last_step": 0 + } +}` + +var testPrivValidatorState = `{ + "height": "0", + "round": "0", + "step": 0 }` diff --git a/config/toml_test.go b/config/toml_test.go index a1637f671..59528db16 100644 --- a/config/toml_test.go +++ b/config/toml_test.go @@ -60,7 +60,7 @@ func TestEnsureTestRoot(t *testing.T) { // TODO: make sure the cfg returned and testconfig are the same! baseConfig := DefaultBaseConfig() - ensureFiles(t, rootDir, defaultDataDir, baseConfig.Genesis, baseConfig.PrivValidator) + ensureFiles(t, rootDir, defaultDataDir, baseConfig.Genesis, baseConfig.PrivValidatorKey, baseConfig.PrivValidatorState) } func checkConfig(configFile string) bool { diff --git a/consensus/common_test.go b/consensus/common_test.go index 46be5cbd7..aa0c75511 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -281,9 +281,10 @@ func newConsensusStateWithConfigAndBlockStore(thisConfig *cfg.Config, state sm.S } func loadPrivValidator(config *cfg.Config) *privval.FilePV { - privValidatorFile := config.PrivValidatorFile() - ensureDir(path.Dir(privValidatorFile), 0700) - privValidator := privval.LoadOrGenFilePV(privValidatorFile) + privValidatorKeyFile := config.PrivValidatorKeyFile() + ensureDir(path.Dir(privValidatorKeyFile), 0700) + privValidatorStateFile := config.PrivValidatorStateFile() + privValidator := privval.LoadOrGenFilePV(privValidatorKeyFile, privValidatorStateFile) privValidator.Reset() return privValidator } @@ -617,11 +618,16 @@ func randConsensusNetWithPeers(nValidators, nPeers int, testName string, tickerF if i < nValidators { privVal = privVals[i] } else { - tempFile, err := ioutil.TempFile("", "priv_validator_") + tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_") if err != nil { panic(err) } - privVal = privval.GenFilePV(tempFile.Name()) + tempStateFile, err := ioutil.TempFile("", "priv_validator_state_") + if err != nil { + panic(err) + } + + privVal = privval.GenFilePV(tempKeyFile.Name(), tempStateFile.Name()) } app := appFunc() diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 7cd32c7aa..71b93775c 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -319,7 +319,7 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) { walFile := tempWALWithData(walBody) config.Consensus.SetWalFile(walFile) - privVal := privval.LoadFilePV(config.PrivValidatorFile()) + privVal := privval.LoadFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()) wal, err := NewWAL(walFile) require.NoError(t, err) @@ -633,7 +633,7 @@ func TestInitChainUpdateValidators(t *testing.T) { clientCreator := proxy.NewLocalClientCreator(app) config := ResetConfig("proxy_test_") - privVal := privval.LoadFilePV(config.PrivValidatorFile()) + privVal := privval.LoadFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()) stateDB, state, store := stateAndStore(config, privVal.GetPubKey(), 0x0) oldValAddr := state.Validators.Validators[0].Address diff --git a/consensus/wal_generator.go b/consensus/wal_generator.go index 5ff597a52..83861d3ec 100644 --- a/consensus/wal_generator.go +++ b/consensus/wal_generator.go @@ -40,8 +40,9 @@ func WALGenerateNBlocks(wr io.Writer, numBlocks int) (err error) { // COPY PASTE FROM node.go WITH A FEW MODIFICATIONS // NOTE: we can't import node package because of circular dependency. // NOTE: we don't do handshake so need to set state.Version.Consensus.App directly. - privValidatorFile := config.PrivValidatorFile() - privValidator := privval.LoadOrGenFilePV(privValidatorFile) + privValidatorKeyFile := config.PrivValidatorKeyFile() + privValidatorStateFile := config.PrivValidatorStateFile() + privValidator := privval.LoadOrGenFilePV(privValidatorKeyFile, privValidatorStateFile) genDoc, err := types.GenesisDocFromFile(config.GenesisFile()) if err != nil { return errors.Wrap(err, "failed to read genesis file") diff --git a/node/node.go b/node/node.go index 8e41dfd11..f03e5bab5 100644 --- a/node/node.go +++ b/node/node.go @@ -87,7 +87,7 @@ func DefaultNewNode(config *cfg.Config, logger log.Logger) (*Node, error) { return nil, err } return NewNode(config, - privval.LoadOrGenFilePV(config.PrivValidatorFile()), + privval.LoadOrGenFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()), nodeKey, proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()), DefaultGenesisDocProviderFunc(config), diff --git a/privval/priv_validator.go b/privval/priv_validator.go index ba777e1fd..7d53c5689 100644 --- a/privval/priv_validator.go +++ b/privval/priv_validator.go @@ -37,21 +37,31 @@ func voteToStep(vote *types.Vote) int8 { // FilePV implements PrivValidator using data persisted to disk // to prevent double signing. -// NOTE: the directory containing the pv.filePath must already exist. +// NOTE: the directories containing pv.Key.filePath and pv.LastSignState.filePath must already exist. // It includes the LastSignature and LastSignBytes so we don't lose the signature // if the process crashes after signing but before the resulting consensus message is processed. type FilePV struct { - Address types.Address `json:"address"` - PubKey crypto.PubKey `json:"pub_key"` - LastHeight int64 `json:"last_height"` - LastRound int `json:"last_round"` - LastStep int8 `json:"last_step"` - LastSignature []byte `json:"last_signature,omitempty"` - LastSignBytes cmn.HexBytes `json:"last_signbytes,omitempty"` - PrivKey crypto.PrivKey `json:"priv_key"` + Key FilePVKey + LastSignState FilePVLastSignState +} + +// FilePVKey stores the immutable part of PrivValidator +type FilePVKey struct { + Address types.Address `json:"address"` + PubKey crypto.PubKey `json:"pub_key"` + PrivKey crypto.PrivKey `json:"priv_key"` + + filePath string +} + +// FilePVState stores the mutable part of PrivValidator +type FilePVLastSignState struct { + Height int64 `json:"height"` + Round int `json:"round"` + Step int8 `json:"step"` + Signature []byte `json:"signature,omitempty"` + SignBytes cmn.HexBytes `json:"signbytes,omitempty"` - // For persistence. - // Overloaded for testing. filePath string mtx sync.Mutex } @@ -59,58 +69,82 @@ type FilePV struct { // GetAddress returns the address of the validator. // Implements PrivValidator. func (pv *FilePV) GetAddress() types.Address { - return pv.Address + return pv.Key.Address } // GetPubKey returns the public key of the validator. // Implements PrivValidator. func (pv *FilePV) GetPubKey() crypto.PubKey { - return pv.PubKey + return pv.Key.PubKey } // GenFilePV generates a new validator with randomly generated private key -// and sets the filePath, but does not call Save(). -func GenFilePV(filePath string) *FilePV { +// and sets the filePaths, but does not call Save(). +func GenFilePV(keyFilePath string, stateFilePath string) *FilePV { privKey := ed25519.GenPrivKey() + return &FilePV{ - Address: privKey.PubKey().Address(), - PubKey: privKey.PubKey(), - PrivKey: privKey, - LastStep: stepNone, - filePath: filePath, + Key: FilePVKey{ + Address: privKey.PubKey().Address(), + PubKey: privKey.PubKey(), + PrivKey: privKey, + filePath: keyFilePath, + }, + LastSignState: FilePVLastSignState{ + Step: stepNone, + filePath: stateFilePath, + }, } } -// LoadFilePV loads a FilePV from the filePath. The FilePV handles double -// signing prevention by persisting data to the filePath. If the filePath does +// LoadFilePV loads a FilePV from the filePaths. The FilePV handles double +// signing prevention by persisting data to the stateFilePath. If the filePaths does // not exist, the FilePV must be created manually and saved. -func LoadFilePV(filePath string) *FilePV { - pvJSONBytes, err := ioutil.ReadFile(filePath) +func LoadFilePV(keyFilePath string, stateFilePath string) *FilePV { + keyJSONBytes, err := ioutil.ReadFile(keyFilePath) if err != nil { cmn.Exit(err.Error()) } - pv := &FilePV{} - err = cdc.UnmarshalJSON(pvJSONBytes, &pv) + pvKey := FilePVKey{} + err = cdc.UnmarshalJSON(keyJSONBytes, &pvKey) if err != nil { - cmn.Exit(fmt.Sprintf("Error reading PrivValidator from %v: %v\n", filePath, err)) + cmn.Exit(fmt.Sprintf("Error reading PrivValidator key from %v: %v\n", keyFilePath, err)) } // overwrite pubkey and address for convenience - pv.PubKey = pv.PrivKey.PubKey() - pv.Address = pv.PubKey.Address() + pvKey.PubKey = pvKey.PrivKey.PubKey() + pvKey.Address = pvKey.PubKey.Address() + pvKey.filePath = keyFilePath + + stateJSONBytes, err := ioutil.ReadFile(stateFilePath) + if err != nil { + cmn.Exit(err.Error()) + } + pvState := FilePVLastSignState{} + err = cdc.UnmarshalJSON(stateJSONBytes, &pvState) + if err != nil { + cmn.Exit(fmt.Sprintf("Error reading PrivValidator state from %v: %v\n", stateFilePath, err)) + } + + pvState.filePath = stateFilePath + + pv := &FilePV{} + + pv.Key = pvKey + pv.LastSignState = pvState - pv.filePath = filePath return pv } -// LoadOrGenFilePV loads a FilePV from the given filePath -// or else generates a new one and saves it to the filePath. -func LoadOrGenFilePV(filePath string) *FilePV { +// LoadOrGenFilePV loads a FilePV from the given filePaths +// or else generates a new one and saves it to the filePaths. +func LoadOrGenFilePV(keyFilePath string, stateFilePath string) *FilePV { var pv *FilePV - if cmn.FileExists(filePath) { - pv = LoadFilePV(filePath) + if cmn.FileExists(keyFilePath) { + println(keyFilePath) + pv = LoadFilePV(keyFilePath, stateFilePath) } else { - pv = GenFilePV(filePath) + pv = GenFilePV(keyFilePath, stateFilePath) pv.Save() } return pv @@ -118,17 +152,36 @@ func LoadOrGenFilePV(filePath string) *FilePV { // Save persists the FilePV to disk. func (pv *FilePV) Save() { - pv.mtx.Lock() - defer pv.mtx.Unlock() - pv.save() + pv.saveKey() + + pv.LastSignState.mtx.Lock() + defer pv.LastSignState.mtx.Unlock() + pv.saveState() } -func (pv *FilePV) save() { - outFile := pv.filePath +func (pv *FilePV) saveKey() { + outFile := pv.Key.filePath if outFile == "" { - panic("Cannot save PrivValidator: filePath not set") + panic("Cannot save PrivValidator key: filePath not set") } - jsonBytes, err := cdc.MarshalJSONIndent(pv, "", " ") + + jsonBytes, err := cdc.MarshalJSONIndent(pv.Key, "", " ") + if err != nil { + panic(err) + } + err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600) + if err != nil { + panic(err) + } + +} + +func (pv *FilePV) saveState() { + outFile := pv.LastSignState.filePath + if outFile == "" { + panic("Cannot save PrivValidator state: filePath not set") + } + jsonBytes, err := cdc.MarshalJSONIndent(pv.LastSignState, "", " ") if err != nil { panic(err) } @@ -142,19 +195,19 @@ func (pv *FilePV) save() { // NOTE: Unsafe! func (pv *FilePV) Reset() { var sig []byte - pv.LastHeight = 0 - pv.LastRound = 0 - pv.LastStep = 0 - pv.LastSignature = sig - pv.LastSignBytes = nil + pv.LastSignState.Height = 0 + pv.LastSignState.Round = 0 + pv.LastSignState.Step = 0 + pv.LastSignState.Signature = sig + pv.LastSignState.SignBytes = nil pv.Save() } // SignVote signs a canonical representation of the vote, along with the // chainID. Implements PrivValidator. func (pv *FilePV) SignVote(chainID string, vote *types.Vote) error { - pv.mtx.Lock() - defer pv.mtx.Unlock() + pv.LastSignState.mtx.Lock() + defer pv.LastSignState.mtx.Unlock() if err := pv.signVote(chainID, vote); err != nil { return fmt.Errorf("Error signing vote: %v", err) } @@ -164,8 +217,8 @@ func (pv *FilePV) SignVote(chainID string, vote *types.Vote) error { // SignProposal signs a canonical representation of the proposal, along with // the chainID. Implements PrivValidator. func (pv *FilePV) SignProposal(chainID string, proposal *types.Proposal) error { - pv.mtx.Lock() - defer pv.mtx.Unlock() + pv.LastSignState.mtx.Lock() + defer pv.LastSignState.mtx.Unlock() if err := pv.signProposal(chainID, proposal); err != nil { return fmt.Errorf("Error signing proposal: %v", err) } @@ -174,21 +227,21 @@ func (pv *FilePV) SignProposal(chainID string, proposal *types.Proposal) error { // returns error if HRS regression or no LastSignBytes. returns true if HRS is unchanged func (pv *FilePV) checkHRS(height int64, round int, step int8) (bool, error) { - if pv.LastHeight > height { + if pv.LastSignState.Height > height { return false, errors.New("Height regression") } - if pv.LastHeight == height { - if pv.LastRound > round { + if pv.LastSignState.Height == height { + if pv.LastSignState.Round > round { return false, errors.New("Round regression") } - if pv.LastRound == round { - if pv.LastStep > step { + if pv.LastSignState.Round == round { + if pv.LastSignState.Step > step { return false, errors.New("Step regression") - } else if pv.LastStep == step { - if pv.LastSignBytes != nil { - if pv.LastSignature == nil { + } else if pv.LastSignState.Step == step { + if pv.LastSignState.SignBytes != nil { + if pv.LastSignState.Signature == nil { panic("pv: LastSignature is nil but LastSignBytes is not!") } return true, nil @@ -218,11 +271,11 @@ func (pv *FilePV) signVote(chainID string, vote *types.Vote) error { // If they only differ by timestamp, use last timestamp and signature // Otherwise, return error if sameHRS { - if bytes.Equal(signBytes, pv.LastSignBytes) { - vote.Signature = pv.LastSignature - } else if timestamp, ok := checkVotesOnlyDifferByTimestamp(pv.LastSignBytes, signBytes); ok { + if bytes.Equal(signBytes, pv.LastSignState.SignBytes) { + vote.Signature = pv.LastSignState.Signature + } else if timestamp, ok := checkVotesOnlyDifferByTimestamp(pv.LastSignState.SignBytes, signBytes); ok { vote.Timestamp = timestamp - vote.Signature = pv.LastSignature + vote.Signature = pv.LastSignState.Signature } else { err = fmt.Errorf("Conflicting data") } @@ -230,7 +283,7 @@ func (pv *FilePV) signVote(chainID string, vote *types.Vote) error { } // It passed the checks. Sign the vote - sig, err := pv.PrivKey.Sign(signBytes) + sig, err := pv.Key.PrivKey.Sign(signBytes) if err != nil { return err } @@ -257,11 +310,11 @@ func (pv *FilePV) signProposal(chainID string, proposal *types.Proposal) error { // If they only differ by timestamp, use last timestamp and signature // Otherwise, return error if sameHRS { - if bytes.Equal(signBytes, pv.LastSignBytes) { - proposal.Signature = pv.LastSignature - } else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(pv.LastSignBytes, signBytes); ok { + if bytes.Equal(signBytes, pv.LastSignState.SignBytes) { + proposal.Signature = pv.LastSignState.Signature + } else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(pv.LastSignState.SignBytes, signBytes); ok { proposal.Timestamp = timestamp - proposal.Signature = pv.LastSignature + proposal.Signature = pv.LastSignState.Signature } else { err = fmt.Errorf("Conflicting data") } @@ -269,7 +322,7 @@ func (pv *FilePV) signProposal(chainID string, proposal *types.Proposal) error { } // It passed the checks. Sign the proposal - sig, err := pv.PrivKey.Sign(signBytes) + sig, err := pv.Key.PrivKey.Sign(signBytes) if err != nil { return err } @@ -282,17 +335,17 @@ func (pv *FilePV) signProposal(chainID string, proposal *types.Proposal) error { func (pv *FilePV) saveSigned(height int64, round int, step int8, signBytes []byte, sig []byte) { - pv.LastHeight = height - pv.LastRound = round - pv.LastStep = step - pv.LastSignature = sig - pv.LastSignBytes = signBytes - pv.save() + pv.LastSignState.Height = height + pv.LastSignState.Round = round + pv.LastSignState.Step = step + pv.LastSignState.Signature = sig + pv.LastSignState.SignBytes = signBytes + pv.saveState() } // String returns a string representation of the FilePV. func (pv *FilePV) String() string { - return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", pv.GetAddress(), pv.LastHeight, pv.LastRound, pv.LastStep) + return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", pv.GetAddress(), pv.LastSignState.Height, pv.LastSignState.Round, pv.LastSignState.Step) } //------------------------------------- diff --git a/privval/priv_validator_test.go b/privval/priv_validator_test.go index 4f4eed97b..394656926 100644 --- a/privval/priv_validator_test.go +++ b/privval/priv_validator_test.go @@ -18,36 +18,72 @@ import ( func TestGenLoadValidator(t *testing.T) { assert := assert.New(t) - tempFile, err := ioutil.TempFile("", "priv_validator_") + tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_") require.Nil(t, err) - privVal := GenFilePV(tempFile.Name()) + tempStateFile, err := ioutil.TempFile("", "priv_validator_state_") + require.Nil(t, err) + + privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name()) height := int64(100) - privVal.LastHeight = height + privVal.LastSignState.Height = height privVal.Save() addr := privVal.GetAddress() - privVal = LoadFilePV(tempFile.Name()) + privVal = LoadFilePV(tempKeyFile.Name(), tempStateFile.Name()) assert.Equal(addr, privVal.GetAddress(), "expected privval addr to be the same") - assert.Equal(height, privVal.LastHeight, "expected privval.LastHeight to have been saved") + assert.Equal(height, privVal.LastSignState.Height, "expected privval.LastHeight to have been saved") } func TestLoadOrGenValidator(t *testing.T) { assert := assert.New(t) - tempFile, err := ioutil.TempFile("", "priv_validator_") + tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_") require.Nil(t, err) - tempFilePath := tempFile.Name() - if err := os.Remove(tempFilePath); err != nil { + tempStateFile, err := ioutil.TempFile("", "priv_validator_state_") + require.Nil(t, err) + + tempKeyFilePath := tempKeyFile.Name() + if err := os.Remove(tempKeyFilePath); err != nil { t.Error(err) } - privVal := LoadOrGenFilePV(tempFilePath) + tempStateFilePath := tempStateFile.Name() + if err := os.Remove(tempStateFilePath); err != nil { + t.Error(err) + } + + privVal := LoadOrGenFilePV(tempKeyFilePath, tempStateFilePath) addr := privVal.GetAddress() - privVal = LoadOrGenFilePV(tempFilePath) + privVal = LoadOrGenFilePV(tempKeyFilePath, tempStateFilePath) assert.Equal(addr, privVal.GetAddress(), "expected privval addr to be the same") } -func TestUnmarshalValidator(t *testing.T) { +func TestUnmarshalValidatorState(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + // create some fixed values + serialized := fmt.Sprintf(`{ + "height": "1", + "round": "1", + "step": 1 + }`) + + val := FilePVLastSignState{} + err := cdc.UnmarshalJSON([]byte(serialized), &val) + require.Nil(err, "%+v", err) + + // make sure the values match + assert.EqualValues(val.Height, 1) + assert.EqualValues(val.Round, 1) + assert.EqualValues(val.Step, 1) + + // export it and make sure it is the same + out, err := cdc.MarshalJSON(val) + require.Nil(err, "%+v", err) + assert.JSONEq(serialized, string(out)) +} + +func TestUnmarshalValidatorKey(t *testing.T) { assert, require := assert.New(t), require.New(t) // create some fixed values @@ -67,22 +103,19 @@ func TestUnmarshalValidator(t *testing.T) { "type": "tendermint/PubKeyEd25519", "value": "%s" }, - "last_height": "0", - "last_round": "0", - "last_step": 0, "priv_key": { "type": "tendermint/PrivKeyEd25519", "value": "%s" } }`, addr, pubB64, privB64) - val := FilePV{} + val := FilePVKey{} err := cdc.UnmarshalJSON([]byte(serialized), &val) require.Nil(err, "%+v", err) // make sure the values match - assert.EqualValues(addr, val.GetAddress()) - assert.EqualValues(pubKey, val.GetPubKey()) + assert.EqualValues(addr, val.Address) + assert.EqualValues(pubKey, val.PubKey) assert.EqualValues(privKey, val.PrivKey) // export it and make sure it is the same @@ -94,9 +127,12 @@ func TestUnmarshalValidator(t *testing.T) { func TestSignVote(t *testing.T) { assert := assert.New(t) - tempFile, err := ioutil.TempFile("", "priv_validator_") + tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_") require.Nil(t, err) - privVal := GenFilePV(tempFile.Name()) + tempStateFile, err := ioutil.TempFile("", "priv_validator_state_") + require.Nil(t, err) + + privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name()) block1 := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}} block2 := types.BlockID{[]byte{3, 2, 1}, types.PartSetHeader{}} @@ -104,7 +140,7 @@ func TestSignVote(t *testing.T) { voteType := byte(types.PrevoteType) // sign a vote for first time - vote := newVote(privVal.Address, 0, height, round, voteType, block1) + vote := newVote(privVal.Key.Address, 0, height, round, voteType, block1) err = privVal.SignVote("mychainid", vote) assert.NoError(err, "expected no error signing vote") @@ -114,10 +150,10 @@ func TestSignVote(t *testing.T) { // 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 + newVote(privVal.Key.Address, 0, height, round-1, voteType, block1), // round regression + newVote(privVal.Key.Address, 0, height-1, round, voteType, block1), // height regression + newVote(privVal.Key.Address, 0, height-2, round+4, voteType, block1), // height regression and different round + newVote(privVal.Key.Address, 0, height, round, voteType, block2), // different block } for _, c := range cases { @@ -136,9 +172,12 @@ func TestSignVote(t *testing.T) { func TestSignProposal(t *testing.T) { assert := assert.New(t) - tempFile, err := ioutil.TempFile("", "priv_validator_") + tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_") require.Nil(t, err) - privVal := GenFilePV(tempFile.Name()) + tempStateFile, err := ioutil.TempFile("", "priv_validator_state_") + require.Nil(t, err) + + privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name()) block1 := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{5, []byte{1, 2, 3}}} block2 := types.BlockID{[]byte{3, 2, 1}, types.PartSetHeader{10, []byte{3, 2, 1}}} @@ -175,9 +214,12 @@ func TestSignProposal(t *testing.T) { } func TestDifferByTimestamp(t *testing.T) { - tempFile, err := ioutil.TempFile("", "priv_validator_") + tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_") require.Nil(t, err) - privVal := GenFilePV(tempFile.Name()) + tempStateFile, err := ioutil.TempFile("", "priv_validator_state_") + require.Nil(t, err) + + privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name()) block1 := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{5, []byte{1, 2, 3}}} height, round := int64(10), 1 @@ -208,7 +250,7 @@ func TestDifferByTimestamp(t *testing.T) { { voteType := byte(types.PrevoteType) blockID := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}} - vote := newVote(privVal.Address, 0, height, round, voteType, blockID) + vote := newVote(privVal.Key.Address, 0, height, round, voteType, blockID) err := privVal.SignVote("mychainid", vote) assert.NoError(t, err, "expected no error signing vote") diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index e68ec1490..b89c0a177 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -119,8 +119,9 @@ func NewTendermint(app abci.Application) *nm.Node { config := GetConfig() logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) logger = log.NewFilter(logger, log.AllowError()) - pvFile := config.PrivValidatorFile() - pv := privval.LoadOrGenFilePV(pvFile) + pvKeyFile := config.PrivValidatorKeyFile() + pvKeyStateFile := config.PrivValidatorStateFile() + pv := privval.LoadOrGenFilePV(pvKeyFile, pvKeyStateFile) papp := proxy.NewLocalClientCreator(app) nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) if err != nil { diff --git a/scripts/wire2amino.go b/scripts/wire2amino.go index 26069b505..da23c30cc 100644 --- a/scripts/wire2amino.go +++ b/scripts/wire2amino.go @@ -48,6 +48,18 @@ type PrivVal struct { PrivKey Data `json:"priv_key"` } +type PrivValKey struct { + Address cmn.HexBytes `json:"address"` + PubKey Data `json:"pub_key"` + PrivKey Data `json:"priv_key"` +} + +type PrivValState struct { + LastHeight int64 `json:"last_height"` + LastRound int `json:"last_round"` + LastStep int8 `json:"last_step"` +} + type Data struct { Type string `json:"type"` Data cmn.HexBytes `json:"data"` @@ -72,8 +84,8 @@ func convertNodeKey(cdc *amino.Codec, jsonBytes []byte) ([]byte, error) { return bz, nil } -func convertPrivVal(cdc *amino.Codec, jsonBytes []byte) ([]byte, error) { - var privVal PrivVal +func convertPrivValKey(cdc *amino.Codec, jsonBytes []byte) ([]byte, error) { + var privVal PrivValKey err := json.Unmarshal(jsonBytes, &privVal) if err != nil { return nil, err @@ -85,13 +97,30 @@ func convertPrivVal(cdc *amino.Codec, jsonBytes []byte) ([]byte, error) { var pubKey ed25519.PubKeyEd25519 copy(pubKey[:], privVal.PubKey.Data) - privValNew := privval.FilePV{ - Address: pubKey.Address(), - PubKey: pubKey, - LastHeight: privVal.LastHeight, - LastRound: privVal.LastRound, - LastStep: privVal.LastStep, - PrivKey: privKey, + privValNew := privval.FilePVKey{ + Address: pubKey.Address(), + PubKey: pubKey, + PrivKey: privKey, + } + + bz, err := cdc.MarshalJSON(privValNew) + if err != nil { + return nil, err + } + return bz, nil +} + +func convertPrivValState(cdc *amino.Codec, jsonBytes []byte) ([]byte, error) { + var privVal PrivValState + err := json.Unmarshal(jsonBytes, &privVal) + if err != nil { + return nil, err + } + + privValNew := privval.FilePVLastSignState{ + Height: privVal.LastHeight, + Round: privVal.LastRound, + Step: privVal.LastStep, } bz, err := cdc.MarshalJSON(privValNew) @@ -165,8 +194,10 @@ func main() { switch fileName { case "node_key.json": bz, err = convertNodeKey(cdc, fileBytes) - case "priv_validator.json": - bz, err = convertPrivVal(cdc, fileBytes) + case "priv_validator_key.json": + bz, err = convertPrivValKey(cdc, fileBytes) + case "priv_validator_state.json": + bz, err = convertPrivValState(cdc, fileBytes) case "genesis.json": bz, err = convertGenesis(cdc, fileBytes) default: From e255b30c63c3e2cccfe0172a1660a6191eed2aca Mon Sep 17 00:00:00 2001 From: yutianwu Date: Sat, 17 Nov 2018 19:08:31 +0800 Subject: [PATCH 02/10] fix bugs --- cmd/tendermint/commands/testnet.go | 5 +++++ scripts/wire2amino.go | 9 --------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/cmd/tendermint/commands/testnet.go b/cmd/tendermint/commands/testnet.go index 6398f4ebd..7ec679982 100644 --- a/cmd/tendermint/commands/testnet.go +++ b/cmd/tendermint/commands/testnet.go @@ -85,6 +85,11 @@ func testnetFiles(cmd *cobra.Command, args []string) error { _ = os.RemoveAll(outputDir) return err } + err = os.MkdirAll(filepath.Join(nodeDir, "data"), nodeDirPerm) + if err != nil { + _ = os.RemoveAll(outputDir) + return err + } initFilesWithConfig(config) diff --git a/scripts/wire2amino.go b/scripts/wire2amino.go index da23c30cc..459d80e08 100644 --- a/scripts/wire2amino.go +++ b/scripts/wire2amino.go @@ -39,15 +39,6 @@ type NodeKey struct { PrivKey Data `json:"priv_key"` } -type PrivVal struct { - Address cmn.HexBytes `json:"address"` - LastHeight int64 `json:"last_height"` - LastRound int `json:"last_round"` - LastStep int8 `json:"last_step"` - PubKey Data `json:"pub_key"` - PrivKey Data `json:"priv_key"` -} - type PrivValKey struct { Address cmn.HexBytes `json:"address"` PubKey Data `json:"pub_key"` From 98128e72faa7af12d4ee02987c40bf9e1fc5ebac Mon Sep 17 00:00:00 2001 From: yutianwu Date: Sat, 24 Nov 2018 17:14:24 +0800 Subject: [PATCH 03/10] minor changes --- cmd/tendermint/commands/init.go | 13 ++++++------- consensus/common_test.go | 17 ++++++++--------- privval/priv_validator.go | 1 - 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/cmd/tendermint/commands/init.go b/cmd/tendermint/commands/init.go index 0ef1e2d24..896bee2e9 100644 --- a/cmd/tendermint/commands/init.go +++ b/cmd/tendermint/commands/init.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/spf13/cobra" - cfg "github.com/tendermint/tendermint/config" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/p2p" @@ -26,17 +25,17 @@ func initFiles(cmd *cobra.Command, args []string) error { func initFilesWithConfig(config *cfg.Config) error { // private validator - privValkeyFile := config.PrivValidatorKeyFile() + privValKeyFile := config.PrivValidatorKeyFile() privValStateFile := config.PrivValidatorStateFile() var pv *privval.FilePV - if cmn.FileExists(privValkeyFile) { - pv = privval.LoadFilePV(privValkeyFile, privValStateFile) - logger.Info("Found private validator", "keyFile", privValkeyFile, + if cmn.FileExists(privValKeyFile) { + pv = privval.LoadFilePV(privValKeyFile, privValStateFile) + logger.Info("Found private validator", "keyFile", privValKeyFile, "stateFile", privValStateFile) } else { - pv = privval.GenFilePV(privValkeyFile, privValStateFile) + pv = privval.GenFilePV(privValKeyFile, privValStateFile) pv.Save() - logger.Info("Generated private validator", "keyFile", privValkeyFile, + logger.Info("Generated private validator", "keyFile", privValKeyFile, "stateFile", privValStateFile) } diff --git a/consensus/common_test.go b/consensus/common_test.go index aa0c75511..1f6be4377 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -6,14 +6,18 @@ import ( "fmt" "io/ioutil" "os" - "path" + "path/filepath" "reflect" "sort" "sync" "testing" "time" + "github.com/go-kit/kit/log/term" + abcicli "github.com/tendermint/tendermint/abci/client" + "github.com/tendermint/tendermint/abci/example/counter" + "github.com/tendermint/tendermint/abci/example/kvstore" abci "github.com/tendermint/tendermint/abci/types" bc "github.com/tendermint/tendermint/blockchain" cfg "github.com/tendermint/tendermint/config" @@ -27,11 +31,6 @@ import ( sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" - - "github.com/tendermint/tendermint/abci/example/counter" - "github.com/tendermint/tendermint/abci/example/kvstore" - - "github.com/go-kit/kit/log/term" ) const ( @@ -282,7 +281,7 @@ func newConsensusStateWithConfigAndBlockStore(thisConfig *cfg.Config, state sm.S func loadPrivValidator(config *cfg.Config) *privval.FilePV { privValidatorKeyFile := config.PrivValidatorKeyFile() - ensureDir(path.Dir(privValidatorKeyFile), 0700) + ensureDir(filepath.Dir(privValidatorKeyFile), 0700) privValidatorStateFile := config.PrivValidatorStateFile() privValidator := privval.LoadOrGenFilePV(privValidatorKeyFile, privValidatorStateFile) privValidator.Reset() @@ -592,7 +591,7 @@ func randConsensusNet(nValidators int, testName string, tickerFunc func() Timeou for _, opt := range configOpts { opt(thisConfig) } - ensureDir(path.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal + ensureDir(filepath.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal app := appFunc() vals := types.TM2PB.ValidatorUpdates(state.Validators) app.InitChain(abci.RequestInitChain{Validators: vals}) @@ -613,7 +612,7 @@ func randConsensusNetWithPeers(nValidators, nPeers int, testName string, tickerF stateDB := dbm.NewMemDB() // each state needs its own db state, _ := sm.LoadStateFromDBOrGenesisDoc(stateDB, genDoc) thisConfig := ResetConfig(fmt.Sprintf("%s_%d", testName, i)) - ensureDir(path.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal + ensureDir(filepath.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal var privVal types.PrivValidator if i < nValidators { privVal = privVals[i] diff --git a/privval/priv_validator.go b/privval/priv_validator.go index 7d53c5689..c761ff3f0 100644 --- a/privval/priv_validator.go +++ b/privval/priv_validator.go @@ -141,7 +141,6 @@ func LoadFilePV(keyFilePath string, stateFilePath string) *FilePV { func LoadOrGenFilePV(keyFilePath string, stateFilePath string) *FilePV { var pv *FilePV if cmn.FileExists(keyFilePath) { - println(keyFilePath) pv = LoadFilePV(keyFilePath, stateFilePath) } else { pv = GenFilePV(keyFilePath, stateFilePath) From b6e44a2b3dca6898842b5a01434ebed779a4db52 Mon Sep 17 00:00:00 2001 From: yutianwu Date: Sat, 24 Nov 2018 17:22:11 +0800 Subject: [PATCH 04/10] retrig test --- privval/priv_validator.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/privval/priv_validator.go b/privval/priv_validator.go index c761ff3f0..b58776780 100644 --- a/privval/priv_validator.go +++ b/privval/priv_validator.go @@ -98,8 +98,8 @@ func GenFilePV(keyFilePath string, stateFilePath string) *FilePV { } // LoadFilePV loads a FilePV from the filePaths. The FilePV handles double -// signing prevention by persisting data to the stateFilePath. If the filePaths does -// not exist, the FilePV must be created manually and saved. +// signing prevention by persisting data to the stateFilePath. If the filePaths +// does not exist, the FilePV must be created manually and saved. func LoadFilePV(keyFilePath string, stateFilePath string) *FilePV { keyJSONBytes, err := ioutil.ReadFile(keyFilePath) if err != nil { From 6132a3ec5239f24312fa21cdb822500606d5f7f5 Mon Sep 17 00:00:00 2001 From: yutianwu Date: Sat, 24 Nov 2018 17:27:04 +0800 Subject: [PATCH 05/10] delete scripts/wire2amino.go --- scripts/wire2amino.go | 204 ------------------------------------------ 1 file changed, 204 deletions(-) delete mode 100644 scripts/wire2amino.go diff --git a/scripts/wire2amino.go b/scripts/wire2amino.go deleted file mode 100644 index 459d80e08..000000000 --- a/scripts/wire2amino.go +++ /dev/null @@ -1,204 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "time" - - "github.com/tendermint/go-amino" - "github.com/tendermint/tendermint/crypto/ed25519" - cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" - - cmn "github.com/tendermint/tendermint/libs/common" - - "github.com/tendermint/tendermint/p2p" - "github.com/tendermint/tendermint/privval" - "github.com/tendermint/tendermint/types" -) - -type GenesisValidator struct { - PubKey Data `json:"pub_key"` - Power int64 `json:"power"` - Name string `json:"name"` -} - -type Genesis struct { - GenesisTime time.Time `json:"genesis_time"` - ChainID string `json:"chain_id"` - ConsensusParams *types.ConsensusParams `json:"consensus_params,omitempty"` - Validators []GenesisValidator `json:"validators"` - AppHash cmn.HexBytes `json:"app_hash"` - AppState json.RawMessage `json:"app_state,omitempty"` - AppOptions json.RawMessage `json:"app_options,omitempty"` // DEPRECATED -} - -type NodeKey struct { - PrivKey Data `json:"priv_key"` -} - -type PrivValKey struct { - Address cmn.HexBytes `json:"address"` - PubKey Data `json:"pub_key"` - PrivKey Data `json:"priv_key"` -} - -type PrivValState struct { - LastHeight int64 `json:"last_height"` - LastRound int `json:"last_round"` - LastStep int8 `json:"last_step"` -} - -type Data struct { - Type string `json:"type"` - Data cmn.HexBytes `json:"data"` -} - -func convertNodeKey(cdc *amino.Codec, jsonBytes []byte) ([]byte, error) { - var nodeKey NodeKey - err := json.Unmarshal(jsonBytes, &nodeKey) - if err != nil { - return nil, err - } - - var privKey ed25519.PrivKeyEd25519 - copy(privKey[:], nodeKey.PrivKey.Data) - - nodeKeyNew := p2p.NodeKey{privKey} - - bz, err := cdc.MarshalJSON(nodeKeyNew) - if err != nil { - return nil, err - } - return bz, nil -} - -func convertPrivValKey(cdc *amino.Codec, jsonBytes []byte) ([]byte, error) { - var privVal PrivValKey - err := json.Unmarshal(jsonBytes, &privVal) - if err != nil { - return nil, err - } - - var privKey ed25519.PrivKeyEd25519 - copy(privKey[:], privVal.PrivKey.Data) - - var pubKey ed25519.PubKeyEd25519 - copy(pubKey[:], privVal.PubKey.Data) - - privValNew := privval.FilePVKey{ - Address: pubKey.Address(), - PubKey: pubKey, - PrivKey: privKey, - } - - bz, err := cdc.MarshalJSON(privValNew) - if err != nil { - return nil, err - } - return bz, nil -} - -func convertPrivValState(cdc *amino.Codec, jsonBytes []byte) ([]byte, error) { - var privVal PrivValState - err := json.Unmarshal(jsonBytes, &privVal) - if err != nil { - return nil, err - } - - privValNew := privval.FilePVLastSignState{ - Height: privVal.LastHeight, - Round: privVal.LastRound, - Step: privVal.LastStep, - } - - bz, err := cdc.MarshalJSON(privValNew) - if err != nil { - return nil, err - } - return bz, nil -} - -func convertGenesis(cdc *amino.Codec, jsonBytes []byte) ([]byte, error) { - var genesis Genesis - err := json.Unmarshal(jsonBytes, &genesis) - if err != nil { - return nil, err - } - - genesisNew := types.GenesisDoc{ - GenesisTime: genesis.GenesisTime, - ChainID: genesis.ChainID, - ConsensusParams: genesis.ConsensusParams, - // Validators - AppHash: genesis.AppHash, - AppState: genesis.AppState, - } - - if genesis.AppOptions != nil { - genesisNew.AppState = genesis.AppOptions - } - - for _, v := range genesis.Validators { - var pubKey ed25519.PubKeyEd25519 - copy(pubKey[:], v.PubKey.Data) - genesisNew.Validators = append( - genesisNew.Validators, - types.GenesisValidator{ - PubKey: pubKey, - Power: v.Power, - Name: v.Name, - }, - ) - - } - - bz, err := cdc.MarshalJSON(genesisNew) - if err != nil { - return nil, err - } - return bz, nil -} - -func main() { - cdc := amino.NewCodec() - cryptoAmino.RegisterAmino(cdc) - - args := os.Args[1:] - if len(args) != 1 { - fmt.Println("Please specify a file to convert") - os.Exit(1) - } - - filePath := args[0] - fileName := filepath.Base(filePath) - - fileBytes, err := ioutil.ReadFile(filePath) - if err != nil { - panic(err) - } - - var bz []byte - - switch fileName { - case "node_key.json": - bz, err = convertNodeKey(cdc, fileBytes) - case "priv_validator_key.json": - bz, err = convertPrivValKey(cdc, fileBytes) - case "priv_validator_state.json": - bz, err = convertPrivValState(cdc, fileBytes) - case "genesis.json": - bz, err = convertGenesis(cdc, fileBytes) - default: - fmt.Println("Expected file name to be in (node_key.json, priv_validator.json, genesis.json)") - os.Exit(1) - } - - if err != nil { - panic(err) - } - fmt.Println(string(bz)) - -} From bc940757ec1ccdb29b2cfff4b64196d1a628cb60 Mon Sep 17 00:00:00 2001 From: yutianwu Date: Mon, 26 Nov 2018 20:01:27 +0800 Subject: [PATCH 06/10] fix test --- privval/priv_validator_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/privval/priv_validator_test.go b/privval/priv_validator_test.go index 394656926..bd8387b32 100644 --- a/privval/priv_validator_test.go +++ b/privval/priv_validator_test.go @@ -62,11 +62,11 @@ func TestUnmarshalValidatorState(t *testing.T) { assert, require := assert.New(t), require.New(t) // create some fixed values - serialized := fmt.Sprintf(`{ + serialized := `{ "height": "1", "round": "1", "step": 1 - }`) + }` val := FilePVLastSignState{} err := cdc.UnmarshalJSON([]byte(serialized), &val) From 897b9f56a6ef6fe7be4f05b8a33962db2e059635 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 17 Dec 2018 22:40:50 -0500 Subject: [PATCH 07/10] fixes from review --- privval/priv_validator.go | 34 +++++++++++++++++----------------- privval/priv_validator_test.go | 4 ++-- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/privval/priv_validator.go b/privval/priv_validator.go index b58776780..016a264ef 100644 --- a/privval/priv_validator.go +++ b/privval/priv_validator.go @@ -45,7 +45,7 @@ type FilePV struct { LastSignState FilePVLastSignState } -// FilePVKey stores the immutable part of PrivValidator +// FilePVKey stores the immutable part of PrivValidator. type FilePVKey struct { Address types.Address `json:"address"` PubKey crypto.PubKey `json:"pub_key"` @@ -54,7 +54,7 @@ type FilePVKey struct { filePath string } -// FilePVState stores the mutable part of PrivValidator +// FilePVLastSignState stores the mutable part of PrivValidator. type FilePVLastSignState struct { Height int64 `json:"height"` Round int `json:"round"` @@ -99,7 +99,7 @@ func GenFilePV(keyFilePath string, stateFilePath string) *FilePV { // LoadFilePV loads a FilePV from the filePaths. The FilePV handles double // signing prevention by persisting data to the stateFilePath. If the filePaths -// does not exist, the FilePV must be created manually and saved. +// do not exist, the FilePV must be created manually and saved. func LoadFilePV(keyFilePath string, stateFilePath string) *FilePV { keyJSONBytes, err := ioutil.ReadFile(keyFilePath) if err != nil { @@ -128,12 +128,10 @@ func LoadFilePV(keyFilePath string, stateFilePath string) *FilePV { pvState.filePath = stateFilePath - pv := &FilePV{} - - pv.Key = pvKey - pv.LastSignState = pvState - - return pv + return &FilePV{ + Key: pvKey, + LastSignState: pvState, + } } // LoadOrGenFilePV loads a FilePV from the given filePaths @@ -226,21 +224,23 @@ func (pv *FilePV) SignProposal(chainID string, proposal *types.Proposal) error { // returns error if HRS regression or no LastSignBytes. returns true if HRS is unchanged func (pv *FilePV) checkHRS(height int64, round int, step int8) (bool, error) { - if pv.LastSignState.Height > height { + lss := pv.LastSignState + + if lss.Height > height { return false, errors.New("Height regression") } - if pv.LastSignState.Height == height { - if pv.LastSignState.Round > round { + if lss.Height == height { + if lss.Round > round { return false, errors.New("Round regression") } - if pv.LastSignState.Round == round { - if pv.LastSignState.Step > step { + if lss.Round == round { + if lss.Step > step { return false, errors.New("Step regression") - } else if pv.LastSignState.Step == step { - if pv.LastSignState.SignBytes != nil { - if pv.LastSignState.Signature == nil { + } else if lss.Step == step { + if lss.SignBytes != nil { + if lss.Signature == nil { panic("pv: LastSignature is nil but LastSignBytes is not!") } return true, nil diff --git a/privval/priv_validator_test.go b/privval/priv_validator_test.go index bd8387b32..f9ec49568 100644 --- a/privval/priv_validator_test.go +++ b/privval/priv_validator_test.go @@ -64,8 +64,8 @@ func TestUnmarshalValidatorState(t *testing.T) { // create some fixed values serialized := `{ "height": "1", - "round": "1", - "step": 1 + "round": "1", + "step": 1 }` val := FilePVLastSignState{} From b5544a4560accea5af2b578227130b63eadb2645 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 17 Dec 2018 22:46:14 -0500 Subject: [PATCH 08/10] privval: remove mtx --- privval/priv_validator.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/privval/priv_validator.go b/privval/priv_validator.go index 016a264ef..6d1f80f21 100644 --- a/privval/priv_validator.go +++ b/privval/priv_validator.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io/ioutil" - "sync" "time" "github.com/tendermint/tendermint/crypto" @@ -63,7 +62,6 @@ type FilePVLastSignState struct { SignBytes cmn.HexBytes `json:"signbytes,omitempty"` filePath string - mtx sync.Mutex } // GetAddress returns the address of the validator. @@ -150,9 +148,6 @@ func LoadOrGenFilePV(keyFilePath string, stateFilePath string) *FilePV { // Save persists the FilePV to disk. func (pv *FilePV) Save() { pv.saveKey() - - pv.LastSignState.mtx.Lock() - defer pv.LastSignState.mtx.Unlock() pv.saveState() } @@ -203,8 +198,6 @@ func (pv *FilePV) Reset() { // SignVote signs a canonical representation of the vote, along with the // chainID. Implements PrivValidator. func (pv *FilePV) SignVote(chainID string, vote *types.Vote) error { - pv.LastSignState.mtx.Lock() - defer pv.LastSignState.mtx.Unlock() if err := pv.signVote(chainID, vote); err != nil { return fmt.Errorf("Error signing vote: %v", err) } @@ -214,8 +207,6 @@ func (pv *FilePV) SignVote(chainID string, vote *types.Vote) error { // SignProposal signs a canonical representation of the proposal, along with // the chainID. Implements PrivValidator. func (pv *FilePV) SignProposal(chainID string, proposal *types.Proposal) error { - pv.LastSignState.mtx.Lock() - defer pv.LastSignState.mtx.Unlock() if err := pv.signProposal(chainID, proposal); err != nil { return fmt.Errorf("Error signing proposal: %v", err) } From d382f064eddee29771ff7ac7f93305dcb8def7f3 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 17 Dec 2018 23:04:23 -0500 Subject: [PATCH 09/10] rearrange priv_validator.go --- privval/priv_validator.go | 241 +++++++++++++++++++++----------------- 1 file changed, 131 insertions(+), 110 deletions(-) diff --git a/privval/priv_validator.go b/privval/priv_validator.go index 6d1f80f21..b4d4ba6e5 100644 --- a/privval/priv_validator.go +++ b/privval/priv_validator.go @@ -34,15 +34,7 @@ func voteToStep(vote *types.Vote) int8 { } } -// FilePV implements PrivValidator using data persisted to disk -// to prevent double signing. -// NOTE: the directories containing pv.Key.filePath and pv.LastSignState.filePath must already exist. -// It includes the LastSignature and LastSignBytes so we don't lose the signature -// if the process crashes after signing but before the resulting consensus message is processed. -type FilePV struct { - Key FilePVKey - LastSignState FilePVLastSignState -} +//------------------------------------------------------------------------------- // FilePVKey stores the immutable part of PrivValidator. type FilePVKey struct { @@ -53,6 +45,26 @@ type FilePVKey struct { filePath string } +// Save persists the FilePVKey to its filePath. +func (pvKey FilePVKey) Save() { + outFile := pvKey.filePath + if outFile == "" { + panic("Cannot save PrivValidator key: filePath not set") + } + + jsonBytes, err := cdc.MarshalJSONIndent(pvKey, "", " ") + if err != nil { + panic(err) + } + err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600) + if err != nil { + panic(err) + } + +} + +//------------------------------------------------------------------------------- + // FilePVLastSignState stores the mutable part of PrivValidator. type FilePVLastSignState struct { Height int64 `json:"height"` @@ -64,16 +76,67 @@ type FilePVLastSignState struct { filePath string } -// GetAddress returns the address of the validator. -// Implements PrivValidator. -func (pv *FilePV) GetAddress() types.Address { - return pv.Key.Address +// CheckHRS checks the given height, round, step (HRS) against that of the +// FilePVLastSignState. It returns an error if the arguments constitute a regression, +// or if they match but the SignBytes are empty. +// The returned boolean indicates whether the last Signature should be reused - +// it returns true if the HRS matches the arguments and the SignBytes are not empty (indicating +// we have already signed for this HRS, and can reuse the existing signature). +// It panics if the HRS matches the arguments, there's a SignBytes, but no Signature. +func (lss *FilePVLastSignState) CheckHRS(height int64, round int, step int8) (bool, error) { + + if lss.Height > height { + return false, errors.New("Height regression") + } + + if lss.Height == height { + if lss.Round > round { + return false, errors.New("Round regression") + } + + if lss.Round == round { + if lss.Step > step { + return false, errors.New("Step regression") + } else if lss.Step == step { + if lss.SignBytes != nil { + if lss.Signature == nil { + panic("pv: Signature is nil but SignBytes is not!") + } + return true, nil + } + return false, errors.New("No SignBytes found") + } + } + } + return false, nil } -// GetPubKey returns the public key of the validator. -// Implements PrivValidator. -func (pv *FilePV) GetPubKey() crypto.PubKey { - return pv.Key.PubKey +// Save persists the FilePvLastSignState to its filePath. +func (lss *FilePVLastSignState) Save() { + outFile := lss.filePath + if outFile == "" { + panic("Cannot save FilePVLastSignState: filePath not set") + } + jsonBytes, err := cdc.MarshalJSONIndent(lss, "", " ") + if err != nil { + panic(err) + } + err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600) + if err != nil { + panic(err) + } +} + +//------------------------------------------------------------------------------- + +// FilePV implements PrivValidator using data persisted to disk +// to prevent double signing. +// NOTE: the directories containing pv.Key.filePath and pv.LastSignState.filePath must already exist. +// It includes the LastSignature and LastSignBytes so we don't lose the signature +// if the process crashes after signing but before the resulting consensus message is processed. +type FilePV struct { + Key FilePVKey + LastSignState FilePVLastSignState } // GenFilePV generates a new validator with randomly generated private key @@ -145,54 +208,16 @@ func LoadOrGenFilePV(keyFilePath string, stateFilePath string) *FilePV { return pv } -// Save persists the FilePV to disk. -func (pv *FilePV) Save() { - pv.saveKey() - pv.saveState() +// GetAddress returns the address of the validator. +// Implements PrivValidator. +func (pv *FilePV) GetAddress() types.Address { + return pv.Key.Address } -func (pv *FilePV) saveKey() { - outFile := pv.Key.filePath - if outFile == "" { - panic("Cannot save PrivValidator key: filePath not set") - } - - jsonBytes, err := cdc.MarshalJSONIndent(pv.Key, "", " ") - if err != nil { - panic(err) - } - err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600) - if err != nil { - panic(err) - } - -} - -func (pv *FilePV) saveState() { - outFile := pv.LastSignState.filePath - if outFile == "" { - panic("Cannot save PrivValidator state: filePath not set") - } - jsonBytes, err := cdc.MarshalJSONIndent(pv.LastSignState, "", " ") - if err != nil { - panic(err) - } - err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600) - if err != nil { - panic(err) - } -} - -// Reset resets all fields in the FilePV. -// NOTE: Unsafe! -func (pv *FilePV) Reset() { - var sig []byte - pv.LastSignState.Height = 0 - pv.LastSignState.Round = 0 - pv.LastSignState.Step = 0 - pv.LastSignState.Signature = sig - pv.LastSignState.SignBytes = nil - pv.Save() +// GetPubKey returns the public key of the validator. +// Implements PrivValidator. +func (pv *FilePV) GetPubKey() crypto.PubKey { + return pv.Key.PubKey } // SignVote signs a canonical representation of the vote, along with the @@ -213,59 +238,57 @@ func (pv *FilePV) SignProposal(chainID string, proposal *types.Proposal) error { return nil } -// returns error if HRS regression or no LastSignBytes. returns true if HRS is unchanged -func (pv *FilePV) checkHRS(height int64, round int, step int8) (bool, error) { - lss := pv.LastSignState - - if lss.Height > height { - return false, errors.New("Height regression") - } - - if lss.Height == height { - if lss.Round > round { - return false, errors.New("Round regression") - } - - if lss.Round == round { - if lss.Step > step { - return false, errors.New("Step regression") - } else if lss.Step == step { - if lss.SignBytes != nil { - if lss.Signature == nil { - panic("pv: LastSignature is nil but LastSignBytes is not!") - } - return true, nil - } - return false, errors.New("No LastSignature found") - } - } - } - return false, nil +// Save persists the FilePV to disk. +func (pv *FilePV) Save() { + pv.Key.Save() + pv.LastSignState.Save() } +// Reset resets all fields in the FilePV. +// NOTE: Unsafe! +func (pv *FilePV) Reset() { + var sig []byte + pv.LastSignState.Height = 0 + pv.LastSignState.Round = 0 + pv.LastSignState.Step = 0 + pv.LastSignState.Signature = sig + pv.LastSignState.SignBytes = nil + pv.Save() +} + +// String returns a string representation of the FilePV. +func (pv *FilePV) String() string { + return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", pv.GetAddress(), pv.LastSignState.Height, pv.LastSignState.Round, pv.LastSignState.Step) +} + +//------------------------------------------------------------------------------------ + // signVote checks if the vote is good to sign and sets the vote signature. // It may need to set the timestamp as well if the vote is otherwise the same as // a previously signed vote (ie. we crashed after signing but before the vote hit the WAL). func (pv *FilePV) signVote(chainID string, vote *types.Vote) error { height, round, step := vote.Height, vote.Round, voteToStep(vote) - signBytes := vote.SignBytes(chainID) - sameHRS, err := pv.checkHRS(height, round, step) + lss := pv.LastSignState + + sameHRS, err := lss.CheckHRS(height, round, step) if err != nil { return err } + signBytes := vote.SignBytes(chainID) + // 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, pv.LastSignState.SignBytes) { - vote.Signature = pv.LastSignState.Signature - } else if timestamp, ok := checkVotesOnlyDifferByTimestamp(pv.LastSignState.SignBytes, signBytes); ok { + if bytes.Equal(signBytes, lss.SignBytes) { + vote.Signature = lss.Signature + } else if timestamp, ok := checkVotesOnlyDifferByTimestamp(lss.SignBytes, signBytes); ok { vote.Timestamp = timestamp - vote.Signature = pv.LastSignState.Signature + vote.Signature = lss.Signature } else { err = fmt.Errorf("Conflicting data") } @@ -287,24 +310,27 @@ func (pv *FilePV) signVote(chainID string, vote *types.Vote) error { // a previously signed proposal ie. we crashed after signing but before the proposal hit the WAL). func (pv *FilePV) signProposal(chainID string, proposal *types.Proposal) error { height, round, step := proposal.Height, proposal.Round, stepPropose - signBytes := proposal.SignBytes(chainID) - sameHRS, err := pv.checkHRS(height, round, step) + lss := pv.LastSignState + + sameHRS, err := lss.CheckHRS(height, round, step) if err != nil { return err } + signBytes := proposal.SignBytes(chainID) + // 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, pv.LastSignState.SignBytes) { - proposal.Signature = pv.LastSignState.Signature - } else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(pv.LastSignState.SignBytes, signBytes); ok { + if bytes.Equal(signBytes, lss.SignBytes) { + proposal.Signature = lss.Signature + } else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(lss.SignBytes, signBytes); ok { proposal.Timestamp = timestamp - proposal.Signature = pv.LastSignState.Signature + proposal.Signature = lss.Signature } else { err = fmt.Errorf("Conflicting data") } @@ -330,15 +356,10 @@ func (pv *FilePV) saveSigned(height int64, round int, step int8, pv.LastSignState.Step = step pv.LastSignState.Signature = sig pv.LastSignState.SignBytes = signBytes - pv.saveState() + pv.LastSignState.Save() } -// String returns a string representation of the FilePV. -func (pv *FilePV) String() string { - return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", pv.GetAddress(), pv.LastSignState.Height, pv.LastSignState.Round, pv.LastSignState.Step) -} - -//------------------------------------- +//----------------------------------------------------------------------------------------- // returns the timestamp from the lastSignBytes. // returns true if the only difference in the votes is their timestamp. From 928d9dad99ee40e4974c32d0c476ddac77560561 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 17 Dec 2018 23:26:33 -0500 Subject: [PATCH 10/10] upgrade path --- config/config.go | 11 +++++ node/node.go | 21 ++++++++- privval/old_priv_validator.go | 80 +++++++++++++++++++++++++++++++++++ privval/priv_validator.go | 6 +-- scripts/privValUpgrade.go | 41 ++++++++++++++++++ 5 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 privval/old_priv_validator.go create mode 100644 scripts/privValUpgrade.go diff --git a/config/config.go b/config/config.go index 85d55b556..4bdd87236 100644 --- a/config/config.go +++ b/config/config.go @@ -50,6 +50,11 @@ var ( defaultAddrBookPath = filepath.Join(defaultConfigDir, defaultAddrBookName) ) +var ( + oldPrivVal = "priv_validator.json" + oldPrivValPath = filepath.Join(defaultConfigDir, oldPrivVal) +) + // Config defines the top level configuration for a Tendermint node type Config struct { // Top level options use an anonymous struct @@ -236,6 +241,12 @@ func (cfg BaseConfig) PrivValidatorStateFile() string { return rootify(cfg.PrivValidatorState, cfg.RootDir) } +// OldPrivValidatorFile returns the full path of the priv_validator.json from pre v0.28.0. +// TODO: eventually remove. +func (cfg BaseConfig) OldPrivValidatorFile() string { + return rootify(oldPrivValPath, cfg.RootDir) +} + // NodeKeyFile returns the full path to the node_key.json file func (cfg BaseConfig) NodeKeyFile() string { return rootify(cfg.NodeKey, cfg.RootDir) diff --git a/node/node.go b/node/node.go index f03e5bab5..daa7d7c40 100644 --- a/node/node.go +++ b/node/node.go @@ -7,6 +7,7 @@ import ( "net" "net/http" _ "net/http/pprof" + "os" "strings" "time" @@ -86,8 +87,26 @@ func DefaultNewNode(config *cfg.Config, logger log.Logger) (*Node, error) { if err != nil { return nil, err } + + // Convert old PrivValidator if it exists. + oldPrivVal := config.OldPrivValidatorFile() + newPrivValKey := config.PrivValidatorKeyFile() + newPrivValState := config.PrivValidatorStateFile() + if _, err := os.Stat(oldPrivVal); !os.IsNotExist(err) { + oldPV, err := privval.LoadOldFilePV(oldPrivVal) + if err != nil { + return nil, fmt.Errorf("Error reading OldPrivValidator from %v: %v\n", oldPrivVal, err) + } + logger.Info("Upgrading PrivValidator file", + "old", oldPrivVal, + "newKey", newPrivValKey, + "newState", newPrivValState, + ) + oldPV.Upgrade(newPrivValKey, newPrivValState) + } + return NewNode(config, - privval.LoadOrGenFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()), + privval.LoadOrGenFilePV(newPrivValKey, newPrivValState), nodeKey, proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()), DefaultGenesisDocProviderFunc(config), diff --git a/privval/old_priv_validator.go b/privval/old_priv_validator.go new file mode 100644 index 000000000..ec72c1834 --- /dev/null +++ b/privval/old_priv_validator.go @@ -0,0 +1,80 @@ +package privval + +import ( + "io/ioutil" + "os" + + "github.com/tendermint/tendermint/crypto" + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/types" +) + +// OldFilePV is the old version of the FilePV, pre v0.28.0. +type OldFilePV struct { + Address types.Address `json:"address"` + PubKey crypto.PubKey `json:"pub_key"` + LastHeight int64 `json:"last_height"` + LastRound int `json:"last_round"` + LastStep int8 `json:"last_step"` + LastSignature []byte `json:"last_signature,omitempty"` + LastSignBytes cmn.HexBytes `json:"last_signbytes,omitempty"` + PrivKey crypto.PrivKey `json:"priv_key"` + + filePath string +} + +// LoadOldFilePV loads an OldFilePV from the filePath. +func LoadOldFilePV(filePath string) (*OldFilePV, error) { + pvJSONBytes, err := ioutil.ReadFile(filePath) + if err != nil { + return nil, err + } + pv := &OldFilePV{} + err = cdc.UnmarshalJSON(pvJSONBytes, &pv) + if err != nil { + return nil, err + } + + // overwrite pubkey and address for convenience + pv.PubKey = pv.PrivKey.PubKey() + pv.Address = pv.PubKey.Address() + + pv.filePath = filePath + return pv, nil +} + +// Upgrade convets the OldFilePV to the new FilePV, separating the immutable and mutable components, +// and persisting them to the keyFilePath and stateFilePath, respectively. +// It renames the original file by adding ".bak". +func (oldFilePV *OldFilePV) Upgrade(keyFilePath, stateFilePath string) *FilePV { + privKey := oldFilePV.PrivKey + pvKey := FilePVKey{ + PrivKey: privKey, + PubKey: privKey.PubKey(), + Address: privKey.PubKey().Address(), + filePath: keyFilePath, + } + + pvState := FilePVLastSignState{ + Height: oldFilePV.LastHeight, + Round: oldFilePV.LastRound, + Step: oldFilePV.LastStep, + Signature: oldFilePV.LastSignature, + SignBytes: oldFilePV.LastSignBytes, + filePath: stateFilePath, + } + + // Save the new PV files + pv := &FilePV{ + Key: pvKey, + LastSignState: pvState, + } + pv.Save() + + // Rename the old PV file + err := os.Rename(oldFilePV.filePath, oldFilePV.filePath+".bak") + if err != nil { + panic(err) + } + return pv +} diff --git a/privval/priv_validator.go b/privval/priv_validator.go index b4d4ba6e5..6473f10ee 100644 --- a/privval/priv_validator.go +++ b/privval/priv_validator.go @@ -141,7 +141,7 @@ type FilePV struct { // GenFilePV generates a new validator with randomly generated private key // and sets the filePaths, but does not call Save(). -func GenFilePV(keyFilePath string, stateFilePath string) *FilePV { +func GenFilePV(keyFilePath, stateFilePath string) *FilePV { privKey := ed25519.GenPrivKey() return &FilePV{ @@ -161,7 +161,7 @@ func GenFilePV(keyFilePath string, stateFilePath string) *FilePV { // LoadFilePV loads a FilePV from the filePaths. The FilePV handles double // signing prevention by persisting data to the stateFilePath. If the filePaths // do not exist, the FilePV must be created manually and saved. -func LoadFilePV(keyFilePath string, stateFilePath string) *FilePV { +func LoadFilePV(keyFilePath, stateFilePath string) *FilePV { keyJSONBytes, err := ioutil.ReadFile(keyFilePath) if err != nil { cmn.Exit(err.Error()) @@ -197,7 +197,7 @@ func LoadFilePV(keyFilePath string, stateFilePath string) *FilePV { // LoadOrGenFilePV loads a FilePV from the given filePaths // or else generates a new one and saves it to the filePaths. -func LoadOrGenFilePV(keyFilePath string, stateFilePath string) *FilePV { +func LoadOrGenFilePV(keyFilePath, stateFilePath string) *FilePV { var pv *FilePV if cmn.FileExists(keyFilePath) { pv = LoadFilePV(keyFilePath, stateFilePath) diff --git a/scripts/privValUpgrade.go b/scripts/privValUpgrade.go new file mode 100644 index 000000000..72ce505ee --- /dev/null +++ b/scripts/privValUpgrade.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" + "os" + + "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/privval" +) + +var ( + logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) +) + +func main() { + args := os.Args[1:] + if len(args) != 3 { + fmt.Println("Expected three args: ") + fmt.Println("Eg. ~/.tendermint/config/priv_validator.json ~/.tendermint/config/priv_validator_key.json ~/.tendermint/data/priv_validator_state.json") + os.Exit(1) + } + err := loadAndUpgrade(args[0], args[1], args[2]) + if err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func loadAndUpgrade(oldPVPath, newPVKeyPath, newPVStatePath string) error { + oldPV, err := privval.LoadOldFilePV(oldPVPath) + if err != nil { + return fmt.Errorf("Error reading OldPrivValidator from %v: %v\n", oldPVPath, err) + } + logger.Info("Upgrading PrivValidator file", + "old", oldPVPath, + "newKey", newPVKeyPath, + "newState", newPVStatePath, + ) + oldPV.Upgrade(newPVKeyPath, newPVStatePath) + return nil +}