diff --git a/.golangci.yml b/.golangci.yml index 8e5c493b49..d109df4a6e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,6 +3,7 @@ # timeout: 5m linters: + disable-all: true enable: - bodyclose - deadcode @@ -30,8 +31,6 @@ linters: - unconvert - unused - misspell - disable: - - errcheck issues: exclude-rules: diff --git a/.mergify.yml b/.mergify.yml index 6e153d510b..fbf9d7015a 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -1,7 +1,7 @@ pull_request_rules: - name: automerge to master with label automerge and branch protection passing conditions: - - "#approved-reviews-by>=1" + - "#approved-reviews-by>1" - base=master - label=automerge actions: diff --git a/CHANGELOG.md b/CHANGELOG.md index be120952fa..972723cbf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ balances or a single balance by denom when the `denom` query parameter is present. * (client) [\#5640](https://github.com/cosmos/cosmos-sdk/pull/5640) The rest server endpoint `/swagger-ui/` is replaced by ´/´. * (x/auth) [\#5702](https://github.com/cosmos/cosmos-sdk/pull/5702) The `x/auth` querier route has changed from `"acc"` to `"auth"`. +* (store/types) [\#5730](https://github.com/cosmos/cosmos-sdk/pull/5730) store.types.Cp() is removed in favour of types.CopyBytes(). ### API Breaking Changes @@ -60,6 +61,7 @@ and provided directly the IAVL store. * (modules) [\#5572](https://github.com/cosmos/cosmos-sdk/pull/5572) Move account balance logic and APIs from `x/auth` to `x/bank`. * (types) [\#5533](https://github.com/cosmos/cosmos-sdk/pull/5533) Refactored `AppModuleBasic` and `AppModuleGenesis` to now accept a `codec.JSONMarshaler` for modular serialization of genesis state. +* (crypto/keys) [\#5735](https://github.com/cosmos/cosmos-sdk/pull/5735) Keyring's Update() function is now no-op. ### Features @@ -148,6 +150,8 @@ Buffers for state serialization instead of Amino. * (rest) [\#5648](https://github.com/cosmos/cosmos-sdk/pull/5648) Enhance /txs usability: * Add `tx.minheight` key to filter transaction with an inclusive minimum block height * Add `tx.maxheight` key to filter transaction with an inclusive maximum block height +* (server) [\#5709](https://github.com/cosmos/cosmos-sdk/pull/5709) There are two new flags for pruning, `--pruning-keep-every` +and `--pruning-snapshot-every` as an alternative to `--pruning`. They allow to fine tune the strategy for pruning the state. ## [v0.38.1] - 2020-02-11 diff --git a/Makefile b/Makefile index 68d7a359ab..a634915bfe 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,7 @@ build: go.sum mocks: $(MOCKS_DIR) mockgen -source=x/auth/types/account_retriever.go -package mocks -destination tests/mocks/account_retriever.go + mockgen -package mocks -destination tests/mocks/tendermint_tm_db_DB.go github.com/tendermint/tm-db DB .PHONY: mocks $(MOCKS_DIR): diff --git a/client/keys/add_ledger_test.go b/client/keys/add_ledger_test.go index 0b8efe8e66..d69dae252a 100644 --- a/client/keys/add_ledger_test.go +++ b/client/keys/add_ledger_test.go @@ -39,7 +39,7 @@ func Test_runAddCmdLedgerWithCustomCoinType(t *testing.T) { // Prepare a keybase kbHome, kbCleanUp := tests.NewTestCaseDir(t) require.NotNil(t, kbHome) - defer kbCleanUp() + t.Cleanup(kbCleanUp) viper.Set(flags.FlagHome, kbHome) viper.Set(flags.FlagUseLedger, true) @@ -54,9 +54,9 @@ func Test_runAddCmdLedgerWithCustomCoinType(t *testing.T) { kb, err := keys.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn) require.NoError(t, err) require.NotNil(t, kb) - defer func() { + t.Cleanup(func() { kb.Delete("keyname1", "", false) - }() + }) mockIn.Reset("test1234\n") if runningUnattended { mockIn.Reset("test1234\ntest1234\n") @@ -87,7 +87,7 @@ func Test_runAddCmdLedger(t *testing.T) { // Prepare a keybase kbHome, kbCleanUp := tests.NewTestCaseDir(t) require.NotNil(t, kbHome) - defer kbCleanUp() + t.Cleanup(kbCleanUp) viper.Set(flags.FlagHome, kbHome) viper.Set(flags.FlagUseLedger, true) @@ -101,9 +101,9 @@ func Test_runAddCmdLedger(t *testing.T) { kb, err := keys.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn) require.NoError(t, err) require.NotNil(t, kb) - defer func() { + t.Cleanup(func() { kb.Delete("keyname1", "", false) - }() + }) mockIn.Reset("test1234\n") if runningUnattended { mockIn.Reset("test1234\ntest1234\n") diff --git a/client/keys/add_test.go b/client/keys/add_test.go index b1ebee3316..5d2eb8ddf0 100644 --- a/client/keys/add_test.go +++ b/client/keys/add_test.go @@ -23,7 +23,7 @@ func Test_runAddCmdBasic(t *testing.T) { kbHome, kbCleanUp := tests.NewTestCaseDir(t) assert.NotNil(t, kbHome) - defer kbCleanUp() + t.Cleanup(kbCleanUp) viper.Set(flags.FlagHome, kbHome) viper.Set(cli.OutputFlag, OutputFormatText) @@ -33,10 +33,10 @@ func Test_runAddCmdBasic(t *testing.T) { mockIn.Reset("y\n") kb, err := keys.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), kbHome, mockIn) require.NoError(t, err) - defer func() { + t.Cleanup(func() { kb.Delete("keyname1", "", false) kb.Delete("keyname2", "", false) - }() + }) } assert.NoError(t, runAddCmd(cmd, []string{"keyname1"})) diff --git a/client/keys/delete_test.go b/client/keys/delete_test.go index b2df8684df..5c4c1a5b70 100644 --- a/client/keys/delete_test.go +++ b/client/keys/delete_test.go @@ -29,15 +29,15 @@ func Test_runDeleteCmd(t *testing.T) { if !runningUnattended { kb, err := keys.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn) require.NoError(t, err) - defer func() { + t.Cleanup(func() { kb.Delete("runDeleteCmd_Key1", "", false) kb.Delete("runDeleteCmd_Key2", "", false) - }() + }) } // Now add a temporary keybase kbHome, cleanUp := tests.NewTestCaseDir(t) - defer cleanUp() + t.Cleanup(cleanUp) viper.Set(flags.FlagHome, kbHome) // Now diff --git a/client/keys/export_test.go b/client/keys/export_test.go index 4d0236cdd0..dff9d344cc 100644 --- a/client/keys/export_test.go +++ b/client/keys/export_test.go @@ -19,16 +19,16 @@ func Test_runExportCmd(t *testing.T) { // Now add a temporary keybase kbHome, cleanUp := tests.NewTestCaseDir(t) - defer cleanUp() + t.Cleanup(cleanUp) viper.Set(flags.FlagHome, kbHome) // create a key kb, err := keys.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn) require.NoError(t, err) if !runningUnattended { - defer func() { + t.Cleanup(func() { kb.Delete("keyname1", "", false) - }() + }) } if runningUnattended { diff --git a/client/keys/import_test.go b/client/keys/import_test.go index faac935179..3ccb606ee9 100644 --- a/client/keys/import_test.go +++ b/client/keys/import_test.go @@ -21,15 +21,15 @@ func Test_runImportCmd(t *testing.T) { // Now add a temporary keybase kbHome, cleanUp := tests.NewTestCaseDir(t) - defer cleanUp() + t.Cleanup(cleanUp) viper.Set(flags.FlagHome, kbHome) if !runningUnattended { kb, err := keys.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn) require.NoError(t, err) - defer func() { + t.Cleanup(func() { kb.Delete("keyname1", "", false) - }() + }) } keyfile := filepath.Join(kbHome, "key.asc") diff --git a/client/keys/list_test.go b/client/keys/list_test.go index ac8ca97d0f..94997e86c0 100644 --- a/client/keys/list_test.go +++ b/client/keys/list_test.go @@ -24,11 +24,11 @@ func Test_runListCmd(t *testing.T) { // Prepare some keybases kbHome1, cleanUp1 := tests.NewTestCaseDir(t) - defer cleanUp1() + t.Cleanup(cleanUp1) // Do nothing, leave home1 empty kbHome2, cleanUp2 := tests.NewTestCaseDir(t) - defer cleanUp2() + t.Cleanup(cleanUp2) viper.Set(flags.FlagHome, kbHome2) mockIn, _, _ := tests.ApplyMockIO(cmdBasic) @@ -41,9 +41,9 @@ func Test_runListCmd(t *testing.T) { _, err = kb.CreateAccount("something", tests.TestMnemonic, "", "", "", keys.Secp256k1) require.NoError(t, err) - defer func() { + t.Cleanup(func() { kb.Delete("something", "", false) - }() + }) testData := []struct { name string kbDir string diff --git a/client/keys/migrate_test.go b/client/keys/migrate_test.go index 0ad9226d30..aec085ecc1 100644 --- a/client/keys/migrate_test.go +++ b/client/keys/migrate_test.go @@ -19,14 +19,13 @@ func Test_runMigrateCmd(t *testing.T) { kbHome, kbCleanUp := tests.NewTestCaseDir(t) assert.NotNil(t, kbHome) - defer kbCleanUp() + t.Cleanup(kbCleanUp) viper.Set(flags.FlagHome, kbHome) viper.Set(cli.OutputFlag, OutputFormatText) mockIn.Reset("test1234\ntest1234\n") - err := runAddCmd(cmd, []string{"keyname1"}) - assert.NoError(t, err) + assert.NoError(t, runAddCmd(cmd, []string{"keyname1"})) viper.Set(flags.FlagDryRun, true) cmd = MigrateCommand() diff --git a/client/keys/mnemonic_test.go b/client/keys/mnemonic_test.go index 7097ef0a3c..a3e84e7182 100644 --- a/client/keys/mnemonic_test.go +++ b/client/keys/mnemonic_test.go @@ -12,8 +12,7 @@ import ( func Test_RunMnemonicCmdNormal(t *testing.T) { cmdBasic := MnemonicKeyCommand() - err := runMnemonicCmd(cmdBasic, []string{}) - require.NoError(t, err) + require.NoError(t, runMnemonicCmd(cmdBasic, []string{})) } func Test_RunMnemonicCmdUser(t *testing.T) { @@ -37,18 +36,15 @@ func Test_RunMnemonicCmdUser(t *testing.T) { // Now provide "good" entropy :) fakeEntropy := strings.Repeat(":)", 40) + "\ny\n" // entropy + accept count mockIn.Reset(fakeEntropy) - err = runMnemonicCmd(cmdUser, []string{}) - require.NoError(t, err) + require.NoError(t, runMnemonicCmd(cmdUser, []string{})) // Now provide "good" entropy but no answer fakeEntropy = strings.Repeat(":)", 40) + "\n" // entropy + accept count mockIn.Reset(fakeEntropy) - err = runMnemonicCmd(cmdUser, []string{}) - require.Error(t, err) + require.Error(t, runMnemonicCmd(cmdUser, []string{})) // Now provide "good" entropy but say no fakeEntropy = strings.Repeat(":)", 40) + "\nn\n" // entropy + accept count mockIn.Reset(fakeEntropy) - err = runMnemonicCmd(cmdUser, []string{}) - require.NoError(t, err) + require.NoError(t, runMnemonicCmd(cmdUser, []string{})) } diff --git a/client/keys/show.go b/client/keys/show.go index 539fb111e5..c959ff1577 100644 --- a/client/keys/show.go +++ b/client/keys/show.go @@ -136,7 +136,7 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) { return nil } - return crypto.LedgerShowAddress(*hdpath, info.GetPubKey()) + return crypto.LedgerShowAddress(*hdpath, info.GetPubKey(), sdk.GetConfig().GetBech32AccountAddrPrefix()) } return nil diff --git a/client/keys/show_test.go b/client/keys/show_test.go index ec84eea49e..80bb865059 100644 --- a/client/keys/show_test.go +++ b/client/keys/show_test.go @@ -44,17 +44,17 @@ func Test_runShowCmd(t *testing.T) { // Prepare a key base // Now add a temporary keybase kbHome, cleanUp := tests.NewTestCaseDir(t) - defer cleanUp() + t.Cleanup(cleanUp) viper.Set(flags.FlagHome, kbHome) fakeKeyName1 := "runShowCmd_Key1" fakeKeyName2 := "runShowCmd_Key2" kb, err := keys.NewKeyring(sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), viper.GetString(flags.FlagHome), mockIn) require.NoError(t, err) - defer func() { + t.Cleanup(func() { kb.Delete("runShowCmd_Key1", "", false) kb.Delete("runShowCmd_Key2", "", false) - }() + }) if runningUnattended { mockIn.Reset("testpass1\ntestpass1\n") } diff --git a/client/keys/types_test.go b/client/keys/types_test.go new file mode 100644 index 0000000000..24428a5b92 --- /dev/null +++ b/client/keys/types_test.go @@ -0,0 +1,29 @@ +package keys_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/client/keys" +) + +func TestConstructors(t *testing.T) { + require.Equal(t, keys.AddNewKey{ + Name: "name", + Password: "password", + Mnemonic: "mnemonic", + Account: 1, + Index: 1, + }, keys.NewAddNewKey("name", "password", "mnemonic", 1, 1)) + + require.Equal(t, keys.RecoverKey{ + Password: "password", + Mnemonic: "mnemonic", + Account: 1, + Index: 1, + }, keys.NewRecoverKey("password", "mnemonic", 1, 1)) + + require.Equal(t, keys.UpdateKeyReq{OldPassword: "old", NewPassword: "new"}, keys.NewUpdateKeyReq("old", "new")) + require.Equal(t, keys.DeleteKeyReq{Password: "password"}, keys.NewDeleteKeyReq("password")) +} diff --git a/client/keys/update_test.go b/client/keys/update_test.go index 351cedd578..d763e9e764 100644 --- a/client/keys/update_test.go +++ b/client/keys/update_test.go @@ -12,8 +12,7 @@ import ( ) func Test_updateKeyCommand(t *testing.T) { - cmd := UpdateKeyCommand() - assert.NotNil(t, cmd) + assert.NotNil(t, UpdateKeyCommand()) // No flags or defaults to validate } @@ -34,7 +33,7 @@ func Test_runUpdateCmd(t *testing.T) { // Prepare a key base // Now add a temporary keybase kbHome, cleanUp1 := tests.NewTestCaseDir(t) - defer cleanUp1() + t.Cleanup(cleanUp1) viper.Set(flags.FlagHome, kbHome) kb, err := NewKeyBaseFromDir(viper.GetString(flags.FlagHome)) diff --git a/crypto/keys/keybase.go b/crypto/keys/keybase.go index 9d95a5ccda..b55c7b68b5 100644 --- a/crypto/keys/keybase.go +++ b/crypto/keys/keybase.go @@ -212,7 +212,7 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub t } case ledgerInfo: - return kb.base.SignWithLedger(info, msg) + return SignWithLedger(info, msg) case offlineInfo, multiInfo: return kb.base.DecodeSignature(info, msg) diff --git a/crypto/keys/keybase_base.go b/crypto/keys/keybase_base.go index 3003b08828..47c7e60d72 100644 --- a/crypto/keys/keybase_base.go +++ b/crypto/keys/keybase_base.go @@ -104,24 +104,6 @@ func SecpPrivKeyGen(bz []byte) tmcrypto.PrivKey { return secp256k1.PrivKeySecp256k1(bzArr) } -// SignWithLedger signs a binary message with the ledger device referenced by an Info object -// and returns the signed bytes and the public key. It returns an error if the device could -// not be queried or it returned an error. -func (kb baseKeybase) SignWithLedger(info Info, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) { - i := info.(ledgerInfo) - priv, err := crypto.NewPrivKeyLedgerSecp256k1Unsafe(i.Path) - if err != nil { - return - } - - sig, err = priv.Sign(msg) - if err != nil { - return nil, nil, err - } - - return sig, priv.PubKey(), nil -} - // DecodeSignature decodes a an length-prefixed binary signature from standard input // and return it as a byte slice. func (kb baseKeybase) DecodeSignature(info Info, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) { @@ -296,3 +278,30 @@ func IsSupportedAlgorithm(supported []SigningAlgo, algo SigningAlgo) bool { } return false } + +// SignWithLedger signs a binary message with the ledger device referenced by an Info object +// and returns the signed bytes and the public key. It returns an error if the device could +// not be queried or it returned an error. +func SignWithLedger(info Info, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) { + switch info.(type) { + case *ledgerInfo, ledgerInfo: + default: + return nil, nil, errors.New("not a ledger object") + } + path, err := info.GetPath() + if err != nil { + return + } + + priv, err := crypto.NewPrivKeyLedgerSecp256k1Unsafe(*path) + if err != nil { + return + } + + sig, err = priv.Sign(msg) + if err != nil { + return nil, nil, err + } + + return sig, priv.PubKey(), nil +} diff --git a/crypto/keys/keybase_test.go b/crypto/keys/keybase_test.go index 0c2396afbf..cbd212a755 100644 --- a/crypto/keys/keybase_test.go +++ b/crypto/keys/keybase_test.go @@ -2,7 +2,9 @@ package keys import ( + "errors" "fmt" + "io" "testing" "github.com/stretchr/testify/assert" @@ -278,7 +280,7 @@ func TestSignVerify(t *testing.T) { // Now try to sign data with a secret-less key _, _, err = cstore.Sign(n3, p3, d3) - require.NotNil(t, err) + require.True(t, errors.Is(io.EOF, err)) } func assertPassword(t *testing.T, cstore Keybase, name, pass, badpass string) { diff --git a/crypto/keys/keyerror/errors_test.go b/crypto/keys/keyerror/errors_test.go new file mode 100644 index 0000000000..80aaf9a69b --- /dev/null +++ b/crypto/keys/keyerror/errors_test.go @@ -0,0 +1,24 @@ +package keyerror_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror" +) + +func TestErrors(t *testing.T) { + err := keyerror.NewErrKeyNotFound("test") + require.True(t, keyerror.IsErrKeyNotFound(err)) + require.Equal(t, "Key test not found", err.Error()) + require.False(t, keyerror.IsErrKeyNotFound(errors.New("test"))) + require.False(t, keyerror.IsErrKeyNotFound(nil)) + + err = keyerror.NewErrWrongPassword() + require.True(t, keyerror.IsErrWrongPassword(err)) + require.Equal(t, "invalid account password", err.Error()) + require.False(t, keyerror.IsErrWrongPassword(errors.New("test"))) + require.False(t, keyerror.IsErrWrongPassword(nil)) +} diff --git a/crypto/keys/keyring.go b/crypto/keys/keyring.go index 3e79c796bd..d21e557435 100644 --- a/crypto/keys/keyring.go +++ b/crypto/keys/keyring.go @@ -7,7 +7,6 @@ import ( "io/ioutil" "os" "path/filepath" - "reflect" "sort" "strings" @@ -218,7 +217,7 @@ func (kb keyringKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, } case ledgerInfo: - return kb.base.SignWithLedger(info, msg) + return SignWithLedger(info, msg) case offlineInfo, multiInfo: return kb.base.DecodeSignature(info, msg) @@ -419,29 +418,7 @@ func (kb keyringKeybase) Delete(name, _ string, _ bool) error { // The oldpass must be the current passphrase used for encryption, getNewpass is // a function to get the passphrase to permanently replace the current passphrase. func (kb keyringKeybase) Update(name, oldpass string, getNewpass func() (string, error)) error { - info, err := kb.Get(name) - if err != nil { - return err - } - - switch linfo := info.(type) { - case localInfo: - key, _, err := mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass) - if err != nil { - return err - } - - newpass, err := getNewpass() - if err != nil { - return err - } - - kb.writeLocalKey(name, key, newpass, linfo.GetAlgo()) - return nil - - default: - return fmt.Errorf("locally stored key required; received: %v", reflect.TypeOf(info).String()) - } + return errors.New("unsupported operation") } // SupportedAlgos returns a list of supported signing algorithms. diff --git a/crypto/keys/keyring_test.go b/crypto/keys/keyring_test.go index 066f438e03..dd179a0c12 100644 --- a/crypto/keys/keyring_test.go +++ b/crypto/keys/keyring_test.go @@ -2,6 +2,7 @@ package keys import ( + "bytes" "testing" "github.com/stretchr/testify/assert" @@ -16,7 +17,7 @@ import ( func TestLazyKeyManagementKeyRing(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) kb, err := NewKeyring("keybasename", "test", dir, nil) require.NoError(t, err) @@ -94,13 +95,51 @@ func TestLazyKeyManagementKeyRing(t *testing.T) { require.Equal(t, 1, len(keyS)) // addr cache gets nuked - and test skip flag - err = kb.Delete(n2, "", true) + require.NoError(t, kb.Delete(n2, "", true)) + + require.NotPanics(t, kb.CloseDB) +} + +// TestSignVerify does some detailed checks on how we sign and validate +// signatures +func TestLazySignVerifyKeyRingWithLedger(t *testing.T) { + dir, cleanup := tests.NewTestCaseDir(t) + t.Cleanup(cleanup) + kb, err := NewKeyring("keybasename", "test", dir, nil) require.NoError(t, err) + + i1, err := kb.CreateLedger("key", Secp256k1, "cosmos", 0, 0) + if err != nil { + require.Equal(t, "ledger nano S: support for ledger devices is not available in this executable", err.Error()) + t.Skip("ledger nano S: support for ledger devices is not available in this executable") + return + } + require.Equal(t, "key", i1.GetName()) + + p1 := "1234" + d1 := []byte("my first message") + s1, pub1, err := kb.Sign("key", p1, d1) + require.NoError(t, err) + + s2, pub2, err := SignWithLedger(i1, d1) + require.NoError(t, err) + + require.Equal(t, i1.GetPubKey(), pub1) + require.Equal(t, i1.GetPubKey(), pub2) + require.True(t, pub1.VerifyBytes(d1, s1)) + require.True(t, i1.GetPubKey().VerifyBytes(d1, s1)) + require.True(t, bytes.Equal(s1, s2)) + + localInfo, _, err := kb.CreateMnemonic("test", English, p1, Secp256k1) + require.NoError(t, err) + _, _, err = SignWithLedger(localInfo, d1) + require.Error(t, err) + require.Equal(t, "not a ledger object", err.Error()) } func TestLazySignVerifyKeyRing(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) kb, err := NewKeyring("keybasename", "test", dir, nil) require.NoError(t, err) algo := Secp256k1 @@ -176,7 +215,7 @@ func TestLazySignVerifyKeyRing(t *testing.T) { func TestLazyExportImportKeyRing(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) kb, err := NewKeyring("keybasename", "test", dir, nil) require.NoError(t, err) @@ -205,7 +244,7 @@ func TestLazyExportImportKeyRing(t *testing.T) { func TestLazyExportImportPubKeyKeyRing(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) kb, err := NewKeyring("keybasename", "test", dir, nil) require.NoError(t, err) algo := Secp256k1 @@ -246,7 +285,7 @@ func TestLazyExportImportPubKeyKeyRing(t *testing.T) { func TestLazyExportPrivateKeyObjectKeyRing(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) kb, err := NewKeyring("keybasename", "test", dir, nil) require.NoError(t, err) @@ -262,7 +301,7 @@ func TestLazyExportPrivateKeyObjectKeyRing(t *testing.T) { func TestLazyAdvancedKeyManagementKeyRing(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) kb, err := NewKeyring("keybasename", "test", dir, nil) require.NoError(t, err) @@ -296,7 +335,7 @@ func TestLazyAdvancedKeyManagementKeyRing(t *testing.T) { func TestLazySeedPhraseKeyRing(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) kb, err := NewKeyring("keybasename", "test", dir, nil) require.NoError(t, err) @@ -325,3 +364,50 @@ func TestLazySeedPhraseKeyRing(t *testing.T) { require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address()) require.Equal(t, info.GetPubKey(), newInfo.GetPubKey()) } + +func TestKeyringKeybaseExportImportPrivKey(t *testing.T) { + dir, cleanup := tests.NewTestCaseDir(t) + t.Cleanup(cleanup) + kb, err := NewKeyring("keybasename", "test", dir, nil) + require.NoError(t, err) + _, _, err = kb.CreateMnemonic("john", English, "password", Secp256k1) + require.NoError(t, err) + + // no error, password is irrelevant, keystr cointains ASCII armored private key + keystr, err := kb.ExportPrivKey("john", "wrongpassword", "password") + require.NoError(t, err) + require.NotEmpty(t, keystr) + + // try import the key - wrong password + err = kb.ImportPrivKey("john2", keystr, "somepassword") + require.Equal(t, "failed to decrypt private key: ciphertext decryption failed", err.Error()) + + // try import the key with the correct password + require.NoError(t, kb.ImportPrivKey("john2", keystr, "password")) + + // overwrite is not allowed + err = kb.ImportPrivKey("john2", keystr, "password") + require.Equal(t, "cannot overwrite key: john2", err.Error()) + + // try export non existing key + _, err = kb.ExportPrivKey("john3", "wrongpassword", "password") + require.Equal(t, "The specified item could not be found in the keyring", err.Error()) +} + +func TestKeyringKeybaseUpdate(t *testing.T) { + dir, cleanup := tests.NewTestCaseDir(t) + t.Cleanup(cleanup) + kb, err := NewKeyring("keybasename", "test", dir, nil) + require.NoError(t, err) + require.Equal(t, "unsupported operation", kb.Update("john", "oldpassword", + func() (string, error) { return "", nil }).Error()) +} + +func TestSupportedAlgos(t *testing.T) { + dir, cleanup := tests.NewTestCaseDir(t) + t.Cleanup(cleanup) + kb, err := NewKeyring("keybasename", "test", dir, nil) + require.NoError(t, err) + require.Equal(t, []SigningAlgo([]SigningAlgo{"secp256k1"}), kb.SupportedAlgos()) + require.Equal(t, []SigningAlgo([]SigningAlgo{"secp256k1"}), kb.SupportedAlgosLedger()) +} diff --git a/crypto/keys/lazy_keybase_test.go b/crypto/keys/lazy_keybase_test.go index 19d5445297..bd5b742acd 100644 --- a/crypto/keys/lazy_keybase_test.go +++ b/crypto/keys/lazy_keybase_test.go @@ -18,7 +18,7 @@ import ( func TestNew(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) kb := New("keybasename", dir) lazykb, ok := kb.(lazyKeybase) require.True(t, ok) @@ -28,7 +28,7 @@ func TestNew(t *testing.T) { func TestLazyKeyManagement(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) kb := New("keybasename", dir) algo := Secp256k1 @@ -111,7 +111,7 @@ func TestLazyKeyManagement(t *testing.T) { func TestLazySignVerify(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) kb := New("keybasename", dir) algo := Secp256k1 @@ -186,7 +186,7 @@ func TestLazySignVerify(t *testing.T) { func TestLazyExportImport(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) kb := New("keybasename", dir) info, _, err := kb.CreateMnemonic("john", English, "secretcpw", Secp256k1) @@ -214,7 +214,7 @@ func TestLazyExportImport(t *testing.T) { func TestLazyExportImportPrivKey(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) kb := New("keybasename", dir) info, _, err := kb.CreateMnemonic("john", English, "secretcpw", Secp256k1) @@ -243,7 +243,7 @@ func TestLazyExportImportPrivKey(t *testing.T) { func TestLazyExportImportPubKey(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) kb := New("keybasename", dir) algo := Secp256k1 @@ -283,7 +283,7 @@ func TestLazyExportImportPubKey(t *testing.T) { func TestLazyExportPrivateKeyObject(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) kb := New("keybasename", dir) info, _, err := kb.CreateMnemonic("john", English, "secretcpw", Secp256k1) @@ -300,7 +300,7 @@ func TestLazyExportPrivateKeyObject(t *testing.T) { func TestLazyAdvancedKeyManagement(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) kb := New("keybasename", dir) algo := Secp256k1 @@ -348,7 +348,7 @@ func TestLazyAdvancedKeyManagement(t *testing.T) { // TestSeedPhrase verifies restoring from a seed phrase func TestLazySeedPhrase(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) kb := New("keybasename", dir) algo := Secp256k1 @@ -401,13 +401,13 @@ func (key testPub) Equals(other crypto.PubKey) bool { return true } func TestKeygenOverride(t *testing.T) { dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) // Save existing codec and reset after test cryptoCdc := CryptoCdc - defer func() { + t.Cleanup(func() { CryptoCdc = cryptoCdc - }() + }) // Setup testCdc encoding and decoding new key type testCdc = codec.New() diff --git a/crypto/keys/mintkey/mintkey.go b/crypto/keys/mintkey/mintkey.go index 8b45884983..7ce836d68b 100644 --- a/crypto/keys/mintkey/mintkey.go +++ b/crypto/keys/mintkey/mintkey.go @@ -11,9 +11,8 @@ import ( cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" "github.com/tendermint/tendermint/crypto/xsalsa20symmetric" - tmos "github.com/tendermint/tendermint/libs/os" - "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror" + "github.com/cosmos/cosmos-sdk/types/errors" ) const ( @@ -134,7 +133,7 @@ func encryptPrivKey(privKey crypto.PrivKey, passphrase string) (saltBytes []byte saltBytes = crypto.CRandBytes(16) key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter) if err != nil { - tmos.Exit("Error generating bcrypt key from passphrase: " + err.Error()) + panic(errors.Wrap(err, "error generating bcrypt key from passphrase")) } key = crypto.Sha256(key) // get 32 bytes privKeyBytes := privKey.Bytes() @@ -151,7 +150,7 @@ func UnarmorDecryptPrivKey(armorStr string, passphrase string) (privKey crypto.P return privKey, "", fmt.Errorf("unrecognized armor type: %v", blockType) } if header["kdf"] != "bcrypt" { - return privKey, "", fmt.Errorf("unrecognized KDF type: %v", header["KDF"]) + return privKey, "", fmt.Errorf("unrecognized KDF type: %v", header["kdf"]) } if header["salt"] == "" { return privKey, "", fmt.Errorf("missing salt bytes") @@ -171,7 +170,7 @@ func UnarmorDecryptPrivKey(armorStr string, passphrase string) (privKey crypto.P func decryptPrivKey(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) { key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter) if err != nil { - tmos.Exit("error generating bcrypt key from passphrase: " + err.Error()) + return privKey, errors.Wrap(err, "error generating bcrypt key from passphrase") } key = crypto.Sha256(key) // Get 32 bytes privKeyBytes, err := xsalsa20symmetric.DecryptSymmetric(encBytes, key) diff --git a/crypto/keys/mintkey/mintkey_test.go b/crypto/keys/mintkey/mintkey_test.go index ef5f77d337..79c158344d 100644 --- a/crypto/keys/mintkey/mintkey_test.go +++ b/crypto/keys/mintkey/mintkey_test.go @@ -1,11 +1,19 @@ package mintkey_test import ( + "bytes" + "errors" + "fmt" + "io" "testing" "github.com/stretchr/testify/require" + "github.com/tendermint/crypto/bcrypt" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/armor" cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" "github.com/tendermint/tendermint/crypto/secp256k1" + "github.com/tendermint/tendermint/crypto/xsalsa20symmetric" "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/crypto/keys/mintkey" @@ -13,13 +21,48 @@ import ( func TestArmorUnarmorPrivKey(t *testing.T) { priv := secp256k1.GenPrivKey() - armor := mintkey.EncryptArmorPrivKey(priv, "passphrase", "") - _, _, err := mintkey.UnarmorDecryptPrivKey(armor, "wrongpassphrase") + armored := mintkey.EncryptArmorPrivKey(priv, "passphrase", "") + _, _, err := mintkey.UnarmorDecryptPrivKey(armored, "wrongpassphrase") require.Error(t, err) - decrypted, algo, err := mintkey.UnarmorDecryptPrivKey(armor, "passphrase") + decrypted, algo, err := mintkey.UnarmorDecryptPrivKey(armored, "passphrase") require.NoError(t, err) require.Equal(t, string(keys.Secp256k1), algo) require.True(t, priv.Equals(decrypted)) + + // empty string + decrypted, algo, err = mintkey.UnarmorDecryptPrivKey("", "passphrase") + require.Error(t, err) + require.True(t, errors.Is(io.EOF, err)) + require.Nil(t, decrypted) + require.Empty(t, algo) + + // wrong key type + armored = mintkey.ArmorPubKeyBytes(priv.PubKey().Bytes(), "") + decrypted, algo, err = mintkey.UnarmorDecryptPrivKey(armored, "passphrase") + require.Error(t, err) + require.Contains(t, err.Error(), "unrecognized armor type") + + // armor key manually + encryptPrivKeyFn := func(privKey crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte) { + saltBytes = crypto.CRandBytes(16) + key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), mintkey.BcryptSecurityParameter) + require.NoError(t, err) + key = crypto.Sha256(key) // get 32 bytes + privKeyBytes := privKey.Bytes() + return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key) + } + saltBytes, encBytes := encryptPrivKeyFn(priv, "passphrase") + + // wrong kdf header + headerWrongKdf := map[string]string{ + "kdf": "wrong", + "salt": fmt.Sprintf("%X", saltBytes), + "type": "secp256k", + } + armored = armor.EncodeArmor("TENDERMINT PRIVATE KEY", headerWrongKdf, encBytes) + _, _, err = mintkey.UnarmorDecryptPrivKey(armored, "passphrase") + require.Error(t, err) + require.Equal(t, "unrecognized KDF type: wrong", err.Error()) } func TestArmorUnarmorPubKey(t *testing.T) { @@ -29,11 +72,66 @@ func TestArmorUnarmorPubKey(t *testing.T) { // Add keys and see they return in alphabetical order info, _, err := cstore.CreateMnemonic("Bob", keys.English, "passphrase", keys.Secp256k1) require.NoError(t, err) - armor := mintkey.ArmorPubKeyBytes(info.GetPubKey().Bytes(), "") - pubBytes, algo, err := mintkey.UnarmorPubKeyBytes(armor) + armored := mintkey.ArmorPubKeyBytes(info.GetPubKey().Bytes(), "") + pubBytes, algo, err := mintkey.UnarmorPubKeyBytes(armored) require.NoError(t, err) pub, err := cryptoAmino.PubKeyFromBytes(pubBytes) require.NoError(t, err) require.Equal(t, string(keys.Secp256k1), algo) require.True(t, pub.Equals(info.GetPubKey())) + + armored = mintkey.ArmorPubKeyBytes(info.GetPubKey().Bytes(), "unknown") + pubBytes, algo, err = mintkey.UnarmorPubKeyBytes(armored) + require.NoError(t, err) + pub, err = cryptoAmino.PubKeyFromBytes(pubBytes) + require.NoError(t, err) + require.Equal(t, "unknown", algo) + require.True(t, pub.Equals(info.GetPubKey())) + + // armor pubkey manually + header := map[string]string{ + "version": "0.0.0", + "type": "unknown", + } + armored = armor.EncodeArmor("TENDERMINT PUBLIC KEY", header, pubBytes) + _, algo, err = mintkey.UnarmorPubKeyBytes(armored) + require.NoError(t, err) + // return secp256k1 if version is 0.0.0 + require.Equal(t, "secp256k1", algo) + + // missing version header + header = map[string]string{ + "type": "unknown", + } + armored = armor.EncodeArmor("TENDERMINT PUBLIC KEY", header, pubBytes) + bz, algo, err := mintkey.UnarmorPubKeyBytes(armored) + require.Nil(t, bz) + require.Empty(t, algo) + require.Error(t, err) + require.Contains(t, err.Error(), "unrecognized version") +} + +func TestArmorInfoBytes(t *testing.T) { + bs := []byte("test") + armoredString := mintkey.ArmorInfoBytes(bs) + unarmoredBytes, err := mintkey.UnarmorInfoBytes(armoredString) + require.NoError(t, err) + require.True(t, bytes.Equal(bs, unarmoredBytes)) +} + +func TestUnarmorInfoBytesErrors(t *testing.T) { + unarmoredBytes, err := mintkey.UnarmorInfoBytes("") + require.Error(t, err) + require.True(t, errors.Is(io.EOF, err)) + require.Nil(t, unarmoredBytes) + + header := map[string]string{ + "type": "Info", + "version": "0.0.1", + } + unarmoredBytes, err = mintkey.UnarmorInfoBytes(armor.EncodeArmor( + "TENDERMINT KEY INFO", header, []byte("plain-text"))) + require.Error(t, err) + require.Equal(t, "unrecognized version: 0.0.1", err.Error()) + require.Nil(t, unarmoredBytes) } diff --git a/crypto/ledger_secp256k1.go b/crypto/ledger_secp256k1.go index eb1c9efe51..2c346d4355 100644 --- a/crypto/ledger_secp256k1.go +++ b/crypto/ledger_secp256k1.go @@ -12,7 +12,6 @@ import ( tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1" "github.com/cosmos/cosmos-sdk/crypto/keys/hd" - sdk "github.com/cosmos/cosmos-sdk/types" ) var ( @@ -103,7 +102,9 @@ func (pkl PrivKeyLedgerSecp256k1) Sign(message []byte) ([]byte, error) { } // LedgerShowAddress triggers a ledger device to show the corresponding address. -func LedgerShowAddress(path hd.BIP44Params, expectedPubKey tmcrypto.PubKey) error { +func LedgerShowAddress(path hd.BIP44Params, expectedPubKey tmcrypto.PubKey, + accountAddressPrefix string) error { + device, err := getLedgerDevice() if err != nil { return err @@ -119,8 +120,7 @@ func LedgerShowAddress(path hd.BIP44Params, expectedPubKey tmcrypto.PubKey) erro return fmt.Errorf("the key's pubkey does not match with the one retrieved from Ledger. Check that the HD path and device are the correct ones") } - config := sdk.GetConfig() - pubKey2, _, err := getPubKeyAddrSafe(device, path, config.GetBech32AccountAddrPrefix()) + pubKey2, _, err := getPubKeyAddrSafe(device, path, accountAddressPrefix) if err != nil { return err } diff --git a/crypto/ledger_test.go b/crypto/ledger_test.go index 0f6c4572ad..fbcf9239b4 100644 --- a/crypto/ledger_test.go +++ b/crypto/ledger_test.go @@ -104,6 +104,8 @@ func TestPublicKeySafe(t *testing.T) { require.Nil(t, err, "%s", err) require.NotNil(t, priv) + require.Nil(t, LedgerShowAddress(path, priv.PubKey(), sdk.GetConfig().GetBech32AccountAddrPrefix())) + require.Equal(t, "eb5ae98721034fef9cd7c4c63588d3b03feb5281b9d232cba34d6f3d71aee59211ffbfe1fe87", fmt.Sprintf("%x", priv.PubKey().Bytes()), "Is your device using test mnemonic: %s ?", tests.TestMnemonic) diff --git a/server/constructors_test.go b/server/constructors_test.go index e1da004a63..e11520fe58 100644 --- a/server/constructors_test.go +++ b/server/constructors_test.go @@ -12,7 +12,7 @@ import ( func Test_openDB(t *testing.T) { t.Parallel() dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) _, err := openDB(dir) require.NoError(t, err) } @@ -20,7 +20,7 @@ func Test_openDB(t *testing.T) { func Test_openTraceWriter(t *testing.T) { t.Parallel() dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) fname := filepath.Join(dir, "logfile") w, err := openTraceWriter(fname) require.NoError(t, err) diff --git a/server/init_test.go b/server/init_test.go index 5d353ddd35..349a9c1696 100644 --- a/server/init_test.go +++ b/server/init_test.go @@ -25,7 +25,7 @@ func TestGenerateCoinKey(t *testing.T) { func TestGenerateSaveCoinKey(t *testing.T) { t.Parallel() dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() // clean after itself + t.Cleanup(cleanup) kb, err := crkeys.NewKeyring(t.Name(), "test", dir, nil) require.NoError(t, err) @@ -47,7 +47,7 @@ func TestGenerateSaveCoinKey(t *testing.T) { func TestGenerateSaveCoinKeyOverwriteFlag(t *testing.T) { t.Parallel() dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() // clean after itself + t.Cleanup(cleanup) kb, err := crkeys.NewKeyring(t.Name(), "test", dir, nil) require.NoError(t, err) diff --git a/server/pruning.go b/server/pruning.go new file mode 100644 index 0000000000..07730f36ad --- /dev/null +++ b/server/pruning.go @@ -0,0 +1,24 @@ +package server + +import ( + "github.com/cosmos/cosmos-sdk/store" + "github.com/spf13/viper" +) + +// GetPruningOptionsFromFlags parses start command flags and returns the correct PruningOptions. +// flagPruning prevails over flagPruningKeepEvery and flagPruningSnapshotEvery. +// Default option is PruneSyncable. +func GetPruningOptionsFromFlags() store.PruningOptions { + if viper.IsSet(flagPruning) { + return store.NewPruningOptionsFromString(viper.GetString(flagPruning)) + } + + if viper.IsSet(flagPruningKeepEvery) && viper.IsSet(flagPruningSnapshotEvery) { + return store.PruningOptions{ + KeepEvery: viper.GetInt64(flagPruningKeepEvery), + SnapshotEvery: viper.GetInt64(flagPruningSnapshotEvery), + } + } + + return store.PruneSyncable +} diff --git a/server/pruning_test.go b/server/pruning_test.go new file mode 100644 index 0000000000..916f4e07df --- /dev/null +++ b/server/pruning_test.go @@ -0,0 +1,51 @@ +package server + +import ( + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/store" +) + +func TestGetPruningOptionsFromFlags(t *testing.T) { + tests := []struct { + name string + initParams func() + expectedOptions store.PruningOptions + }{ + { + name: "pruning", + initParams: func() { + viper.Set(flagPruning, store.PruningStrategyNothing) + }, + expectedOptions: store.PruneNothing, + }, + { + name: "granular pruning", + initParams: func() { + viper.Set(flagPruningSnapshotEvery, 1234) + viper.Set(flagPruningKeepEvery, 4321) + }, + expectedOptions: store.PruningOptions{ + SnapshotEvery: 1234, + KeepEvery: 4321, + }, + }, + { + name: "default", + initParams: func() {}, + expectedOptions: store.PruneSyncable, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(j *testing.T) { + viper.Reset() + tt.initParams() + require.Equal(t, tt.expectedOptions, GetPruningOptionsFromFlags()) + }) + } +} diff --git a/server/start.go b/server/start.go index 5a9cd05008..c5bee58e8f 100644 --- a/server/start.go +++ b/server/start.go @@ -20,16 +20,29 @@ import ( // Tendermint full-node start flags const ( - flagWithTendermint = "with-tendermint" - flagAddress = "address" - flagTraceStore = "trace-store" - flagPruning = "pruning" - flagCPUProfile = "cpu-profile" - FlagMinGasPrices = "minimum-gas-prices" - FlagHaltHeight = "halt-height" - FlagHaltTime = "halt-time" - FlagInterBlockCache = "inter-block-cache" - FlagUnsafeSkipUpgrades = "unsafe-skip-upgrades" + flagWithTendermint = "with-tendermint" + flagAddress = "address" + flagTraceStore = "trace-store" + flagPruning = "pruning" + flagPruningKeepEvery = "pruning-keep-every" + flagPruningSnapshotEvery = "pruning-snapshot-every" + flagCPUProfile = "cpu-profile" + FlagMinGasPrices = "minimum-gas-prices" + FlagHaltHeight = "halt-height" + FlagHaltTime = "halt-time" + FlagInterBlockCache = "inter-block-cache" + FlagUnsafeSkipUpgrades = "unsafe-skip-upgrades" +) + +var ( + errPruningWithGranularOptions = fmt.Errorf( + "'--%s' flag is not compatible with granular options '--%s' or '--%s'", + flagPruning, flagPruningKeepEvery, flagPruningSnapshotEvery, + ) + errPruningGranularOptions = fmt.Errorf( + "'--%s' and '--%s' must be set together", + flagPruningSnapshotEvery, flagPruningKeepEvery, + ) ) // StartCmd runs the service passed in, either stand-alone or in-process with @@ -41,7 +54,9 @@ func StartCmd(ctx *Context, appCreator AppCreator) *cobra.Command { Long: `Run the full node application with Tendermint in or out of process. By default, the application will run with Tendermint in process. -Pruning options can be provided via the '--pruning' flag. The options are as follows: +Pruning options can be provided via the '--pruning' flag or alternatively with '--pruning-snapshot-every' and 'pruning-keep-every' together. + +For '--pruning' the options are as follows: syncable: only those states not needed for state syncing will be deleted (flushes every 100th to disk and keeps every 10000th) nothing: all historic states will be saved, nothing will be deleted (i.e. archiving node) @@ -56,6 +71,9 @@ will not be able to commit subsequent blocks. For profiling and benchmarking purposes, CPU profiling can be enabled via the '--cpu-profile' flag which accepts a path for the resulting pprof file. `, + PreRunE: func(cmd *cobra.Command, args []string) error { + return checkPruningParams() + }, RunE: func(cmd *cobra.Command, args []string) error { if !viper.GetBool(flagWithTendermint) { ctx.Logger.Info("starting ABCI without Tendermint") @@ -74,6 +92,8 @@ which accepts a path for the resulting pprof file. cmd.Flags().String(flagAddress, "tcp://0.0.0.0:26658", "Listen address") cmd.Flags().String(flagTraceStore, "", "Enable KVStore tracing to an output file") cmd.Flags().String(flagPruning, "syncable", "Pruning strategy: syncable, nothing, everything") + cmd.Flags().Int64(flagPruningKeepEvery, 0, "Define the state number that will be kept") + cmd.Flags().Int64(flagPruningSnapshotEvery, 0, "Defines the state that will be snapshot for pruning") cmd.Flags().String( FlagMinGasPrices, "", "Minimum gas prices to accept for transactions; Any fee in a tx must meet this minimum (e.g. 0.01photino;0.0001stake)", @@ -89,6 +109,27 @@ which accepts a path for the resulting pprof file. return cmd } +// checkPruningParams checks that the provided pruning params are correct +func checkPruningParams() error { + if !viper.IsSet(flagPruning) && !viper.IsSet(flagPruningKeepEvery) && !viper.IsSet(flagPruningSnapshotEvery) { + return nil + } + + if viper.IsSet(flagPruning) { + if viper.IsSet(flagPruningKeepEvery) || viper.IsSet(flagPruningSnapshotEvery) { + return errPruningWithGranularOptions + } + + return nil + } + + if !(viper.IsSet(flagPruningKeepEvery) && viper.IsSet(flagPruningSnapshotEvery)) { + return errPruningGranularOptions + } + + return nil +} + func startStandAlone(ctx *Context, appCreator AppCreator) error { addr := viper.GetString(flagAddress) home := viper.GetString("home") diff --git a/server/start_test.go b/server/start_test.go new file mode 100644 index 0000000000..c9c4f61ef6 --- /dev/null +++ b/server/start_test.go @@ -0,0 +1,104 @@ +package server + +import ( + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/require" +) + +func TestPruningOptions(t *testing.T) { + startCommand := StartCmd(nil, nil) + + tests := []struct { + name string + paramInit func() + returnsErr bool + expectedErr error + }{ + { + name: "none set, returns nil and will use default from flags", + paramInit: func() {}, + returnsErr: false, + expectedErr: nil, + }, + { + name: "only keep-every provided", + paramInit: func() { + viper.Set(flagPruningKeepEvery, 12345) + }, + returnsErr: true, + expectedErr: errPruningGranularOptions, + }, + { + name: "only snapshot-every provided", + paramInit: func() { + viper.Set(flagPruningSnapshotEvery, 12345) + }, + returnsErr: true, + expectedErr: errPruningGranularOptions, + }, + { + name: "pruning flag with other granular options 1", + paramInit: func() { + viper.Set(flagPruning, "set") + viper.Set(flagPruningSnapshotEvery, 1234) + }, + returnsErr: true, + expectedErr: errPruningWithGranularOptions, + }, + { + name: "pruning flag with other granular options 2", + paramInit: func() { + viper.Set(flagPruning, "set") + viper.Set(flagPruningKeepEvery, 1234) + }, + returnsErr: true, + expectedErr: errPruningWithGranularOptions, + }, + { + name: "pruning flag with other granular options 3", + paramInit: func() { + viper.Set(flagPruning, "set") + viper.Set(flagPruningKeepEvery, 1234) + viper.Set(flagPruningSnapshotEvery, 1234) + }, + returnsErr: true, + expectedErr: errPruningWithGranularOptions, + }, + { + name: "only prunning set", + paramInit: func() { + viper.Set(flagPruning, "set") + }, + returnsErr: false, + expectedErr: nil, + }, + { + name: "only granular set", + paramInit: func() { + viper.Set(flagPruningSnapshotEvery, 12345) + viper.Set(flagPruningKeepEvery, 12345) + }, + returnsErr: false, + expectedErr: nil, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + viper.Reset() + tt.paramInit() + + err := startCommand.PreRunE(nil, nil) + + if tt.returnsErr { + require.EqualError(t, err, tt.expectedErr.Error()) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/store/dbadapter/store_test.go b/store/dbadapter/store_test.go new file mode 100644 index 0000000000..a29e3f6c8d --- /dev/null +++ b/store/dbadapter/store_test.go @@ -0,0 +1,70 @@ +package dbadapter_test + +import ( + "bytes" + "errors" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/store/dbadapter" + "github.com/cosmos/cosmos-sdk/store/types" + "github.com/cosmos/cosmos-sdk/tests/mocks" +) + +var errFoo = errors.New("dummy") + +func TestAccessors(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockDB := mocks.NewMockDB(mockCtrl) + store := dbadapter.Store{mockDB} + key := []byte("test") + value := []byte("testvalue") + + require.Equal(t, types.StoreTypeDB, store.GetStoreType()) + store.GetStoreType() + + retFoo := []byte("xxx") + mockDB.EXPECT().Get(gomock.Eq(key)).Times(1).Return(retFoo, nil) + require.True(t, bytes.Equal(retFoo, store.Get(key))) + + mockDB.EXPECT().Get(gomock.Eq(key)).Times(1).Return(nil, errFoo) + require.Panics(t, func() { store.Get(key) }) + + mockDB.EXPECT().Has(gomock.Eq(key)).Times(1).Return(true, nil) + require.True(t, store.Has(key)) + + mockDB.EXPECT().Has(gomock.Eq(key)).Times(1).Return(false, nil) + require.False(t, store.Has(key)) + + mockDB.EXPECT().Has(gomock.Eq(key)).Times(1).Return(false, errFoo) + require.Panics(t, func() { store.Has(key) }) + + mockDB.EXPECT().Set(gomock.Eq(key), gomock.Eq(value)).Times(1).Return(nil) + require.NotPanics(t, func() { store.Set(key, value) }) + + mockDB.EXPECT().Set(gomock.Eq(key), gomock.Eq(value)).Times(1).Return(errFoo) + require.Panics(t, func() { store.Set(key, value) }) + + mockDB.EXPECT().Delete(gomock.Eq(key)).Times(1).Return(nil) + require.NotPanics(t, func() { store.Delete(key) }) + + mockDB.EXPECT().Delete(gomock.Eq(key)).Times(1).Return(errFoo) + require.Panics(t, func() { store.Delete(key) }) + + start, end := []byte("start"), []byte("end") + mockDB.EXPECT().Iterator(gomock.Eq(start), gomock.Eq(end)).Times(1).Return(nil, nil) + require.NotPanics(t, func() { store.Iterator(start, end) }) + + mockDB.EXPECT().Iterator(gomock.Eq(start), gomock.Eq(end)).Times(1).Return(nil, errFoo) + require.Panics(t, func() { store.Iterator(start, end) }) + + mockDB.EXPECT().ReverseIterator(gomock.Eq(start), gomock.Eq(end)).Times(1).Return(nil, nil) + require.NotPanics(t, func() { store.ReverseIterator(start, end) }) + + mockDB.EXPECT().ReverseIterator(gomock.Eq(start), gomock.Eq(end)).Times(1).Return(nil, errFoo) + require.Panics(t, func() { store.ReverseIterator(start, end) }) +} diff --git a/store/firstlast.go b/store/firstlast.go index 05a9a5fd85..0aab0a53ab 100644 --- a/store/firstlast.go +++ b/store/firstlast.go @@ -5,7 +5,7 @@ import ( tmkv "github.com/tendermint/tendermint/libs/kv" - "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" ) // Gets the first item. @@ -24,7 +24,7 @@ func Last(st KVStore, start, end []byte) (kv tmkv.Pair, ok bool) { iter := st.ReverseIterator(end, start) if !iter.Valid() { if v := st.Get(start); v != nil { - return tmkv.Pair{Key: types.Cp(start), Value: types.Cp(v)}, true + return tmkv.Pair{Key: sdk.CopyBytes(start), Value: sdk.CopyBytes(v)}, true } return kv, false } diff --git a/store/gaskv/store_test.go b/store/gaskv/store_test.go index 6fab9ebf83..ec0d42be85 100644 --- a/store/gaskv/store_test.go +++ b/store/gaskv/store_test.go @@ -22,6 +22,11 @@ func TestGasKVStoreBasic(t *testing.T) { mem := dbadapter.Store{DB: dbm.NewMemDB()} meter := types.NewGasMeter(10000) st := gaskv.NewStore(mem, meter, types.KVGasConfig()) + + require.Equal(t, types.StoreTypeDB, st.GetStoreType()) + require.Panics(t, func() { st.CacheWrap() }) + require.Panics(t, func() { st.CacheWrapWithTrace(nil, nil) }) + require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") st.Set(keyFmt(1), valFmt(1)) require.Equal(t, valFmt(1), st.Get(keyFmt(1))) @@ -34,11 +39,20 @@ func TestGasKVStoreIterator(t *testing.T) { mem := dbadapter.Store{DB: dbm.NewMemDB()} meter := types.NewGasMeter(10000) st := gaskv.NewStore(mem, meter, types.KVGasConfig()) + require.False(t, st.Has(keyFmt(1))) require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") require.Empty(t, st.Get(keyFmt(2)), "Expected `key2` to be empty") st.Set(keyFmt(1), valFmt(1)) + require.True(t, st.Has(keyFmt(1))) st.Set(keyFmt(2), valFmt(2)) + iterator := st.Iterator(nil, nil) + start, end := iterator.Domain() + require.Nil(t, start) + require.Nil(t, end) + require.NoError(t, iterator.Error()) + + t.Cleanup(iterator.Close) ka := iterator.Key() require.Equal(t, ka, keyFmt(1)) va := iterator.Value() @@ -51,7 +65,18 @@ func TestGasKVStoreIterator(t *testing.T) { iterator.Next() require.False(t, iterator.Valid()) require.Panics(t, iterator.Next) - require.Equal(t, meter.GasConsumed(), types.Gas(6987)) + require.NoError(t, iterator.Error()) + + reverseIterator := st.ReverseIterator(nil, nil) + t.Cleanup(reverseIterator.Close) + require.Equal(t, reverseIterator.Key(), keyFmt(2)) + reverseIterator.Next() + require.Equal(t, reverseIterator.Key(), keyFmt(1)) + reverseIterator.Next() + require.False(t, reverseIterator.Valid()) + require.Panics(t, reverseIterator.Next) + + require.Equal(t, types.Gas(9194), meter.GasConsumed()) } func TestGasKVStoreOutOfGasSet(t *testing.T) { diff --git a/store/iavl/store.go b/store/iavl/store.go index d1e1d9c4ca..311d0f887a 100644 --- a/store/iavl/store.go +++ b/store/iavl/store.go @@ -15,6 +15,7 @@ import ( "github.com/cosmos/cosmos-sdk/store/cachekv" "github.com/cosmos/cosmos-sdk/store/tracekv" "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -357,8 +358,8 @@ var _ types.Iterator = (*iavlIterator)(nil) func newIAVLIterator(tree *iavl.ImmutableTree, start, end []byte, ascending bool) *iavlIterator { iter := &iavlIterator{ tree: tree, - start: types.Cp(start), - end: types.Cp(end), + start: sdk.CopyBytes(start), + end: sdk.CopyBytes(end), ascending: ascending, iterCh: make(chan tmkv.Pair), // Set capacity > 0? quitCh: make(chan struct{}), diff --git a/store/iavl/tree_test.go b/store/iavl/tree_test.go new file mode 100644 index 0000000000..7a1eef3023 --- /dev/null +++ b/store/iavl/tree_test.go @@ -0,0 +1,37 @@ +package iavl + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/tendermint/iavl" + dbm "github.com/tendermint/tm-db" +) + +func TestImmutableTreePanics(t *testing.T) { + t.Parallel() + immTree := iavl.NewImmutableTree(dbm.NewMemDB(), 100) + it := &immutableTree{immTree} + require.Panics(t, func() { it.Set([]byte{}, []byte{}) }) + require.Panics(t, func() { it.Remove([]byte{}) }) + require.Panics(t, func() { it.SaveVersion() }) + require.Panics(t, func() { it.DeleteVersion(int64(1)) }) + v, _ := it.GetVersioned([]byte{0x01}, 1) + require.Equal(t, int64(-1), v) + v, _ = it.GetVersioned([]byte{0x01}, 0) + require.Equal(t, int64(0), v) + + val, proof, err := it.GetVersionedWithProof(nil, 1) + require.Error(t, err) + require.Nil(t, val) + require.Nil(t, proof) + + imm, err := it.GetImmutable(1) + require.Error(t, err) + require.Nil(t, imm) + + imm, err = it.GetImmutable(0) + require.NoError(t, err) + require.NotNil(t, imm) + require.Equal(t, immTree, imm) +} diff --git a/store/transient/store_test.go b/store/transient/store_test.go index 846c8a3a43..113d95be86 100644 --- a/store/transient/store_test.go +++ b/store/transient/store_test.go @@ -1,15 +1,18 @@ -package transient +package transient_test import ( + "bytes" "testing" + "github.com/cosmos/cosmos-sdk/store/transient" + "github.com/cosmos/cosmos-sdk/store/types" "github.com/stretchr/testify/require" ) var k, v = []byte("hello"), []byte("world") func TestTransientStore(t *testing.T) { - tstore := NewStore() + tstore := transient.NewStore() require.Nil(t, tstore.Get(k)) @@ -20,4 +23,12 @@ func TestTransientStore(t *testing.T) { tstore.Commit() require.Nil(t, tstore.Get(k)) + + // no-op + tstore.SetPruning(types.PruningOptions{}) + + emptyCommitID := tstore.LastCommitID() + require.Equal(t, emptyCommitID.Version, int64(0)) + require.True(t, bytes.Equal(emptyCommitID.Hash, nil)) + require.Equal(t, types.StoreTypeTransient, tstore.GetStoreType()) } diff --git a/store/types/gas_test.go b/store/types/gas_test.go index 00d3d834db..bd62d463cd 100644 --- a/store/types/gas_test.go +++ b/store/types/gas_test.go @@ -7,7 +7,23 @@ import ( "github.com/stretchr/testify/require" ) +func TestInfiniteGasMeter(t *testing.T) { + t.Parallel() + meter := NewInfiniteGasMeter() + require.Equal(t, uint64(0), meter.Limit()) + require.Equal(t, uint64(0), meter.GasConsumed()) + require.Equal(t, uint64(0), meter.GasConsumedToLimit()) + meter.ConsumeGas(10, "consume 10") + require.Equal(t, uint64(10), meter.GasConsumed()) + require.Equal(t, uint64(10), meter.GasConsumedToLimit()) + require.False(t, meter.IsPastLimit()) + require.False(t, meter.IsOutOfGas()) + meter.ConsumeGas(Gas(math.MaxUint64/2), "consume half max uint64") + require.Panics(t, func() { meter.ConsumeGas(Gas(math.MaxUint64/2)+2, "panic") }) +} + func TestGasMeter(t *testing.T) { + t.Parallel() cases := []struct { limit Gas usage []Gas @@ -41,11 +57,14 @@ func TestGasMeter(t *testing.T) { require.Panics(t, func() { meter.ConsumeGas(1, "") }, "Exceeded but not panicked. tc #%d", tcnum) require.Equal(t, meter.GasConsumedToLimit(), meter.Limit(), "Gas consumption (to limit) not match limit") require.Equal(t, meter.GasConsumed(), meter.Limit()+1, "Gas consumption not match limit+1") - + meter2 := NewGasMeter(math.MaxUint64) + meter2.ConsumeGas(Gas(math.MaxUint64/2), "consume half max uint64") + require.Panics(t, func() { meter2.ConsumeGas(Gas(math.MaxUint64/2)+2, "panic") }) } } func TestAddUint64Overflow(t *testing.T) { + t.Parallel() testCases := []struct { a, b uint64 result uint64 @@ -69,3 +88,17 @@ func TestAddUint64Overflow(t *testing.T) { ) } } + +func TestTransientGasConfig(t *testing.T) { + t.Parallel() + config := TransientGasConfig() + require.Equal(t, config, GasConfig{ + HasCost: 1000, + DeleteCost: 1000, + ReadCostFlat: 1000, + ReadCostPerByte: 3, + WriteCostFlat: 2000, + WriteCostPerByte: 30, + IterNextCostFlat: 30, + }) +} diff --git a/store/types/pruning_test.go b/store/types/pruning_test.go new file mode 100644 index 0000000000..4187d16a4c --- /dev/null +++ b/store/types/pruning_test.go @@ -0,0 +1,79 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/store/types" +) + +func TestPruningOptions_FlushVersion(t *testing.T) { + t.Parallel() + require.True(t, types.PruneEverything.FlushVersion(-1)) + require.True(t, types.PruneEverything.FlushVersion(0)) + require.True(t, types.PruneEverything.FlushVersion(1)) + require.True(t, types.PruneEverything.FlushVersion(2)) + + require.True(t, types.PruneNothing.FlushVersion(-1)) + require.True(t, types.PruneNothing.FlushVersion(0)) + require.True(t, types.PruneNothing.FlushVersion(1)) + require.True(t, types.PruneNothing.FlushVersion(2)) + + require.False(t, types.PruneSyncable.FlushVersion(-1)) + require.True(t, types.PruneSyncable.FlushVersion(0)) + require.False(t, types.PruneSyncable.FlushVersion(1)) + require.True(t, types.PruneSyncable.FlushVersion(100)) + require.False(t, types.PruneSyncable.FlushVersion(101)) +} + +func TestPruningOptions_SnapshotVersion(t *testing.T) { + t.Parallel() + require.False(t, types.PruneEverything.SnapshotVersion(-1)) + require.False(t, types.PruneEverything.SnapshotVersion(0)) + require.False(t, types.PruneEverything.SnapshotVersion(1)) + require.False(t, types.PruneEverything.SnapshotVersion(2)) + + require.True(t, types.PruneNothing.SnapshotVersion(-1)) + require.True(t, types.PruneNothing.SnapshotVersion(0)) + require.True(t, types.PruneNothing.SnapshotVersion(1)) + require.True(t, types.PruneNothing.SnapshotVersion(2)) + + require.False(t, types.PruneSyncable.SnapshotVersion(-1)) + require.True(t, types.PruneSyncable.SnapshotVersion(0)) + require.False(t, types.PruneSyncable.SnapshotVersion(1)) + require.True(t, types.PruneSyncable.SnapshotVersion(10000)) + require.False(t, types.PruneSyncable.SnapshotVersion(10001)) +} + +func TestPruningOptions_IsValid(t *testing.T) { + t.Parallel() + type fields struct { + KeepEvery int64 + SnapshotEvery int64 + } + tests := []struct { + name string + fields fields + want bool + }{ + {"PruneEverything", fields{types.PruneEverything.KeepEvery, types.PruneEverything.SnapshotEvery}, true}, + {"PruneNothing", fields{types.PruneNothing.KeepEvery, types.PruneNothing.SnapshotEvery}, true}, + {"PruneSyncable", fields{types.PruneSyncable.KeepEvery, types.PruneSyncable.SnapshotEvery}, true}, + {"KeepEvery=0", fields{0, 0}, false}, + {"KeepEvery<0", fields{-1, 0}, false}, + {"SnapshotEvery<0", fields{1, -1}, false}, + {"SnapshotEvery%KeepEvery!=0", fields{15, 30}, true}, + {"SnapshotEvery%KeepEvery!=0", fields{15, 20}, false}, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + po := types.PruningOptions{ + KeepEvery: tt.fields.KeepEvery, + SnapshotEvery: tt.fields.SnapshotEvery, + } + require.Equal(t, tt.want, po.IsValid(), "IsValid() = %v, want %v", po.IsValid(), tt.want) + }) + } +} diff --git a/store/types/store_test.go b/store/types/store_test.go index 7ef1d6e889..bf70af1bb6 100644 --- a/store/types/store_test.go +++ b/store/types/store_test.go @@ -1,12 +1,15 @@ package types import ( + "fmt" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestStoreUpgrades(t *testing.T) { + t.Parallel() type toDelete struct { key string delete bool @@ -55,3 +58,27 @@ func TestStoreUpgrades(t *testing.T) { }) } } + +func TestCommitID(t *testing.T) { + t.Parallel() + require.True(t, CommitID{}.IsZero()) + require.False(t, CommitID{Version: int64(1)}.IsZero()) + require.False(t, CommitID{Hash: []byte("x")}.IsZero()) + require.Equal(t, "CommitID{[120 120 120 120]:64}", CommitID{Version: int64(100), Hash: []byte("xxxx")}.String()) +} + +func TestKVStoreKey(t *testing.T) { + t.Parallel() + key := NewKVStoreKey("test") + require.Equal(t, "test", key.name) + require.Equal(t, key.name, key.Name()) + require.Equal(t, fmt.Sprintf("KVStoreKey{%p, test}", key), key.String()) +} + +func TestTransientStoreKey(t *testing.T) { + t.Parallel() + key := NewTransientStoreKey("test") + require.Equal(t, "test", key.name) + require.Equal(t, key.name, key.Name()) + require.Equal(t, fmt.Sprintf("TransientStoreKey{%p, test}", key), key.String()) +} diff --git a/store/types/utils.go b/store/types/utils.go index 3149c07fb6..8d4c837354 100644 --- a/store/types/utils.go +++ b/store/types/utils.go @@ -17,15 +17,18 @@ func KVStoreReversePrefixIterator(kvs KVStore, prefix []byte) Iterator { } // DiffKVStores compares two KVstores and returns all the key/value pairs -// that differ from one another. It also skips value comparison for a set of provided prefixes +// that differ from one another. It also skips value comparison for a set of provided prefixes. func DiffKVStores(a KVStore, b KVStore, prefixesToSkip [][]byte) (kvAs, kvBs []tmkv.Pair) { iterA := a.Iterator(nil, nil) + defer iterA.Close() iterB := b.Iterator(nil, nil) + defer iterB.Close() for { if !iterA.Valid() && !iterB.Valid() { - break + return kvAs, kvBs } + var kvA, kvB tmkv.Pair if iterA.Valid() { kvA = tmkv.Pair{Key: iterA.Key(), Value: iterA.Value()} @@ -38,7 +41,9 @@ func DiffKVStores(a KVStore, b KVStore, prefixesToSkip [][]byte) (kvAs, kvBs []t if !bytes.Equal(kvA.Key, kvB.Key) { kvAs = append(kvAs, kvA) kvBs = append(kvBs, kvB) + continue // no need to compare the value } + compareValue := true for _, prefix := range prefixesToSkip { // Skip value comparison if we matched a prefix @@ -51,7 +56,6 @@ func DiffKVStores(a KVStore, b KVStore, prefixesToSkip [][]byte) (kvAs, kvBs []t kvBs = append(kvBs, kvB) } } - return kvAs, kvBs } // PrefixEndBytes returns the []byte that would end a @@ -69,13 +73,13 @@ func PrefixEndBytes(prefix []byte) []byte { if end[len(end)-1] != byte(255) { end[len(end)-1]++ break - } else { - end = end[:len(end)-1] - if len(end) == 0 { - end = nil - break - } } + end = end[:len(end)-1] + if len(end) == 0 { + end = nil + break + } + } return end } @@ -85,13 +89,3 @@ func PrefixEndBytes(prefix []byte) []byte { func InclusiveEndBytes(inclusiveBytes []byte) []byte { return append(inclusiveBytes, byte(0x00)) } - -//---------------------------------------- -func Cp(bz []byte) (ret []byte) { - if bz == nil { - return nil - } - ret = make([]byte, len(bz)) - copy(ret, bz) - return ret -} diff --git a/store/types/utils_test.go b/store/types/utils_test.go new file mode 100644 index 0000000000..32064d7e18 --- /dev/null +++ b/store/types/utils_test.go @@ -0,0 +1,88 @@ +package types_test + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" + dbm "github.com/tendermint/tm-db" + + "github.com/cosmos/cosmos-sdk/store/rootmulti" + "github.com/cosmos/cosmos-sdk/store/types" +) + +func initTestStores(t *testing.T) (types.KVStore, types.KVStore) { + db := dbm.NewMemDB() + ms := rootmulti.NewStore(db) + + key1 := types.NewKVStoreKey("store1") + key2 := types.NewKVStoreKey("store2") + require.NotPanics(t, func() { ms.MountStoreWithDB(key1, types.StoreTypeIAVL, db) }) + require.NotPanics(t, func() { ms.MountStoreWithDB(key2, types.StoreTypeIAVL, db) }) + require.NoError(t, ms.LoadLatestVersion()) + return ms.GetKVStore(key1), ms.GetKVStore(key2) +} + +func TestDiffKVStores(t *testing.T) { + t.Parallel() + store1, store2 := initTestStores(t) + // Two equal stores + k1, v1 := []byte("k1"), []byte("v1") + store1.Set(k1, v1) + store2.Set(k1, v1) + + kvAs, kvBs := types.DiffKVStores(store1, store2, nil) + require.Equal(t, 0, len(kvAs)) + require.Equal(t, len(kvAs), len(kvBs)) + + // delete k1 from store2, which is now empty + store2.Delete(k1) + kvAs, kvBs = types.DiffKVStores(store1, store2, nil) + require.Equal(t, 1, len(kvAs)) + require.Equal(t, len(kvAs), len(kvBs)) + + // set k1 in store2, different value than what store1 holds for k1 + v2 := []byte("v2") + store2.Set(k1, v2) + kvAs, kvBs = types.DiffKVStores(store1, store2, nil) + require.Equal(t, 1, len(kvAs)) + require.Equal(t, len(kvAs), len(kvBs)) + + // add k2 to store2 + k2 := []byte("k2") + store2.Set(k2, v2) + kvAs, kvBs = types.DiffKVStores(store1, store2, nil) + require.Equal(t, 2, len(kvAs)) + require.Equal(t, len(kvAs), len(kvBs)) + + // Reset stores + store1.Delete(k1) + store2.Delete(k1) + store2.Delete(k2) + + // Same keys, different value. Comparisons will be nil as prefixes are skipped. + prefix := []byte("prefix:") + k1Prefixed := append(prefix, k1...) + store1.Set(k1Prefixed, v1) + store2.Set(k1Prefixed, v2) + kvAs, kvBs = types.DiffKVStores(store1, store2, [][]byte{prefix}) + require.Equal(t, 0, len(kvAs)) + require.Equal(t, len(kvAs), len(kvBs)) +} + +func TestPrefixEndBytes(t *testing.T) { + t.Parallel() + bs1 := []byte{0x23, 0xA5, 0x06} + require.True(t, bytes.Equal([]byte{0x23, 0xA5, 0x07}, types.PrefixEndBytes(bs1))) + bs2 := []byte{0x23, 0xA5, 0xFF} + require.True(t, bytes.Equal([]byte{0x23, 0xA6}, types.PrefixEndBytes(bs2))) + require.Nil(t, types.PrefixEndBytes([]byte{0xFF})) + require.Nil(t, types.PrefixEndBytes(nil)) +} + +func TestInclusiveEndBytes(t *testing.T) { + t.Parallel() + require.True(t, bytes.Equal([]byte{0x00}, types.InclusiveEndBytes(nil))) + bs := []byte("test") + require.True(t, bytes.Equal(append(bs, byte(0x00)), types.InclusiveEndBytes(bs))) +} diff --git a/store/types/validity_test.go b/store/types/validity_test.go new file mode 100644 index 0000000000..e2adcd19c4 --- /dev/null +++ b/store/types/validity_test.go @@ -0,0 +1,23 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/store/types" +) + +func TestAssertValidKey(t *testing.T) { + t.Parallel() + require.NotPanics(t, func() { types.AssertValidKey([]byte{}) }) + require.NotPanics(t, func() { types.AssertValidKey([]byte{0x01}) }) + require.Panics(t, func() { types.AssertValidKey(nil) }) +} + +func TestAssertValidValue(t *testing.T) { + t.Parallel() + require.NotPanics(t, func() { types.AssertValidValue([]byte{}) }) + require.NotPanics(t, func() { types.AssertValidValue([]byte{0x01}) }) + require.Panics(t, func() { types.AssertValidValue(nil) }) +} diff --git a/tests/mocks/account_retriever.go b/tests/mocks/account_retriever.go index 9f8ee69282..a3086c77bd 100644 --- a/tests/mocks/account_retriever.go +++ b/tests/mocks/account_retriever.go @@ -1,30 +1,38 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: x/auth/types/account_retriever.go + +// Package mocks is a generated GoMock package. package mocks import ( - reflect "reflect" - gomock "github.com/golang/mock/gomock" + reflect "reflect" ) +// MockNodeQuerier is a mock of NodeQuerier interface type MockNodeQuerier struct { ctrl *gomock.Controller recorder *MockNodeQuerierMockRecorder } +// MockNodeQuerierMockRecorder is the mock recorder for MockNodeQuerier type MockNodeQuerierMockRecorder struct { mock *MockNodeQuerier } +// NewMockNodeQuerier creates a new mock instance func NewMockNodeQuerier(ctrl *gomock.Controller) *MockNodeQuerier { mock := &MockNodeQuerier{ctrl: ctrl} mock.recorder = &MockNodeQuerierMockRecorder{mock} return mock } +// EXPECT returns an object that allows the caller to indicate expected use func (m *MockNodeQuerier) EXPECT() *MockNodeQuerierMockRecorder { return m.recorder } +// QueryWithData mocks base method func (m *MockNodeQuerier) QueryWithData(path string, data []byte) ([]byte, int64, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "QueryWithData", path, data) @@ -34,6 +42,7 @@ func (m *MockNodeQuerier) QueryWithData(path string, data []byte) ([]byte, int64 return ret0, ret1, ret2 } +// QueryWithData indicates an expected call of QueryWithData func (mr *MockNodeQuerierMockRecorder) QueryWithData(path, data interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryWithData", reflect.TypeOf((*MockNodeQuerier)(nil).QueryWithData), path, data) diff --git a/tests/mocks/tendermint_tm_db_DB.go b/tests/mocks/tendermint_tm_db_DB.go new file mode 100644 index 0000000000..bed59a498d --- /dev/null +++ b/tests/mocks/tendermint_tm_db_DB.go @@ -0,0 +1,206 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/tendermint/tm-db (interfaces: DB) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + gomock "github.com/golang/mock/gomock" + tm_db "github.com/tendermint/tm-db" + reflect "reflect" +) + +// MockDB is a mock of DB interface +type MockDB struct { + ctrl *gomock.Controller + recorder *MockDBMockRecorder +} + +// MockDBMockRecorder is the mock recorder for MockDB +type MockDBMockRecorder struct { + mock *MockDB +} + +// NewMockDB creates a new mock instance +func NewMockDB(ctrl *gomock.Controller) *MockDB { + mock := &MockDB{ctrl: ctrl} + mock.recorder = &MockDBMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockDB) EXPECT() *MockDBMockRecorder { + return m.recorder +} + +// Close mocks base method +func (m *MockDB) Close() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Close") + ret0, _ := ret[0].(error) + return ret0 +} + +// Close indicates an expected call of Close +func (mr *MockDBMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockDB)(nil).Close)) +} + +// Delete mocks base method +func (m *MockDB) Delete(arg0 []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete +func (mr *MockDBMockRecorder) Delete(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockDB)(nil).Delete), arg0) +} + +// DeleteSync mocks base method +func (m *MockDB) DeleteSync(arg0 []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteSync", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteSync indicates an expected call of DeleteSync +func (mr *MockDBMockRecorder) DeleteSync(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSync", reflect.TypeOf((*MockDB)(nil).DeleteSync), arg0) +} + +// Get mocks base method +func (m *MockDB) Get(arg0 []byte) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get +func (mr *MockDBMockRecorder) Get(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockDB)(nil).Get), arg0) +} + +// Has mocks base method +func (m *MockDB) Has(arg0 []byte) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Has", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Has indicates an expected call of Has +func (mr *MockDBMockRecorder) Has(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Has", reflect.TypeOf((*MockDB)(nil).Has), arg0) +} + +// Iterator mocks base method +func (m *MockDB) Iterator(arg0, arg1 []byte) (tm_db.Iterator, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Iterator", arg0, arg1) + ret0, _ := ret[0].(tm_db.Iterator) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Iterator indicates an expected call of Iterator +func (mr *MockDBMockRecorder) Iterator(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Iterator", reflect.TypeOf((*MockDB)(nil).Iterator), arg0, arg1) +} + +// NewBatch mocks base method +func (m *MockDB) NewBatch() tm_db.Batch { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewBatch") + ret0, _ := ret[0].(tm_db.Batch) + return ret0 +} + +// NewBatch indicates an expected call of NewBatch +func (mr *MockDBMockRecorder) NewBatch() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewBatch", reflect.TypeOf((*MockDB)(nil).NewBatch)) +} + +// Print mocks base method +func (m *MockDB) Print() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Print") + ret0, _ := ret[0].(error) + return ret0 +} + +// Print indicates an expected call of Print +func (mr *MockDBMockRecorder) Print() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Print", reflect.TypeOf((*MockDB)(nil).Print)) +} + +// ReverseIterator mocks base method +func (m *MockDB) ReverseIterator(arg0, arg1 []byte) (tm_db.Iterator, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReverseIterator", arg0, arg1) + ret0, _ := ret[0].(tm_db.Iterator) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ReverseIterator indicates an expected call of ReverseIterator +func (mr *MockDBMockRecorder) ReverseIterator(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReverseIterator", reflect.TypeOf((*MockDB)(nil).ReverseIterator), arg0, arg1) +} + +// Set mocks base method +func (m *MockDB) Set(arg0, arg1 []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Set", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Set indicates an expected call of Set +func (mr *MockDBMockRecorder) Set(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockDB)(nil).Set), arg0, arg1) +} + +// SetSync mocks base method +func (m *MockDB) SetSync(arg0, arg1 []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetSync", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetSync indicates an expected call of SetSync +func (mr *MockDBMockRecorder) SetSync(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSync", reflect.TypeOf((*MockDB)(nil).SetSync), arg0, arg1) +} + +// Stats mocks base method +func (m *MockDB) Stats() map[string]string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Stats") + ret0, _ := ret[0].(map[string]string) + return ret0 +} + +// Stats indicates an expected call of Stats +func (mr *MockDBMockRecorder) Stats() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stats", reflect.TypeOf((*MockDB)(nil).Stats)) +} diff --git a/types/bytes.go b/types/bytes.go deleted file mode 100644 index 600af9f04c..0000000000 --- a/types/bytes.go +++ /dev/null @@ -1,11 +0,0 @@ -package types - -// copy bytes -func CopyBytes(bz []byte) (ret []byte) { - if bz == nil { - return nil - } - ret = make([]byte, len(bz)) - copy(ret, bz) - return ret -} diff --git a/types/utils.go b/types/utils.go index 2a6add6b45..be33160261 100644 --- a/types/utils.go +++ b/types/utils.go @@ -80,3 +80,13 @@ func NewLevelDB(name, dir string) (db dbm.DB, err error) { }() return dbm.NewDB(name, backend, dir), err } + +// copy bytes +func CopyBytes(bz []byte) (ret []byte) { + if bz == nil { + return nil + } + ret = make([]byte, len(bz)) + copy(ret, bz) + return ret +} diff --git a/types/utils_test.go b/types/utils_test.go index 352beccd42..6aa652220e 100644 --- a/types/utils_test.go +++ b/types/utils_test.go @@ -1,6 +1,7 @@ package types import ( + "bytes" "testing" "time" @@ -66,3 +67,12 @@ func TestTimeFormatAndParse(t *testing.T) { require.Equal(t, timeFromRFC.Format(SortableTimeFormat), tc.SDKSortableTimeStr) } } + +func TestCopyBytes(t *testing.T) { + t.Parallel() + require.Nil(t, CopyBytes(nil)) + require.Equal(t, 0, len(CopyBytes([]byte{}))) + bs := []byte("test") + bsCopy := CopyBytes(bs) + require.True(t, bytes.Equal(bs, bsCopy)) +} diff --git a/x/genutil/client/cli/init_test.go b/x/genutil/client/cli/init_test.go index 6ab1ef208a..9bf2e3b146 100644 --- a/x/genutil/client/cli/init_test.go +++ b/x/genutil/client/cli/init_test.go @@ -27,10 +27,10 @@ import ( var testMbm = module.NewBasicManager(genutil.AppModuleBasic{}) func TestInitCmd(t *testing.T) { - defer server.SetupViper(t)() - defer setupClientHome(t)() + t.Cleanup(server.SetupViper(t)) + t.Cleanup(server.SetupViper(t)) home, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) logger := log.NewNopLogger() cfg, err := tcmd.ParseConfig() @@ -50,11 +50,11 @@ func setupClientHome(t *testing.T) func() { } func TestEmptyState(t *testing.T) { - defer server.SetupViper(t)() - defer setupClientHome(t)() + t.Cleanup(server.SetupViper(t)) + t.Cleanup(setupClientHome(t)) home, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) logger := log.NewNopLogger() cfg, err := tcmd.ParseConfig() @@ -94,9 +94,9 @@ func TestEmptyState(t *testing.T) { func TestStartStandAlone(t *testing.T) { home, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) viper.Set(cli.HomeFlag, home) - defer setupClientHome(t)() + t.Cleanup(setupClientHome(t)) logger := log.NewNopLogger() cfg, err := tcmd.ParseConfig() @@ -124,7 +124,7 @@ func TestStartStandAlone(t *testing.T) { func TestInitNodeValidatorFiles(t *testing.T) { home, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) viper.Set(cli.HomeFlag, home) viper.Set(flags.FlagName, "moniker") cfg, err := tcmd.ParseConfig() diff --git a/x/genutil/client/cli/migrate_test.go b/x/genutil/client/cli/migrate_test.go index 423629efcb..d7ea942500 100644 --- a/x/genutil/client/cli/migrate_test.go +++ b/x/genutil/client/cli/migrate_test.go @@ -38,6 +38,7 @@ func TestGetMigrationCallback(t *testing.T) { func TestMigrateGenesis(t *testing.T) { home, cleanup := tests.NewTestCaseDir(t) + t.Cleanup(cleanup) viper.Set(cli.HomeFlag, home) viper.Set(flags.FlagName, "moniker") logger := log.NewNopLogger() @@ -49,8 +50,6 @@ func TestMigrateGenesis(t *testing.T) { genesisPath := path.Join(home, "genesis.json") target := "v0.36" - defer cleanup() - // Reject if we dont' have the right parameters or genesis does not exists require.Error(t, MigrateGenesisCmd(ctx, cdc).RunE(nil, []string{target, genesisPath})) diff --git a/x/genutil/utils_test.go b/x/genutil/utils_test.go index cb04c8f866..4c93fbd925 100644 --- a/x/genutil/utils_test.go +++ b/x/genutil/utils_test.go @@ -14,7 +14,7 @@ import ( func TestExportGenesisFileWithTime(t *testing.T) { t.Parallel() dir, cleanup := tests.NewTestCaseDir(t) - defer cleanup() + t.Cleanup(cleanup) fname := filepath.Join(dir, "genesis.json") require.NoError(t, ExportGenesisFileWithTime(fname, "test", nil, json.RawMessage(""), time.Now())) diff --git a/x/gov/keeper/test_common.go b/x/gov/keeper/test_common.go index 983df0579f..1184f8b88a 100644 --- a/x/gov/keeper/test_common.go +++ b/x/gov/keeper/test_common.go @@ -56,10 +56,6 @@ var ( pubkeys = []crypto.PubKey{ delPk1, delPk2, delPk3, valOpPk1, valOpPk2, valOpPk3, } - - emptyDelAddr sdk.AccAddress - emptyValAddr sdk.ValAddress - emptyPubkey crypto.PubKey ) // TODO: remove dependency with staking @@ -76,7 +72,7 @@ func newPubKey(pk string) (res crypto.PubKey) { panic(err) } var pkEd ed25519.PubKeyEd25519 - copy(pkEd[:], pkBytes[:]) + copy(pkEd[:], pkBytes) return pkEd } diff --git a/x/upgrade/types/storeloader_test.go b/x/upgrade/types/storeloader_test.go index 955ab07cdf..019b26bffb 100644 --- a/x/upgrade/types/storeloader_test.go +++ b/x/upgrade/types/storeloader_test.go @@ -68,8 +68,8 @@ func checkStore(t *testing.T, db dbm.DB, ver int64, storeKey string, k, v []byte // Test that LoadLatestVersion actually does. func TestSetLoader(t *testing.T) { // set a temporary home dir - homeDir, cleanUp := tests.NewTestCaseDir(t) - defer cleanUp() + homeDir, cleanup := tests.NewTestCaseDir(t) + t.Cleanup(cleanup) // TODO cleanup viper viper.Set(flags.FlagHome, homeDir)