diff --git a/.circleci/config.yml b/.circleci/config.yml index dcc0e289b..5669384c6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2 defaults: &defaults working_directory: /go/src/github.com/tendermint/tendermint docker: - - image: circleci/golang:1.11.3 + - image: circleci/golang:1.11.4 environment: GOBIN: /tmp/workspace/bin diff --git a/CHANGELOG.md b/CHANGELOG.md index 0397ebdb5..46e9cb374 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v0.27.4 + +*December 21st, 2018* + +### BUG FIXES: + +- [mempool] [\#3036](https://github.com/tendermint/tendermint/issues/3036) Fix + LRU cache by popping the least recently used item when the cache is full, + not the most recently used one! + ## v0.27.3 *December 16th, 2018* diff --git a/cmd/tendermint/commands/reset_priv_validator.go b/cmd/tendermint/commands/reset_priv_validator.go index 1669e6737..122c2a725 100644 --- a/cmd/tendermint/commands/reset_priv_validator.go +++ b/cmd/tendermint/commands/reset_priv_validator.go @@ -5,6 +5,7 @@ import ( "github.com/spf13/cobra" + cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/privval" ) @@ -37,21 +38,23 @@ func resetPrivValidator(cmd *cobra.Command, args []string) { resetFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile(), logger) } -// ResetAll removes the privValidator and address book files plus all data. +// ResetAll removes address book files plus all data, and resets the privValdiator data. // Exported so other CLI tools can use it. 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) } else { logger.Error("Error removing all blockchain history", "dir", dbDir, "err", err) } + // recreate the dbDir since the privVal state needs to live there + cmn.EnsureDir(dbDir, 0700) + resetFilePV(privValKeyFile, privValStateFile, logger) } func resetFilePV(privValKeyFile, privValStateFile string, logger log.Logger) { if _, err := os.Stat(privValKeyFile); err == nil { - pv := privval.LoadFilePV(privValKeyFile, privValStateFile) + pv := privval.LoadFilePVEmptyState(privValKeyFile, privValStateFile) pv.Reset() logger.Info("Reset private validator file to genesis state", "keyFile", privValKeyFile, "stateFile", privValStateFile) diff --git a/privval/old_priv_validator_test.go b/privval/old_priv_validator_test.go new file mode 100644 index 000000000..46391a3fe --- /dev/null +++ b/privval/old_priv_validator_test.go @@ -0,0 +1,77 @@ +package privval_test + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/tendermint/tendermint/privval" +) + +const oldPrivvalContent = `{ + "address": "1D8089FAFDFAE4A637F3D616E17B92905FA2D91D", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "r3Yg2AhDZ745CNTpavsGU+mRZ8WpRXqoJuyqjN8mJq0=" + }, + "last_height": "5", + "last_round": "0", + "last_step": 3, + "last_signature": "CTr7b9ZQlrJJf+12rPl5t/YSCUc/KqV7jQogCfFJA24e7hof69X6OMT7eFLVQHyodPjD/QTA298XHV5ejxInDQ==", + "last_signbytes": "750802110500000000000000220B08B398F3E00510F48DA6402A480A20FC258973076512999C3E6839A22E9FBDB1B77CF993E8A9955412A41A59D4CAD312240A20C971B286ACB8AAA6FCA0365EB0A660B189EDC08B46B5AF2995DEFA51A28D215B10013211746573742D636861696E2D533245415533", + "priv_key": { + "type": "tendermint/PrivKeyEd25519", + "value": "7MwvTGEWWjsYwjn2IpRb+GYsWi9nnFsw8jPLLY1UtP6vdiDYCENnvjkI1Olq+wZT6ZFnxalFeqgm7KqM3yYmrQ==" + } +}` + +func TestLoadAndUpgrade(t *testing.T) { + + oldFilePath := initTmpOldFile(t) + defer os.Remove(oldFilePath) + newStateFile, err := ioutil.TempFile("", "priv_validator_state*.json") + defer os.Remove(newStateFile.Name()) + require.NoError(t, err) + newKeyFile, err := ioutil.TempFile("", "priv_validator_key*.json") + defer os.Remove(newKeyFile.Name()) + require.NoError(t, err) + + oldPV, err := privval.LoadOldFilePV(oldFilePath) + assert.NoError(t, err) + newPV := oldPV.Upgrade(newKeyFile.Name(), newStateFile.Name()) + + assertEqualPV(t, oldPV, newPV) + assert.NoError(t, err) + upgradedPV := privval.LoadFilePV(newKeyFile.Name(), newStateFile.Name()) + assertEqualPV(t, oldPV, upgradedPV) + oldPV, err = privval.LoadOldFilePV(oldFilePath + ".bak") + require.NoError(t, err) + assertEqualPV(t, oldPV, upgradedPV) +} + +func assertEqualPV(t *testing.T, oldPV *privval.OldFilePV, newPV *privval.FilePV) { + assert.Equal(t, oldPV.Address, newPV.Key.Address) + assert.Equal(t, oldPV.Address, newPV.GetAddress()) + assert.Equal(t, oldPV.PubKey, newPV.Key.PubKey) + assert.Equal(t, oldPV.PubKey, newPV.GetPubKey()) + assert.Equal(t, oldPV.PrivKey, newPV.Key.PrivKey) + + assert.Equal(t, oldPV.LastHeight, newPV.LastSignState.Height) + assert.Equal(t, oldPV.LastRound, newPV.LastSignState.Round) + assert.Equal(t, oldPV.LastSignature, newPV.LastSignState.Signature) + assert.Equal(t, oldPV.LastSignBytes, newPV.LastSignState.SignBytes) + assert.Equal(t, oldPV.LastStep, newPV.LastSignState.Step) +} + +func initTmpOldFile(t *testing.T) string { + tmpfile, err := ioutil.TempFile("", "priv_validator_*.json") + require.NoError(t, err) + t.Logf("created test file %s", tmpfile.Name()) + _, err = tmpfile.WriteString(oldPrivvalContent) + require.NoError(t, err) + + return tmpfile.Name() +} diff --git a/privval/priv_validator.go b/privval/priv_validator.go index 6473f10ee..1ee5b4d8e 100644 --- a/privval/priv_validator.go +++ b/privval/priv_validator.go @@ -159,9 +159,20 @@ func GenFilePV(keyFilePath, 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. +// signing prevention by persisting data to the stateFilePath. If either file path +// does not exist, the program will exit. func LoadFilePV(keyFilePath, stateFilePath string) *FilePV { + return loadFilePV(keyFilePath, stateFilePath, true) +} + +// LoadFilePVEmptyState loads a FilePV from the given keyFilePath, with an empty LastSignState. +// If the keyFilePath does not exist, the program will exit. +func LoadFilePVEmptyState(keyFilePath, stateFilePath string) *FilePV { + return loadFilePV(keyFilePath, stateFilePath, false) +} + +// If loadState is true, we load from the stateFilePath. Otherwise, we use an empty LastSignState. +func loadFilePV(keyFilePath, stateFilePath string, loadState bool) *FilePV { keyJSONBytes, err := ioutil.ReadFile(keyFilePath) if err != nil { cmn.Exit(err.Error()) @@ -177,14 +188,16 @@ func LoadFilePV(keyFilePath, stateFilePath string) *FilePV { 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)) + if loadState { + stateJSONBytes, err := ioutil.ReadFile(stateFilePath) + if err != nil { + cmn.Exit(err.Error()) + } + 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 diff --git a/privval/priv_validator_test.go b/privval/priv_validator_test.go index f9ec49568..06d75a809 100644 --- a/privval/priv_validator_test.go +++ b/privval/priv_validator_test.go @@ -35,6 +35,34 @@ func TestGenLoadValidator(t *testing.T) { assert.Equal(height, privVal.LastSignState.Height, "expected privval.LastHeight to have been saved") } +func TestResetValidator(t *testing.T) { + tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_") + require.Nil(t, err) + tempStateFile, err := ioutil.TempFile("", "priv_validator_state_") + require.Nil(t, err) + + privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name()) + emptyState := FilePVLastSignState{filePath: tempStateFile.Name()} + + // new priv val has empty state + assert.Equal(t, privVal.LastSignState, emptyState) + + // test vote + height, round := int64(10), 1 + voteType := byte(types.PrevoteType) + blockID := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}} + vote := newVote(privVal.Key.Address, 0, height, round, voteType, blockID) + err = privVal.SignVote("mychainid", vote) + assert.NoError(t, err, "expected no error signing vote") + + // priv val after signing is not same as empty + assert.NotEqual(t, privVal.LastSignState, emptyState) + + // priv val after reset is same as empty + privVal.Reset() + assert.Equal(t, privVal.LastSignState, emptyState) +} + func TestLoadOrGenValidator(t *testing.T) { assert := assert.New(t) diff --git a/scripts/privValUpgrade_test.go b/scripts/privValUpgrade_test.go new file mode 100644 index 000000000..bac4d315f --- /dev/null +++ b/scripts/privValUpgrade_test.go @@ -0,0 +1,121 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/privval" +) + +const oldPrivvalContent = `{ + "address": "1D8089FAFDFAE4A637F3D616E17B92905FA2D91D", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "r3Yg2AhDZ745CNTpavsGU+mRZ8WpRXqoJuyqjN8mJq0=" + }, + "last_height": "5", + "last_round": "0", + "last_step": 3, + "last_signature": "CTr7b9ZQlrJJf+12rPl5t/YSCUc/KqV7jQogCfFJA24e7hof69X6OMT7eFLVQHyodPjD/QTA298XHV5ejxInDQ==", + "last_signbytes": "750802110500000000000000220B08B398F3E00510F48DA6402A480A20FC258973076512999C3E6839A22E9FBDB1B77CF993E8A9955412A41A59D4CAD312240A20C971B286ACB8AAA6FCA0365EB0A660B189EDC08B46B5AF2995DEFA51A28D215B10013211746573742D636861696E2D533245415533", + "priv_key": { + "type": "tendermint/PrivKeyEd25519", + "value": "7MwvTGEWWjsYwjn2IpRb+GYsWi9nnFsw8jPLLY1UtP6vdiDYCENnvjkI1Olq+wZT6ZFnxalFeqgm7KqM3yYmrQ==" + } +}` + +func TestLoadAndUpgrade(t *testing.T) { + + oldFilePath := initTmpOldFile(t) + defer os.Remove(oldFilePath) + newStateFile, err := ioutil.TempFile("", "priv_validator_state*.json") + defer os.Remove(newStateFile.Name()) + require.NoError(t, err) + newKeyFile, err := ioutil.TempFile("", "priv_validator_key*.json") + defer os.Remove(newKeyFile.Name()) + require.NoError(t, err) + emptyOldFile, err := ioutil.TempFile("", "priv_validator_empty*.json") + require.NoError(t, err) + defer os.Remove(emptyOldFile.Name()) + + type args struct { + oldPVPath string + newPVKeyPath string + newPVStatePath string + } + tests := []struct { + name string + args args + wantErr bool + wantPanic bool + }{ + {"successful upgrade", + args{oldPVPath: oldFilePath, newPVKeyPath: newKeyFile.Name(), newPVStatePath: newStateFile.Name()}, + false, false, + }, + {"unsuccessful upgrade: empty old privval file", + args{oldPVPath: emptyOldFile.Name(), newPVKeyPath: newKeyFile.Name(), newPVStatePath: newStateFile.Name()}, + true, false, + }, + {"unsuccessful upgrade: invalid new paths (1/3)", + args{oldPVPath: oldFilePath, newPVKeyPath: "", newPVStatePath: newStateFile.Name()}, + false, true, + }, + {"unsuccessful upgrade: invalid new paths (2/3)", + args{oldPVPath: oldFilePath, newPVKeyPath: newKeyFile.Name(), newPVStatePath: ""}, + false, true, + }, + {"unsuccessful upgrade: invalid new paths (3/3)", + args{oldPVPath: oldFilePath, newPVKeyPath: "", newPVStatePath: ""}, + false, true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // need to re-write the file everytime because upgrading renames it + err := ioutil.WriteFile(oldFilePath, []byte(oldPrivvalContent), 0600) + require.NoError(t, err) + if tt.wantPanic { + require.Panics(t, func() { loadAndUpgrade(tt.args.oldPVPath, tt.args.newPVKeyPath, tt.args.newPVStatePath) }) + } else { + err = loadAndUpgrade(tt.args.oldPVPath, tt.args.newPVKeyPath, tt.args.newPVStatePath) + if tt.wantErr { + assert.Error(t, err) + fmt.Println("ERR", err) + } else { + assert.NoError(t, err) + upgradedPV := privval.LoadFilePV(tt.args.newPVKeyPath, tt.args.newPVStatePath) + oldPV, err := privval.LoadOldFilePV(tt.args.oldPVPath + ".bak") + require.NoError(t, err) + + assert.Equal(t, oldPV.Address, upgradedPV.Key.Address) + assert.Equal(t, oldPV.Address, upgradedPV.GetAddress()) + assert.Equal(t, oldPV.PubKey, upgradedPV.Key.PubKey) + assert.Equal(t, oldPV.PubKey, upgradedPV.GetPubKey()) + assert.Equal(t, oldPV.PrivKey, upgradedPV.Key.PrivKey) + + assert.Equal(t, oldPV.LastHeight, upgradedPV.LastSignState.Height) + assert.Equal(t, oldPV.LastRound, upgradedPV.LastSignState.Round) + assert.Equal(t, oldPV.LastSignature, upgradedPV.LastSignState.Signature) + assert.Equal(t, oldPV.LastSignBytes, upgradedPV.LastSignState.SignBytes) + assert.Equal(t, oldPV.LastStep, upgradedPV.LastSignState.Step) + + } + } + }) + } +} + +func initTmpOldFile(t *testing.T) string { + tmpfile, err := ioutil.TempFile("", "priv_validator_*.json") + require.NoError(t, err) + t.Logf("created test file %s", tmpfile.Name()) + _, err = tmpfile.WriteString(oldPrivvalContent) + require.NoError(t, err) + + return tmpfile.Name() +} diff --git a/version/version.go b/version/version.go index ace1b41d2..3cbdab02f 100644 --- a/version/version.go +++ b/version/version.go @@ -18,7 +18,7 @@ const ( // TMCoreSemVer is the current version of Tendermint Core. // It's the Semantic Version of the software. // Must be a string because scripts like dist.sh read this file. - TMCoreSemVer = "0.27.3" + TMCoreSemVer = "0.27.4" // ABCISemVer is the semantic version of the ABCI library ABCISemVer = "0.15.0"